Skip to content

Commit

Permalink
Allow "KEYINFO --list"
Browse files Browse the repository at this point in the history
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
  • Loading branch information
DemiMarie committed Oct 22, 2024
1 parent 2202d1d commit a198651
Showing 1 changed file with 28 additions and 24 deletions.
52 changes: 28 additions & 24 deletions splitgpg2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,10 +454,14 @@ async def connect_agent(self) -> None:

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

@staticmethod
def verify_keygrip_arguments(min_count: int, max_count: int,
untrusted_args: Optional[bytes]) -> bytes:
untrusted_args: Optional[bytes],
allow_list: bool) -> bytes:
if untrusted_args is None:
raise Filtered
args_regex = re.compile(rb'\A[0-9A-F]{40}( [0-9A-F]{40}){%d,%d}\Z' %
(min_count-1, max_count-1))
if allow_list and untrusted_args.startswith(b'--list'):
if untrusted_args == b'--list':
pass
elif untrusted_args[6] == 61: # ASCII '='
# 1000 is the default value used by gpg2
sanitize_int(untrusted_args[len(b'--list='):], 1, 1000)
else:
raise Filtered
else:
args_regex = re.compile(rb'\A[0-9A-F]{40}( [0-9A-F]{40}){%d,%d}\Z' %
(min_count-1, max_count-1))

if args_regex.match(untrusted_args) is None:
raise Filtered
if args_regex.match(untrusted_args) is None:
raise Filtered
return untrusted_args

def sanitize_key_desc(self, untrusted_args: bytes) -> bytes:
Expand Down Expand Up @@ -731,21 +745,11 @@ async def command_HAVEKEY(self, untrusted_args: Optional[bytes]) -> None:
if untrusted_args is None:
raise Filtered
# upper keygrip limit is arbitary
if untrusted_args.startswith(b'--list'):
if b'=' in untrusted_args:
# 1000 is the default value used by gpg2
limit = sanitize_int(untrusted_args[len(b'--list='):], 1, 1000)
args = b'--list=%d' % limit
else:
if untrusted_args != b'--list':
raise Filtered
args = b'--list'
else:
args = self.verify_keygrip_arguments(1, 200, untrusted_args)
args = self.verify_keygrip_arguments(1, 200, untrusted_args, True)
await self.send_agent_command(b'HAVEKEY', args)

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

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

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

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

Expand Down Expand Up @@ -996,7 +1000,7 @@ async def command_READKEY(self, untrusted_args: Optional[bytes]) -> None:
raise Filtered
if untrusted_args.startswith(b'-- '):
untrusted_args = untrusted_args[3:]
args = self.verify_keygrip_arguments(1, 1, untrusted_args)
args = self.verify_keygrip_arguments(1, 1, untrusted_args, False)

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

Expand Down

0 comments on commit a198651

Please sign in to comment.