@@ -218,6 +218,7 @@ class GpgServer:
218218 commands : Dict [bytes , 'NoneCallback' ]
219219 seen_data : bool
220220 config_loaded : bool
221+ agent_unrestricted_socket_path : Optional [str ]
221222 agent_socket_path : Optional [str ]
222223 agent_reader : Optional [asyncio .StreamReader ]
223224 agent_writer : Optional [asyncio .StreamWriter ]
@@ -245,6 +246,7 @@ class GpgServer:
245246 'seen_data' ,
246247 'config_loaded' ,
247248 'agent_socket_path' ,
249+ 'agent_unrestricted_socket_path' ,
248250 'agent_reader' ,
249251 'agent_writer' ,
250252 'source_keyring_dir' ,
@@ -275,6 +277,7 @@ def __init__(self, reader: asyncio.StreamReader,
275277
276278 self .log = logging .getLogger ('splitgpg2.Server' )
277279 self .agent_socket_path = None
280+ self .agent_unrestricted_socket_path = None
278281 self .agent_reader : Optional [asyncio .StreamReader ] = None
279282 self .agent_writer : Optional [asyncio .StreamWriter ] = None
280283
@@ -463,11 +466,20 @@ async def connect_agent(self) -> None:
463466 # a message.
464467 # The filtering done by split-gpg2 is far stronger than anything the agent does
465468 # internally.
466- socket_field = b'agent-socket:'
469+ unrestricted_socket_field = b'agent-socket'
470+ socket_field = unrestricted_socket_field if self .allow_keygen else b'agent-extra-socket'
467471 # search for agent-socket:/run/user/1000/gnupg/S.gpg-agent
468- agent_socket_path = [d .split (b':' , 1 )[1 ] for d in dirs .splitlines ()
469- if d .startswith (socket_field )][0 ]
470- self .agent_socket_path = agent_socket_path .decode ()
472+ for d in dirs .splitlines ():
473+ key , value = d .split (b':' )
474+ if key == socket_field :
475+ self .agent_socket_path = value .decode ("UTF-8" , "surrogateescape" )
476+ if key == unrestricted_socket_field :
477+ self .agent_unrestricted_socket_path = value .decode ("UTF-8" , "surrogateescape" )
478+ if ((self .agent_unrestricted_socket_path is not None ) and
479+ (self .agent_socket_path is not None )):
480+ break
481+ else :
482+ raise RuntimeError ("bad output from gpgconf" )
471483
472484 self .agent_reader , self .agent_writer = await asyncio .open_unix_connection (
473485 path = self .agent_socket_path )
@@ -476,7 +488,7 @@ async def connect_agent(self) -> None:
476488 self .notify ('connected' )
477489
478490 # wait for agent hello
479- await self .handle_agent_response ({})
491+ await self .handle_agent_response ({}, self . agent_reader )
480492
481493 def close (self , reason : str , log_level : int = logging .ERROR ) -> None :
482494 self .log .log (log_level , '%s; Closing!' , reason )
@@ -563,8 +575,8 @@ def default_options() -> Dict[bytes, Tuple[OptionHandlingType, Optional[bytes]]]
563575 b'lc-messages' : (OptionHandlingType .fake , b'OK' ),
564576 b'putenv' : (OptionHandlingType .fake , b'OK' ),
565577 b'pinentry-mode' : (OptionHandlingType .fake , b'ERR 67108924 Not supported <GPG Agent>' ),
566- b'allow-pinentry-notify' : (OptionHandlingType .verify , None ),
567- b'agent-awareness' : (OptionHandlingType .verify , b'2.1.0' )
578+ b'allow-pinentry-notify' : (OptionHandlingType .fake , b'OK' ),
579+ b'agent-awareness' : (OptionHandlingType .verify , b'2.1.0' ),
568580 }
569581
570582 @staticmethod
@@ -656,7 +668,9 @@ def verify_keygrip_arguments(min_count: int, max_count: int,
656668 pass
657669 elif untrusted_args [6 ] == 61 : # ASCII '='
658670 # 1000 is the default value used by gpg2
659- sanitize_int (untrusted_args [7 :], 1 , 1000 )
671+ if untrusted_args != b'--list=%d' % sanitize_int (untrusted_args [7 :], 1 , 1000 ):
672+ assert False , "bug in filtering"
673+ raise Filtered
660674 else :
661675 raise Filtered
662676 else :
@@ -744,11 +758,13 @@ async def command_HAVEKEY(self, untrusted_args: Optional[bytes]) -> None:
744758 raise Filtered
745759 # upper keygrip limit is arbitary
746760 args = self .verify_keygrip_arguments (1 , 200 , untrusted_args , True )
747- await self .send_agent_command (b'HAVEKEY' , args )
761+ unrestricted = args .startswith (b'--list' ) and not self .allow_keygen
762+ await self .send_agent_command (b'HAVEKEY' , args , unrestricted )
748763
749764 async def command_KEYINFO (self , untrusted_args : Optional [bytes ]) -> None :
750765 args = self .verify_keygrip_arguments (1 , 1 , untrusted_args , True )
751- await self .send_agent_command (b'KEYINFO' , args )
766+ unrestricted = args .startswith (b'--list' ) and not self .allow_keygen
767+ await self .send_agent_command (b'KEYINFO' , args , unrestricted )
752768
753769 async def command_GENKEY (self , untrusted_args : Optional [bytes ]) -> None :
754770 if not self .allow_keygen :
@@ -823,7 +839,8 @@ async def setkeydesc(self, keygrip: bytes) -> None:
823839 key .fingerprint ,
824840 subkey_desc )
825841
826- self .agent_write (b'SETKEYDESC %s\n ' % self .percent_plus_escape (desc ))
842+ assert self .agent_writer is not None , "no writer?"
843+ self .agent_write (b'SETKEYDESC %s\n ' % self .percent_plus_escape (desc ), self .agent_writer )
827844
828845 assert self .agent_reader is not None
829846 untrusted_line = await self .agent_reader .readline ()
@@ -1020,43 +1037,56 @@ def get_inquires_for_command(self, command: bytes) -> Dict[bytes, 'ArgCallback']
10201037 }
10211038 return {}
10221039
1023- async def send_agent_command (self , command : bytes , args : Optional [bytes ]) -> None :
1040+ async def send_agent_command (self , command : bytes , args : Optional [bytes ],
1041+ unrestricted : bool = False ) -> None :
10241042 """ Sends command to local gpg agent and handle the response """
10251043 expected_inquires = self .get_inquires_for_command (command )
1026- if args :
1027- if not self .command_argument_regex .match (args ):
1028- raise AssertionError ("BUG: corrupt command about to be sent to agent!" )
1029- cmd_with_args = command + b' ' + args + b'\n '
1044+ assert self .agent_reader is not None , "no reader?"
1045+ assert self .agent_writer is not None , "no writer?"
1046+ if unrestricted and not self .allow_keygen :
1047+ reader , writer = await asyncio .open_unix_connection (
1048+ self .agent_unrestricted_socket_path )
1049+ line = await reader .readline ()
1050+ if not line .startswith (b'OK ' ):
1051+ raise Filtered # TODO: better handle error
10301052 else :
1031- cmd_with_args = command + b'\n '
1032- self .agent_write (cmd_with_args )
1033- while True :
1034- more_expected = await self .handle_agent_response (
1035- expected_inquires = expected_inquires )
1036- if not more_expected :
1037- break
1053+ reader , writer = self .agent_reader , self .agent_writer
1054+ try :
1055+ if args :
1056+ if not self .command_argument_regex .match (args ):
1057+ raise AssertionError ("BUG: corrupt command about to be sent to agent!" )
1058+ cmd_with_args = command + b' ' + args + b'\n '
1059+ else :
1060+ cmd_with_args = command + b'\n '
1061+ self .agent_write (cmd_with_args , writer )
1062+ while True :
1063+ more_expected = await self .handle_agent_response (expected_inquires , reader )
1064+ if not more_expected :
1065+ break
1066+ finally :
1067+ if reader is not self .agent_reader :
1068+ writer .close ()
10381069
1039- def agent_write (self , data : bytes ) -> None :
1040- writer = self .agent_writer
1070+ def agent_write (self , data : bytes , writer : asyncio .StreamWriter ) -> None :
10411071 assert writer is not None , 'agent_write called with no agent writer?'
10421072 self .log_io ('A <<<' , data )
10431073 writer .write (data )
10441074
10451075 async def handle_agent_response (self ,
1046- expected_inquires : Dict [bytes , 'ArgCallback' ]) -> bool :
1076+ expected_inquires : Dict [bytes , 'ArgCallback' ],
1077+ agent_reader : asyncio .StreamReader ) -> bool :
10471078 """ Receive and handle one agent response. Return whether there are
10481079 more expected """
1049- assert self .agent_reader is not None
10501080 assert self .client_writer is not None
10511081 if self .client_writer .is_closing ():
10521082 # If something went wrong, agent might send back junk.
10531083 # Discard all remaining data from agent and return.
1054- while await self . agent_reader .read (1024 ):
1084+ while await agent_reader .read (1024 ):
10551085 pass
10561086 return False
10571087 # We generally consider the agent as trusted. But since the client can
10581088 # determine part of the response we handle this here as untrusted.
1059- untrusted_line = await self . agent_reader .readline ()
1089+ untrusted_line = await agent_reader .readline ()
10601090 untrusted_line = untrusted_line .rstrip (b'\n ' )
10611091 self .log_io ('A >>>' , untrusted_line )
10621092 untrusted_res , untrusted_args = extract_args (untrusted_line )
@@ -1294,7 +1324,9 @@ async def inquire_command_D(self, validate_sexp: 'SExprValidator', *,
12941324 raise Filtered from e
12951325 args = untrusted_sexp
12961326
1297- self .agent_write (b'D ' + self .escape_D (self .serialize_sexpr (args )) + b'\n ' )
1327+ assert self .agent_writer is not None , "no writer?"
1328+ self .agent_write (b'D ' + self .escape_D (self .serialize_sexpr (args )) + b'\n ' ,
1329+ self .agent_writer )
12981330 self .seen_data = True
12991331 return True
13001332
@@ -1392,7 +1424,8 @@ def serialize_item(item: 'SExpr') -> bytes:
13921424 async def inquire_command_END (self , * , untrusted_args : bytes ) -> bool :
13931425 if untrusted_args :
13941426 raise Filtered ('unexpected arguments to END' )
1395- self .agent_write (b'END\n ' )
1427+ assert self .agent_writer is not None , "no writer?"
1428+ self .agent_write (b'END\n ' , self .agent_writer )
13961429 return False
13971430
13981431 # endregion
0 commit comments