def gpg_wait_for_string(self, fd, string): '''Look for a specific string on the status-fd.''' line = '' while line not in (string, ): debug('Waiting for line %s' % string) raw_line = fd.readline() if not raw_line: raise GpgUnknownError('gpg output unexpectedly ended') line = raw_line.strip() debug('got line %s' % line)
def gpg_wait_for_string(self, fd, string): """Look for a specific string on the status-fd.""" line = "" while line not in (string, ): PiusUtil.debug("Waiting for line %s" % string) raw_line = fd.readline() if not raw_line: raise GpgUnknownError("gpg output unexpectedly ended") line = raw_line.strip() PiusUtil.debug("got line %s" % line)
def _run_and_check_status(self, cmd): '''Helper function for running a gpg call that requires no input but that we want to make sure succeeded.''' logcmd(cmd) gpg = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=self.null, stderr=self.null, close_fds=True) retval = gpg.wait() if retval != 0: # We don't catch this, but that's fine, if this errors, a stack # trace is what we want raise GpgUnknownError("'%s' exited with %d" % (' '.join(cmd), retval))
def _run_and_check_status(self, cmd, shell=False): """Helper function for running a gpg call that requires no input but that we want to make sure succeeded.""" PiusUtil.logcmd(cmd) gpg = subprocess.Popen( cmd, stdin=subprocess.PIPE, stdout=self.null, stderr=self.null, shell=shell, close_fds=(not shell), ) retval = gpg.wait() if retval != 0: # We don't catch this, but that's fine, if this errors, a stack # trace is what we want raise GpgUnknownError( "'%s' exited with %d" % (" ".join(cmd) if isinstance(cmd, list) else cmd, retval))
def sign_uid(self, key, index, level): '''Sign a single UID of a key. This can use either cached passpharse or gpg-agent.''' agent = [] if self.mode == MODE_AGENT: agent = ['--use-agent'] keyring = ['--no-default-keyring', '--keyring', self.tmp_keyring] # Note that if passphrase-fd is different from command-fd, nothing works. cmd = [self.gpg] + self.gpg_base_opts + self.gpg_quiet_opts + \ self.gpg_fd_opts + keyring + [ '-u', self.force_signer, ] + agent + self.policy_opts() + [ '--default-cert-level', level, '--no-ask-cert-level', '--edit-key', key, ] # NB: keep the `--edit-key <key>` at the very end of this list! logcmd(cmd) gpg = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=self.null, close_fds=True) if self.mode == MODE_AGENT: # For some reason when using agent an initial enter is needed if not self.gpg2: gpg.stdin.write('\n') else: # For some unidentified reason you must send the passphrase # first, not when it asks for it. debug('Sending passphrase') gpg.stdin.write('%s\n' % self.passphrase) debug('Waiting for prompt') self.gpg_wait_for_string(gpg.stdout, PiusSigner.GPG_PROMPT) debug('Selecting UID %d' % index) gpg.stdin.write('%s\n' % str(index)) debug('Waiting for ack') self.gpg_wait_for_string(gpg.stdout, PiusSigner.GPG_ACK) debug('Running sign subcommand') self.gpg_wait_for_string(gpg.stdout, PiusSigner.GPG_PROMPT) debug('Sending sign command') gpg.stdin.write('sign\n') self.gpg_wait_for_string(gpg.stdout, PiusSigner.GPG_ACK) while True: debug('Waiting for response') line = gpg.stdout.readline() debug('Got %s' % line) if PiusSigner.GPG_ALREADY_SIGNED in line: print(' UID already signed') gpg.stdin.write('quit\n') return False elif PiusSigner.GPG_KEY_CONSIDERED in line: debug('Got KEY_CONSIDERED') continue elif (PiusSigner.GPG_KEY_EXP in line or PiusSigner.GPG_SIG_EXP in line): # The user has an expired signing or encryption key, keep going debug('Got GPG_KEY/SIG_EXP') continue elif PiusSigner.GPG_PROMPT in line: # Unfortunately PGP doesn't give us anything parsable in this case. It # just gives us another prompt. We give the most likely problem. Best we # can do. print( ' ERROR: GnuPG won\'t let us sign, this probably means it' ' can\'t find a secret key, which most likely means that the' ' keyring you are using doesn\'t have _your_ _public_ key on' ' it.') gpg.stdin.write('quit\n') raise NoSelfKeyError elif PiusSigner.GPG_CONFIRM in line: # This is what we want break else: print(' ERROR: GnuPG reported an unknown error') gpg.stdin.write('quit\n') # Don't raise an exception, it's not probably just this UID... return False debug('Confirming signing') gpg.stdin.write('Y\n') self.gpg_wait_for_string(gpg.stdout, PiusSigner.GPG_ACK) # # gpg-agent doesn't always work as well as we like. Of the problems: # * It can't always pop up an X window reliably (pinentry problems) # * It doesn't seem able to figure out the best pinetry program # to use in many situations # * Sometimes it silently fails in odd ways # # So this chunk of code will follow gpg through as many tries as gpg-agent # is willing to give and then inform the user of an error and raise an # exception. # # Since we're here, we also handle the highly unlikely case where the # verified cached passphrase doesn't work. # while True: line = gpg.stdout.readline() debug('Got %s' % line) # gpg1 + gpgagent1 reported BAD_PASSPHRASE for both the agent the wrong # passphrase, and for canceling the prompt. # # gpg2.0 + gpgagent2.0 seems to do MISSING_PASSPHRASE and BAD_PASSPHRASE # for the respective answers # # gpg2.1 + gpgagent2.1 seems to just do ERROR if 'ERROR' in line: print(' ERROR: Agent reported an error.') raise AgentError if 'MISSING_PASSPHRASE' in line: print(' ERROR: Agent didn\'t provide passphrase to PGP.') raise AgentError if 'BAD_PASSPHRASE' in line: if self.mode == MODE_AGENT: line = gpg.stdout.readline() debug('Got %s' % line) if 'USERID_HINT' in line: continue print( ' ERROR: Agent reported the passphrase was incorrect.' ) raise AgentError else: print(' ERROR: GPG didn\'t accept the passphrase.') raise PassphraseError if 'GOOD_PASSPHRASE' in line: break if PiusSigner.GPG_PROMPT in line: if self.gpg2: break print(' ERROR: GPG didn\'t sign.') raise GpgUnknownError(line) debug('Saving key') if not self.gpg2: self.gpg_wait_for_string(gpg.stdout, PiusSigner.GPG_PROMPT) gpg.stdin.write('save\n') gpg.wait() return True