Skip to content

Commit a198651

Browse files
committed
Allow "KEYINFO --list"
Currently, this command is blocked. GnuPG detects that the agent connection is restricted and doesn't try to use it, while Sequoia Chameleon does not implement the fallback and is unable to list secret keys or decrypt messages. Furthermore, gpg prints "gpg: problem with fast path key listing: Forbidden - ignored", which Mutt interprets as a prompt the user must respond to. This causes the user to need to press enter twice to send a signed email. Fix these problems by allowing this request. The request does not work over a restricted connection, so an unrestricted connection must be used. However, the filtering done by split-gpg2 is far stronger than the access checks in gpg-agent so there is no loss of security. Fixes: QubesOS/qubes-issues#9529
1 parent 2202d1d commit a198651

File tree

1 file changed

+28
-24
lines changed

1 file changed

+28
-24
lines changed

splitgpg2/__init__.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -454,10 +454,14 @@ async def connect_agent(self) -> None:
454454

455455
dirs = subprocess.check_output(
456456
['gpgconf', *self.homedir_opts(), '--list-dirs', '-o/dev/stdout'])
457-
if self.allow_keygen:
458-
socket_field = b'agent-socket:'
459-
else:
460-
socket_field = b'agent-extra-socket:'
457+
# Do not use the restricted socket.
458+
# Sequoia Chameleon is unable to list secret keys or decrypt messages,
459+
# and gpg prints "gpg: problem with fast path key listing: Forbidden - ignored",
460+
# which causes Mutt to require the user to press "Enter" again before sending
461+
# a message.
462+
# The filtering done by split-gpg2 is far stronger than anything the agent does
463+
# internally.
464+
socket_field = b'agent-socket:'
461465
# search for agent-socket:/run/user/1000/gnupg/S.gpg-agent
462466
agent_socket_path = [d.split(b':', 1)[1] for d in dirs.splitlines()
463467
if d.startswith(socket_field)][0]
@@ -641,14 +645,24 @@ def fake_respond(self, response: bytes) -> None:
641645

642646
@staticmethod
643647
def verify_keygrip_arguments(min_count: int, max_count: int,
644-
untrusted_args: Optional[bytes]) -> bytes:
648+
untrusted_args: Optional[bytes],
649+
allow_list: bool) -> bytes:
645650
if untrusted_args is None:
646651
raise Filtered
647-
args_regex = re.compile(rb'\A[0-9A-F]{40}( [0-9A-F]{40}){%d,%d}\Z' %
648-
(min_count-1, max_count-1))
652+
if allow_list and untrusted_args.startswith(b'--list'):
653+
if untrusted_args == b'--list':
654+
pass
655+
elif untrusted_args[6] == 61: # ASCII '='
656+
# 1000 is the default value used by gpg2
657+
sanitize_int(untrusted_args[len(b'--list='):], 1, 1000)
658+
else:
659+
raise Filtered
660+
else:
661+
args_regex = re.compile(rb'\A[0-9A-F]{40}( [0-9A-F]{40}){%d,%d}\Z' %
662+
(min_count-1, max_count-1))
649663

650-
if args_regex.match(untrusted_args) is None:
651-
raise Filtered
664+
if args_regex.match(untrusted_args) is None:
665+
raise Filtered
652666
return untrusted_args
653667

654668
def sanitize_key_desc(self, untrusted_args: bytes) -> bytes:
@@ -731,21 +745,11 @@ async def command_HAVEKEY(self, untrusted_args: Optional[bytes]) -> None:
731745
if untrusted_args is None:
732746
raise Filtered
733747
# upper keygrip limit is arbitary
734-
if untrusted_args.startswith(b'--list'):
735-
if b'=' in untrusted_args:
736-
# 1000 is the default value used by gpg2
737-
limit = sanitize_int(untrusted_args[len(b'--list='):], 1, 1000)
738-
args = b'--list=%d' % limit
739-
else:
740-
if untrusted_args != b'--list':
741-
raise Filtered
742-
args = b'--list'
743-
else:
744-
args = self.verify_keygrip_arguments(1, 200, untrusted_args)
748+
args = self.verify_keygrip_arguments(1, 200, untrusted_args, True)
745749
await self.send_agent_command(b'HAVEKEY', args)
746750

747751
async def command_KEYINFO(self, untrusted_args: Optional[bytes]) -> None:
748-
args = self.verify_keygrip_arguments(1, 1, untrusted_args)
752+
args = self.verify_keygrip_arguments(1, 1, untrusted_args, True)
749753
await self.send_agent_command(b'KEYINFO', args)
750754

751755
async def command_GENKEY(self, untrusted_args: Optional[bytes]) -> None:
@@ -785,12 +789,12 @@ async def command_GENKEY(self, untrusted_args: Optional[bytes]) -> None:
785789
await self.send_agent_command(b'GENKEY', b' '.join(args))
786790

787791
async def command_SIGKEY(self, untrusted_args: Optional[bytes]) -> None:
788-
args = self.verify_keygrip_arguments(1, 1, untrusted_args)
792+
args = self.verify_keygrip_arguments(1, 1, untrusted_args, False)
789793
await self.send_agent_command(b'SIGKEY', args)
790794
await self.setkeydesc(args)
791795

792796
async def command_SETKEY(self, untrusted_args: Optional[bytes]) -> None:
793-
args = self.verify_keygrip_arguments(1, 1, untrusted_args)
797+
args = self.verify_keygrip_arguments(1, 1, untrusted_args, False)
794798
await self.send_agent_command(b'SETKEY', args)
795799
await self.setkeydesc(args)
796800

@@ -996,7 +1000,7 @@ async def command_READKEY(self, untrusted_args: Optional[bytes]) -> None:
9961000
raise Filtered
9971001
if untrusted_args.startswith(b'-- '):
9981002
untrusted_args = untrusted_args[3:]
999-
args = self.verify_keygrip_arguments(1, 1, untrusted_args)
1003+
args = self.verify_keygrip_arguments(1, 1, untrusted_args, False)
10001004

10011005
await self.send_agent_command(b'READKEY', b'-- ' + args)
10021006

0 commit comments

Comments
 (0)