Example #1
0
File: signer.py Project: doyke/pius
 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)
     line = fd.readline().strip()
     debug('got line %s' % line)
Example #2
0
File: signer.py Project: doyke/pius
 def export_clean_key(self, key):
   '''Export clean key from the users' KeyID.'''
   debug('exporting %s' % key)
   # We have to export our own public key as well
   keys_to_export = [key, self.signer]
   path = self._tmpfile_path('%s.asc' % key)
   self._export_key(self.keyring, keys_to_export, path)
Example #3
0
 def export_clean_key(self, key):
   '''Export clean key from the users' KeyID.'''
   debug('exporting %s' % key)
   # We have to export our own public key as well
   keys_to_export = [key, self.signer]
   path = self._tmpfile_path('%s.asc' % key)
   self._export_key(self.keyring, keys_to_export, path)
Example #4
0
 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)
     line = fd.readline().strip()
     debug('got line %s' % line)
Example #5
0
 def export_clean_key(self, key):
     '''Export clean key from the users' KeyID.'''
     # Export our public key and the given public key
     for x in [self.signer, key]:
         debug('exporting %s' % x)
         path = self._tmpfile_path('%s.asc' % x)
         self._export_key(self.keyring, [x], path)
Example #6
0
 def import_clean_key(self, key):
     '''Import the clean key we exported in export_clean_key() to our temp
 keyring.'''
     # Import the export of our public key and the given public key
     for x in [self.signer, key]:
         debug('importing %s' % x)
         import_opts = ['import-minimal']
         if x == self.signer:
             import_opts.append('keep-ownertrust')
         path = self._tmpfile_path('%s.asc' % x)
         cmd = [self.gpg] + self.gpg_base_opts + self.gpg_quiet_opts + [
             '--no-default-keyring',
             '--keyring',
             self.tmp_keyring,
             '--import-options',
             ','.join(import_opts),
             '--import',
             path,
         ]
         try:
             self._run_and_check_status(cmd)
         except GpgUnknownError as e:
             if x == self.signer:
                 print("\n\nERROR: Didn't find the signing key on keyring")
                 sys.exit(1)
             raise e
Example #7
0
 def export_clean_key(self, key):
   '''Export clean key from the users' KeyID.'''
   # Export our public key and the given public key
   for x in [self.signer, key]:
      debug('exporting %s' % x)
      path = self._tmpfile_path('%s.asc' % x)
      self._export_key(self.keyring, [x], path)
Example #8
0
    def verify_passphrase(self):
        '''Verify a passpharse gotten from get_passpharse().'''
        magic_string = 'test1234'
        filename = self._tmpfile_path('pius_tmp')
        filename_enc = self._tmpfile_path('pius_tmp.gpg')
        filename_dec = self._tmpfile_path('pius_tmp2')
        clean_files([filename, filename_enc, filename_dec])
        tfile = open(filename, 'w')
        tfile.write(magic_string)
        tfile.close()
        cmd = [self.gpg] + self.gpg_base_opts + self.gpg_quiet_opts + [
            '--no-armor',
            '--always-trust',
            '-r',
            self.signer,
            '-e',
            filename,
        ]
        logcmd(cmd)
        subprocess.call(cmd,
                        stdout=self.null,
                        stderr=self.null,
                        close_fds=True)
        cmd = [self.gpg] + self.gpg_base_opts + self.gpg_quiet_opts + \
          self.gpg_fd_opts + [
              '--output', filename_dec,
              '-d', filename_enc,
          ]
        logcmd(cmd)
        gpg = subprocess.Popen(cmd,
                               stdin=subprocess.PIPE,
                               stdout=subprocess.PIPE,
                               stderr=self.null,
                               close_fds=True)

        debug('Sending passphrase')
        gpg.stdin.write('%s\n' % self.passphrase)

        line = gpg.stdout.read()
        debug('wait()ing on gpg')
        retval = gpg.wait()
        if retval != 0:
            debug('gpg decrypt return code %s' % retval)
            clean_files([filename, filename_enc, filename_dec])
            return False

        if not os.path.exists(filename_dec):
            debug('Resulting file %s not found' % filename_dec)
            clean_files([filename, filename_enc, filename_dec])
            return False
        tfile = open(filename_dec, 'r')
        line = tfile.readline()
        tfile.close()
        clean_files([filename, filename_enc, filename_dec])
        if line == magic_string:
            return True
        debug('File does not contain magic string')
        return False
Example #9
0
 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)
Example #10
0
 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)
Example #11
0
File: signer.py Project: zorun/pius
  def verify_passphrase(self):
    '''Verify a passpharse gotten from get_passpharse().'''
    magic_string = 'test1234'
    filename = self._tmpfile_path('pius_tmp')
    filename_enc = self._tmpfile_path('pius_tmp.gpg')
    filename_dec = self._tmpfile_path('pius_tmp2')
    clean_files([filename, filename_enc, filename_dec])
    tfile = open(filename, 'w')
    tfile.write(magic_string)
    tfile.close()
    cmd = [self.gpg] + self.gpg_base_opts + self.gpg_quiet_opts + [
        '--no-armor',
        '--always-trust',
        '-r', self.signer,
        '-e', filename,
    ]
    logcmd(cmd)
    subprocess.call(cmd, stdout=self.null, stderr=self.null, close_fds=True)
    cmd = [self.gpg] + self.gpg_base_opts + self.gpg_quiet_opts + \
      self.gpg_fd_opts + [
          '--output', filename_dec,
          '-d', filename_enc,
      ]
    logcmd(cmd)
    gpg = subprocess.Popen(cmd, stdin=subprocess.PIPE,
                           stdout=subprocess.PIPE,
                           stderr=self.null,
                           close_fds=True)

    debug('Sending passphrase')
    gpg.stdin.write('%s\n' % self.passphrase)

    line = gpg.stdout.read()
    debug('wait()ing on gpg')
    retval = gpg.wait()
    if retval != 0:
      debug('gpg decrypt return code %s' % retval)
      clean_files([filename, filename_enc, filename_dec])
      return False

    if not os.path.exists(filename_dec):
      debug('Resulting file %s not found' % filename_dec)
      clean_files([filename, filename_enc, filename_dec])
      return False
    tfile = open(filename_dec, 'r')
    line = tfile.readline()
    tfile.close()
    clean_files([filename, filename_enc, filename_dec])
    if line == magic_string:
      return True
    debug('File does not contain magic string')
    return False
Example #12
0
 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.'''
   debug(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
Example #13
0
    def _send_mail(self, to, msg):
        '''Given a to and Message object, send email.'''
        # We don't duplicate the header logic in the sub functions, we
        # do that here
        debug("send_mail called with to (%s), subject (%s)" %
              (to, msg['subject']))
        if self.display_name:
            msg['From'] = self.display_name + ' <' + self.mail + '>'
        else:
            msg['From'] = self.mail
        if self.address_override:
            msg['To'] = self.address_override
        else:
            msg['To'] = to
        msg['Date'] = formatdate(localtime=True)

        try:
            smtp = smtplib.SMTP(self.host, self.port)
            if self.tls:
                # NOTE WELL: SECURITY IMPORTANT NOTE!
                # In python 2.6 if you attempt to starttls() and the server doesn't
                # understand an exception is raised. However before that, it just
                # carried on and one could attempt to auth over a plain-text session.
                # This is BAD!
                #
                # So, in order be secure on older pythons we ehlo() and then check the
                # response before attempting startls.
                smtp.ehlo()
                if not smtp.has_extn('STARTTLS'):
                    # Emulate 2.6 behavior
                    raise smtplib.SMTPException(
                        'Server does not support STARTTLS')
                smtp.starttls()
                # must re-ehlo after STARTTLS
                smtp.ehlo()
                # Don't want to send auth information unless we're TLS'd
                if self.user:
                    smtp.login(self.user, self.password)
            if self.address_override:
                env_to = self.address_override
            else:
                # BCC the user...
                env_to = [msg['To'], self.mail]

            smtp.sendmail(self.mail, env_to, msg.as_string())
            smtp.quit()
        except smtplib.SMTPException as emsg:
            raise MailSendError(emsg)
        except socket.error as emsg:
            raise MailSendError(emsg)
Example #14
0
    def sign_uid_expect(self, key, index, level):
        '''Sign a UID, using the expect stuff. Interactive mode.'''
        cmd = [self.gpg] + self.gpg_base_opts + [
            '--no-default-keyring',
            '--keyring',
            self.tmp_keyring,
            '--default-cert-level',
            level,
            '--no-ask-cert-level',
            '--no-use-agent',
            '--edit-key',
            key,
        ] + self.policy_opts()
        logcmd(cmd)
        gpg = pexpect.spawn(' '.join((quote(arg) for arg in cmd)))
        gpg.setecho(False)
        gpg.expect('gpg> ')
        debug('Selecting UID %s' % index)
        gpg.sendline(str(index))
        gpg.expect('gpg> ')
        debug('Running sign subcommand')
        gpg.sendline('sign')
        line = gpg.readline()
        if 'already signed' in line:
            print('  UID already signed')
            return False
        # else it's a blank line...

        gpg.expect(re.compile('Really sign.*'))
        debug('Confirming signing')
        gpg.sendline('y')
        # Tell the user how to get out of this, and then drop them into the gpg
        # shell.
        print('\n\nPassing you to gpg for passphrase.')
        print('Hit ^] after succesfully typing in your passphrase')
        gpg.interact()
        # When we return, we have a Command> prompt that w can't
        # 'expect'... or at least if the user did it right
        print('')
        # Unselect this UID
        debug('unselecting uid')
        debug('Saving key')
        gpg.sendline('save')
        return True
Example #15
0
File: mailer.py Project: zorun/pius
    def _send_mail(self, to, msg):
        """Given a to and Message object, send email."""
        # We don't duplicate the header logic in the sub functions, we
        # do that here
        debug("send_mail called with to (%s), subject (%s)" % (to, msg["subject"]))
        msg["From"] = self.mail
        if self.address_override:
            msg["To"] = self.address_override
        else:
            msg["To"] = to
        msg["Date"] = formatdate(localtime=True)

        try:
            smtp = smtplib.SMTP(self.host, self.port)
            if self.tls:
                # NOTE WELL: SECURITY IMPORTANT NOTE!
                # In python 2.6 if you attempt to starttls() and the server doesn't
                # understand an exception is raised. However before that, it just
                # carried on # and one could attempt to auth over a plain-text session.
                # This is BAD!
                #
                # So, in order be secure on older pythons we ehlo() and then check the
                # response before attempting startls.
                smtp.ehlo()
                if not smtp.has_extn("STARTTLS"):
                    # Emulate 2.6 behavior
                    raise smtplib.SMTPException("Server does not support STARTTLS")
                smtp.starttls()
                # must re-ehlo after STARTTLS
                smtp.ehlo()
                # Don't want to send auth information unless we're TLS'd
                if self.user:
                    smtp.login(self.user, self.password)
            if self.address_override:
                env_to = self.address_override
            else:
                # BCC the user...
                env_to = [msg["To"], self.mail]

            smtp.sendmail(self.mail, env_to, msg.as_string())
            smtp.quit()
        except smtplib.SMTPException, emsg:
            raise MailSendError(emsg)
Example #16
0
  def check_fingerprint(self, key):
    '''Prompt the user to see if they have verified this fingerprint.'''
    cmd = [self.gpg] + self.gpg_base_opts + self.gpg_quiet_opts + [
      '--no-default-keyring',
      '--keyring', self.keyring,
      '--fingerprint', key,
    ]
    debug(cmd)
    gpg = subprocess.Popen(cmd,
                           stdin=self.null,
                           stdout=subprocess.PIPE,
                           stderr=self.null,
                           close_fds=True)
    output = gpg.stdout.read()
    output = output.strip()
    retval = gpg.wait()
    if retval != 0:
      print 'WARNING: Keyid %s not valid, skipping.' % key
      return False

    print output

    while True:
      ans = raw_input("\nHave you verified this user/key, and if so, what level"
                      " do you want to sign at?\n  0-3, Show again, Next, Help,"
                      " or Quit? [0|1|2|3|s|n|h|q] (default: n) ")
      print

      if ans == 'y':
        print ('"Yes" is no longer a valid answer, please specify a level to'
               ' sign at.')
      elif ans in ('n', 'N', ''):
        return False
      elif ans in ('s', 'S'):
        print output
      elif ans in ('0', '1', '2', '3'):
        return ans
      elif ans in ('?', 'h', 'H'):
        self._print_cert_levels()
      elif ans in ('q', 'Q'):
        print 'Dying at user request'
        sys.exit(1)
Example #17
0
File: signer.py Project: zorun/pius
  def get_all_keyids(self):
    '''Given a keyring, get all the KeyIDs from it.'''
    debug('extracting all keyids from keyring')
    cmd = [self.gpg] + self.gpg_base_opts + [
        '--no-default-keyring',
        '--keyring', self.keyring,
        '--no-options',
        '--with-colons',
        '--keyid-format', 'long',
        '--fingerprint',
        '--fixed-list-mode',
    ]
    logcmd(cmd)
    gpg = subprocess.Popen(
        cmd,
        stdin=self.null,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT
    )
    # We use 'pub' instead of 'fpr' to support old crufty keys too...
    pub_re = re.compile('^pub:')
    uid_re = re.compile('^uid:')
    key_tuples = []
    name = keyid = None
    for line in gpg.stdout:
      if pub_re.match(line):
        lineparts = line.split(':')
        keyid = lineparts[4]
      elif keyid and uid_re.match(line):
        lineparts = line.split(':')
        name = lineparts[9]
        debug('Got id %s for %s' % (keyid, name))
        key_tuples.append((name, keyid))
        name = keyid = None

    # sort the list
    if self.sort_keyring:
      keyids = [i[1] for i in sorted(key_tuples)]
    else:
      keyids = [i[1] for i in key_tuples]
    return keyids
Example #18
0
    def get_all_keyids(self):
        '''Given a keyring, get all the KeyIDs from it.'''
        debug('extracting all keyids from keyring')
        cmd = [self.gpg] + self.gpg_base_opts + [
            '--no-default-keyring',
            '--keyring',
            self.keyring,
            '--no-options',
            '--with-colons',
            '--keyid-format',
            'long',
            '--fingerprint',
            '--fixed-list-mode',
        ]
        logcmd(cmd)
        gpg = subprocess.Popen(cmd,
                               stdin=self.null,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.STDOUT)
        # We use 'pub' instead of 'fpr' to support old crufty keys too...
        pub_re = re.compile('^pub:')
        uid_re = re.compile('^uid:')
        key_tuples = []
        name = keyid = None
        for line in gpg.stdout:
            if pub_re.match(line):
                lineparts = line.split(':')
                keyid = lineparts[4]
            elif keyid and uid_re.match(line):
                lineparts = line.split(':')
                name = lineparts[9]
                debug('Got id %s for %s' % (keyid, name))
                key_tuples.append((name, keyid))
                name = keyid = None

        # sort the list
        if self.sort_keyring:
            keyids = [i[1] for i in sorted(key_tuples)]
        else:
            keyids = [i[1] for i in key_tuples]
        return keyids
Example #19
0
File: signer.py Project: zorun/pius
  def sign_uid_expect(self, key, index, level):
    '''Sign a UID, using the expect stuff. Interactive mode.'''
    cmd = [self.gpg] + self.gpg_base_opts + [
        '--no-default-keyring',
        '--keyring', self.tmp_keyring,
        '--default-cert-level', level,
        '--no-ask-cert-level',
        '--no-use-agent',
        '--edit-key', key,
    ] + self.policy_opts()
    logcmd(cmd)
    gpg = pexpect.spawn(' '.join((quote(arg) for arg in cmd)))
    gpg.setecho(False)
    gpg.expect('gpg> ')
    debug('Selecting UID %s' % index)
    gpg.sendline(str(index))
    gpg.expect('gpg> ')
    debug('Running sign subcommand')
    gpg.sendline('sign')
    line = gpg.readline()
    if 'already signed' in line:
      print '  UID already signed'
      return False
    # else it's a blank line...

    gpg.expect(re.compile('Really sign.*'))
    debug('Confirming signing')
    gpg.sendline('y')
    # Tell the user how to get out of this, and then drop them into the gpg
    # shell.
    print '\n\nPassing you to gpg for passphrase.'
    print 'Hit ^] after succesfully typing in your passphrase'
    gpg.interact()
    # When we return, we have a Command> prompt that w can't
    # 'expect'... or at least if the user did it right
    print ''
    # Unselect this UID
    debug('unselecting uid')
    debug('Saving key')
    gpg.sendline('save')
    return True
Example #20
0
 def load_signed_keys(self):
     if not os.path.exists(self.kPIUS_SIGNED_KEYS):
         return dict()
     with open(self.kPIUS_SIGNED_KEYS, 'r') as fp:
         data = fp.read()
         # We have had multiple versions of the state file.
         #
         # v1 was just one key per line, each key was "signed"
         #
         # v2 was a hash taking the form:
         #   key: (SIGNED | WILL_NOTSIGN | NOT_SIGNED)
         #
         # v3 was the first one with a version identifier in it. It includes
         # a `meta` entry for the version and any other future metadata.
         # The data itself takes the format of:
         #   key: {
         #     'OUTBOUND': (SIGNED | WILL_NOTSIGN | NOT_SIGNED | None),
         #     'OUTBOUND': (None | Ignore),
         #   }
         try:
             signstate = json.loads(data)
             if '_meta' in signstate and signstate['_meta']['version'] == 3:
                 util.debug('Loading v3 PIUS statefile')
                 del (signstate['_meta'])
             elif '_meta' not in signstate:
                 util.debug('Loading v2 PIUS statefile')
                 signstate = self.convert_from_v2(signstate)
         except ValueError:
             util.debug('Loading v1 PIUS statefile')
             signstate = self.convert_from_v1(data)
     return signstate
Example #21
0
File: state.py Project: jaymzh/pius
 def load_signed_keys(self):
   if not os.path.exists(self.kPIUS_SIGNED_KEYS):
     return dict()
   with open(self.kPIUS_SIGNED_KEYS, 'r') as fp:
     data = fp.read()
     # We have had multiple versions of the state file.
     #
     # v1 was just one key per line, each key was "signed"
     #
     # v2 was a hash taking the form:
     #   key: (SIGNED | WILL_NOTSIGN | NOT_SIGNED)
     #
     # v3 was the first one with a version identifier in it. It includes
     # a `meta` entry for the version and any other future metadata.
     # The data itself takes the format of:
     #   key: {
     #     'OUTBOUND': (SIGNED | WILL_NOTSIGN | NOT_SIGNED | None),
     #     'OUTBOUND': (None | Ignore),
     #   }
     try:
       signstate = json.loads(data)
       if '_meta' in signstate and signstate['_meta']['version'] == 3:
         util.debug('Loading v3 PIUS statefile')
         del(signstate['_meta'])
       elif '_meta' not in signstate:
         util.debug('Loading v2 PIUS statefile')
         signstate = self.convert_from_v2(signstate)
     except ValueError:
       util.debug('Loading v1 PIUS statefile')
       signstate = self.convert_from_v1(data)
   return signstate
Example #22
0
 def import_clean_key(self, key):
   '''Import the clean key we exported in export_clean_key() to our temp
   keyring.'''
   # Import the export of our public key and the given public key
   for x in [self.signer, key]:
     debug('importing %s' % x)
     import_opts = ['import-minimal']
     if x == self.signer:
       import_opts.append('keep-ownertrust')
     path = self._tmpfile_path('%s.asc' %  x)
     cmd = [self.gpg] + self.gpg_base_opts + self.gpg_quiet_opts + [
         '--no-default-keyring',
         '--keyring', self.tmp_keyring,
         '--import-options', ','.join(import_opts),
         '--import', path,
     ]
     try:
       self._run_and_check_status(cmd)
     except GpgUnknownError as e:
       if x == self.signer:
           print("\n\nERROR: Didn't find the signing key on keyring")
           sys.exit(1)
       raise e
Example #23
0
    def encrypt_and_sign_file(self, infile, outfile, keyid):
        '''Encrypt and sign a file.

    Used for PGP/Mime email generation.'''
        agent = []
        if self.mode == MODE_AGENT:
            agent = ['--use-agent']
        cmd = [self.gpg] + self.gpg_base_opts +  self.gpg_quiet_opts + \
          self.gpg_fd_opts + agent + [
              '--no-default-keyring',
              '--keyring', self.tmp_keyring,
              '--no-options',
              '--always-trust',
              '-u', self.force_signer,
              '-aes',
              '-r', keyid,
              '-r', self.signer,
              '--output', outfile,
              infile,
          ]
        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
            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)

        while True:
            debug('Waiting for response')
            line = gpg.stdout.readline().strip()
            debug('Got %s' % line)
            if PiusSigner.GPG_ENC_BEG in line:
                debug('Got GPG_ENC_BEG')
                continue
            elif PiusSigner.GPG_ENC_END in line:
                debug('Got GPG_ENC_END')
                break
            elif PiusSigner.GPG_ENC_INV in line:
                debug('Got GPG_ENC_INV')
                raise EncryptionKeyError
            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):
                # These just mean we passed a given key/sig that's expired, there
                # may be ones left that are good. We cannot report an error until
                # we get a ENC_INV.
                debug('Got GPG_KEY/SIG_EXP')
                continue
            elif (PiusSigner.GPG_USERID in line
                  or PiusSigner.GPG_NEED_PASS in line
                  or PiusSigner.GPG_GOOD_PASS in line
                  or PiusSigner.GPG_SIG_BEG in line
                  or PiusSigner.GPG_SIG_CREATED in line
                  or PiusSigner.GPG_PROGRESS in line
                  or PiusSigner.GPG_PINENTRY_LAUNCHED in line
                  or PiusSigner.GPG_WARN_VERSION):
                debug('Got skippable stuff')
                continue
            else:
                raise EncryptionUnknownError(line)

        retval = gpg.wait()
        if retval != 0:
            raise EncryptionUnknownError("Return code was %s" % retval)
Example #24
0
    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
Example #25
0
  def get_uids(self, key):
    '''Get all UIDs on a given key.'''
    cmd = [self.gpg] + self.gpg_base_opts +  self.gpg_quiet_opts + \
      self.gpg_fd_opts + [
        '--no-default-keyring',
        '--keyring', self.keyring,
        '--no-options',
        '--with-colons',
        '--edit-key', key,
      ]
    debug(cmd)
    gpg = subprocess.Popen(cmd, stdin=subprocess.PIPE,
                           stdout=subprocess.PIPE,
                           stderr=self.null,
                           close_fds=True)

    gpg.stdin.write('\n')
    uids = []
    unique_files = []
    while True:
      line = gpg.stdout.readline().strip()

      # skip the things we don't care about...
      if not line:
        debug('breaking, EOF')
        break
      if line == PiusSigner.GPG_PROMPT:
        debug('got to command prompt')
        break

      # Parse the line...
      debug('Got a line %s' % line)
      fields = line.split(':')

      if fields[0] != 'uid':
        continue

      status = fields[1]
      uid = fields[9]
      index = int(fields[13].split(',')[0])

      debug('Got UID %s with status %s' % (uid, status))

      # If we can we capture an email address is saved for 
      # emailing off signed keys (not yet implemented), and
      # also for the ID for that UID.
      #
      # If we can't, then we grab what we can and make it the
      # id and blank out the email.
      #
      # For the normal case (have email), we'll be storing each email twice
      # but that's OK since it means that email is *always* a valid email or
      # None and id is *always* a valid identifier
      match = re.search('.* <(.*)>', uid)
      if match:
        email = match.group(1)
        debug('got email %s' % email)
        filename = re.sub('@', '_at_', email)
        filename = '%s__%s' % (key, filename)
        uid = email
      else:
        # but if it doesn't have an email, do the right thing
        email = None
        debug('no email')
        uid = re.sub(' ', '_', uid)
        uid = re.sub('\'', '', uid)
        filename = '%s__%s' % (key, uid)
      # Append the UID we're signing with in case people sign with 2
      # keys in succession:
      filename = '%s__%s' % (filename, self.signer)

      if filename in unique_files:
        debug('Filename is a duplicate')
        count = 2
        while True:
          test = '%s_%s' % (filename, count)
          debug('Trying %s' % test)
          if test not in unique_files:
            debug('%s worked!' % test)
            filename = test
            break
          else:
            count += 1
      else:
        debug('%s isn\'t in %s' % (filename, repr(unique_files)))

      # NOTE: Make sure to append the file BEFORE adding the extension
      #       since that's what we test against above!
      unique_files.append(filename)
      filename += '.asc'
      uids.append({'email': email, 'file': self._outfile_path(filename),
                   'status': status, 'id': uid, 'index': index})

    debug('quitting')
    # sometimes it wants a save here. I don't know why. We can quit and check
    # for a save prompt, and then hit no, but we have to make sure it's still
    # running or we'll hang. It's just easier to issue a 'save' instead of a
    # quit
    gpg.stdin.write('save\n')
    debug('waiting')
    gpg.wait()

    return uids
Example #26
0
 def export_signed_uid(self, key, filename):
   '''Export the signed UID form working keyring.'''
   debug('exporting %s' % key)
   self._export_key(self.tmp_keyring, [key], filename)
Example #27
0
  def encrypt_signed_uid(self, key, filename):
    '''Encrypt the file we exported the signed UID to.'''
    (base, ext) = os.path.splitext(os.path.basename(filename))
    enc_file = '%s_ENCRYPTED%s' % (base, ext)
    enc_path = self._outfile_path(enc_file)
    if os.path.exists(enc_path):
      os.unlink(enc_path)
    cmd = [self.gpg] + self.gpg_base_opts + self.gpg_quiet_opts + \
      self.gpg_fd_opts + [
        '--no-default-keyring',
        '--keyring', self.tmp_keyring,
        '--always-trust',
        '--armor',
        '-r', key,
        '--output', enc_path,
        '-e', filename,
      ]
    debug(cmd)
    gpg = subprocess.Popen(cmd, stdin=subprocess.PIPE,
                           stdout=subprocess.PIPE,
                           stderr=self.null,
                           close_fds=True)

    # Must send a blank line...
    gpg.stdin.write('\n')
    while True:
      debug('Waiting for response')
      line = gpg.stdout.readline().strip()
      debug('Got %s' % line)
      if PiusSigner.GPG_ENC_BEG in line:
        debug('Got GPG_ENC_BEG')
        continue
      elif PiusSigner.GPG_ENC_END in line:
        debug('Got GPG_ENC_END')
        break
      elif PiusSigner.GPG_ENC_INV in line:
        debug('Got GPG_ENC_INV')
        raise EncryptionKeyError
      elif (PiusSigner.GPG_KEY_EXP in line or
            PiusSigner.GPG_SIG_EXP in line):
        # These just mean we passed a given key/sig that's expired, there
        # may be ones left that are good. We cannot report an error until
        # we get a ENC_INV.
        debug('Got GPG_KEY_EXP')
        continue
      else:
        raise EncryptionUnknownError, line

    gpg.wait()
    return enc_file
Example #28
0
 def export_signed_uid(self, key, filename):
     '''Export the signed UID form working keyring.'''
     debug('exporting %s' % key)
     self._export_key(self.tmp_keyring, [key], filename)
Example #29
0
    def encrypt_signed_uid(self, key, filename):
        '''Encrypt the file we exported the signed UID to.'''
        (base, ext) = os.path.splitext(os.path.basename(filename))
        enc_file = '%s_ENCRYPTED%s' % (base, ext)
        enc_path = self._outfile_path(enc_file)
        if os.path.exists(enc_path):
            os.unlink(enc_path)
        cmd = [self.gpg] + self.gpg_base_opts + self.gpg_quiet_opts + \
          self.gpg_fd_opts + [
              '--no-default-keyring',
              '--keyring', self.tmp_keyring,
              '--always-trust',
              '--armor',
              '-r', key,
              '--output', enc_path,
              '-e', filename,
          ]
        logcmd(cmd)
        gpg = subprocess.Popen(cmd,
                               stdin=subprocess.PIPE,
                               stdout=subprocess.PIPE,
                               stderr=self.null,
                               close_fds=True)

        # Must send a blank line...
        gpg.stdin.write('\n')
        while True:
            debug('Waiting for response')
            line = gpg.stdout.readline().strip()
            debug('Got %s' % line)
            if PiusSigner.GPG_ENC_BEG in line:
                debug('Got GPG_ENC_BEG')
                continue
            elif PiusSigner.GPG_ENC_END in line:
                debug('Got GPG_ENC_END')
                break
            elif PiusSigner.GPG_ENC_INV in line:
                debug('Got GPG_ENC_INV')
                raise EncryptionKeyError
            elif (PiusSigner.GPG_KEY_EXP in line
                  or PiusSigner.GPG_SIG_EXP in line):
                # These just mean we passed a given key/sig that's expired, there
                # may be ones left that are good. We cannot report an error until
                # we get a ENC_INV.
                debug('Got GPG_KEY_EXP')
                continue
            elif PiusSigner.GPG_KEY_CONSIDERED in line:
                debug('Got KEY_CONSIDERED')
                continue
            elif PiusSigner.GPG_PROGRESS in line:
                debug('Got skippable stuff')
                continue
            else:
                raise EncryptionUnknownError(line)

        gpg.wait()
        return enc_file
Example #30
0
    def get_uids(self, key):
        '''Get all UIDs on a given key.'''
        cmd = [self.gpg] + self.gpg_base_opts +  self.gpg_quiet_opts + \
          self.gpg_fd_opts + [
              '--no-default-keyring',
              '--keyring', self.keyring,
              '--no-options',
              '--with-colons',
              '--edit-key', key,
          ]
        logcmd(cmd)
        gpg = subprocess.Popen(cmd,
                               stdin=subprocess.PIPE,
                               stdout=subprocess.PIPE,
                               stderr=self.null,
                               close_fds=True)

        gpg.stdin.write('\n')
        uids = []
        unique_files = []
        while True:
            line = gpg.stdout.readline().strip()

            # skip the things we don't care about...
            if not line:
                debug('breaking, EOF')
                break
            if line == PiusSigner.GPG_PROMPT:
                debug('got to command prompt')
                break

            # Parse the line...
            debug('Got a line %s' % line)
            fields = line.split(':')

            if fields[0] != 'uid':
                continue

            status = fields[1]
            uid = fields[9]
            index = int(fields[13].split(',')[0])

            debug('Got UID %s with status %s' % (uid, status))

            # If we can we capture an email address is saved for
            # emailing off signed keys (not yet implemented), and
            # also for the ID for that UID.
            #
            # If we can't, then we grab what we can and make it the
            # id and blank out the email.
            #
            # For the normal case (have email), we'll be storing each email twice
            # but that's OK since it means that email is *always* a valid email or
            # None and id is *always* a valid identifier
            match = re.search('.* <(.*)>', uid)
            if match:
                email = match.group(1)
                debug('got email %s' % email)
                filename = re.sub('@', '_at_', email)
                filename = '%s__%s' % (key, filename)
                uid = email
            else:
                # but if it doesn't have an email, do the right thing
                email = None
                debug('no email')
                uid = re.sub(' ', '_', uid)
                uid = re.sub('\'', '', uid)
                filename = '%s__%s' % (key, uid)
            # Append the UID we're signing with in case people sign with 2
            # keys in succession:
            filename = '%s__%s' % (filename, self.signer)

            if filename in unique_files:
                debug('Filename is a duplicate')
                count = 2
                while True:
                    test = '%s_%s' % (filename, count)
                    debug('Trying %s' % test)
                    if test not in unique_files:
                        debug('%s worked!' % test)
                        filename = test
                        break
                    else:
                        count += 1
            else:
                debug('%s isn\'t in %s' % (filename, repr(unique_files)))

            # NOTE: Make sure to append the file BEFORE adding the extension
            #       since that's what we test against above!
            unique_files.append(filename)
            filename += '.asc'
            uids.append({
                'email': email,
                'file': self._outfile_path(filename),
                'status': status,
                'id': uid,
                'index': index
            })

        debug('quitting')
        # sometimes it wants a save here. I don't know why. We can quit and check
        # for a save prompt, and then hit no, but we have to make sure it's still
        # running or we'll hang. It's just easier to issue a 'save' instead of a
        # quit
        gpg.stdin.write('save\n')
        debug('waiting')
        gpg.wait()

        return uids
Example #31
0
  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.signer,
      ] + agent + [
        '--default-cert-level', level,
        '--no-ask-cert-level',
        '--edit-key', key,
      ] + self.policy_opts()
    debug(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
      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_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)
      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 didn\'t provide passphrase to PGP.'
          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:
        print '  ERROR: GPG didn\'t sign.'
        raise GpgUnknownError(line)

    debug('Saving key')
    self.gpg_wait_for_string(gpg.stdout, PiusSigner.GPG_PROMPT)
    gpg.stdin.write('save\n')

    gpg.wait()
    return True
Example #32
0
  def encrypt_and_sign_file(self, infile, outfile, keyid):
    '''Encrypt and sign a file.

    Used for PGP/Mime email generation.'''
    agent = []
    if self.mode == MODE_AGENT:
      agent = ['--use-agent']
    cmd = [self.gpg] + self.gpg_base_opts +  self.gpg_quiet_opts + \
      self.gpg_fd_opts + agent + [
        '--no-default-keyring',
        '--keyring', self.tmp_keyring,
        '--no-options',
        '--always-trust',
        '-u', self.signer,
        '-aes',
        '-r', keyid,
        '-r', self.signer,
        '--output', outfile,
        infile,
      ]
    debug(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
      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)

    while True:
      debug('Waiting for response')
      line = gpg.stdout.readline().strip()
      debug('Got %s' % line)
      if PiusSigner.GPG_ENC_BEG in line:
        debug('Got GPG_ENC_BEG')
        continue
      elif PiusSigner.GPG_ENC_END in line:
        debug('Got GPG_ENC_END')
        break
      elif PiusSigner.GPG_ENC_INV in line:
        debug('Got GPG_ENC_INV')
        raise EncryptionKeyError
      elif (PiusSigner.GPG_KEY_EXP in line or
            PiusSigner.GPG_SIG_EXP in line):
        # These just mean we passed a given key/sig that's expired, there
        # may be ones left that are good. We cannot report an error until
        # we get a ENC_INV.
        debug('Got GPG_KEY/SIG_EXP')
        continue
      elif (PiusSigner.GPG_USERID in line or
            PiusSigner.GPG_NEED_PASS in line or
            PiusSigner.GPG_GOOD_PASS in line or
            PiusSigner.GPG_SIG_BEG in line or
            PiusSigner.GPG_SIG_CREATED in line):
        debug('Got skippable stuff')
        continue
      else:
        raise EncryptionUnknownError, line

    retval = gpg.wait()
    if retval != 0:
      raise EncryptionUnknownError, "Return code was %s" % retval