openpgp-rollover / src /

#!/usr/bin/env python
'''A very simple module containing definitions of GPG's --with-colons format'''

from collections import namedtuple
import logging

log = logging.getLogger(__name__)

fields_explanation = [
{'type': '''1. Field:  Type of record
            pub = public key
            crt = X.509 certificate
            crs = X.509 certificate and private key available
            sub = subkey (secondary key)
            sec = secret key
            ssb = secret subkey (secondary key)
            uid = user id (only field 10 is used).
            uat = user attribute (same as user id except for field 10).
            sig = signature
            rev = revocation signature
            fpr = fingerprint: (fingerprint is in field 10)
            pkd = public key data (special field format, see below)
            grp = reserved for gpgsm
            rvk = revocation key
            tru = trust database information
            spk = signature subpacket'''},
{'trust': '''2. Field:  A letter describing the calculated trust. This is a single
            letter, but be prepared that additional information may follow
            in some future versions. (not used for secret keys)
                o = Unknown (this key is new to the system)
                i = The key is invalid (e.g. due to a missing self-signature)
                d = The key has been disabled
                    (deprecated - use the 'D' in field 12 instead)
                r = The key has been revoked
                e = The key has expired
                - = Unknown trust (i.e. no value assigned)
                q = Undefined trust
                    '-' and 'q' may safely be treated as the same
                    value for most purposes
                n = Don't trust this key at all
                m = There is marginal trust in this key
                f = The key is fully trusted
                u = The key is ultimately trusted.  This often means
                    that the secret key is available, but any key may
                    be marked as ultimately trusted.'''},
{'keylen': '3. Field:  length of key in bits.'},

{'algorithm': ''' 4. Field:  Algorithm:  1 = RSA
                       16 = Elgamal (encrypt only)
                       17 = DSA (sometimes called DH, sign only)
                       20 = Elgamal (sign and encrypt - don't use them!)                       
            (for other id's see include/cipher.h)'''},
{'keyid': '5. Field:  KeyID'},

{'creation': '''6. Field:  Creation Date (in UTC).  For UID and UAT records, this is the
            self-signature date.  Note that the dae is usally printed
            in seconds since epoch, however, we are migrating to an ISO
            8601 format (e.g. "19660205T091500").  This is currently
            only relevant for X.509, A simple way to detect the format
            is be scannning for the 'T'.'''},
{'expiry': '''7. Field:  Key or user ID/user attribute expiration date or empty if none.'''},

{'serial': '''8. Field:  Used for serial number in crt records (used to be the Local-ID).
            For UID and UAT records, this is a hash of the user ID contents
            used to represent that exact user ID.  For trust signatures,
            this is the trust depth seperated by the trust value by a

{'ownertrust': '''9. Field:  Ownertrust (primary public keys only)
            This is a single letter, but be prepared that additional
            information may follow in some future versions.  For trust
            signatures with a regular expression, this is the regular
            expression value, quoted as in field 10.'''},

{'userid': '''10. Field:  User-ID.  The value is quoted like a C string to avoid
            control characters (the colon is quoted "\x3a").
            This is not used with --fixed-list-mode in gpg.
            A UAT record puts the attribute subpacket count here, a
            space, and then the total attribute subpacket size.
            In gpgsm the issuer name comes here
            An FPR record stores the fingerprint here.
            The fingerprint of an revocation key is stored here.'''},

{'sig_class': '''11. Field:  Signature class.  This is a 2 digit hexnumber followed by
            either the letter 'x' for an exportable signature or the
            letter 'l' for a local-only signature.
            The class byte of an revocation key is also given here,
            'x' and 'l' ist used the same way.'''},

{'key_caps': '''12. Field:  Key capabilities:
                e = encrypt
                s = sign
                c = certify
                a = authentication
            A key may have any combination of them in any order.  In
            addition to these letters, the primary key has uppercase
            versions of the letters to denote the _usable_
            capabilities of the entire key, and a potential letter 'D'
            to indicate a disabled key.'''},

{'sig_issuer': '''13. Field:  Used in FPR records for S/MIME keys to store the fingerprint of
            the issuer certificate.  This is useful to build the
            certificate path based on certificates stored in the local
            keyDB; it is only filled if the issue certificate is
            available. The advantage of using this value is that it is
            guaranteed to have been been build by the same lookup
            algorithm as gpgsm uses.
            For "uid" recods this lists the preferences n the sameway the 
            -edit menu does.
            For "sig" records, this is the fingerprint of the key that
            issued the signature.  Note that this is only filled in if
            the signature verified correctly.  Note also that for
            various technical reasons, this fingerprint is only
            available if --no-sig-cache is used.'''},

{'edit': '14. Field   Flag field used in the --edit menu output:'},

{'token': '''15. Field   Used in sec/sbb to print the serial number of a token
            (internal protect mode 1002) or a '#' if that key is a
            simple stub (internal protect mode 1001)'''},

types_of_record = (
            'pub', #public key
            'crt', #X.509 certificate
            'crs', #X.509 certificate and private key available
            'sub', #subkey (secondary key)
            'sec', #secret key
            'ssb', #secret subkey (secondary key)
            'uid', #user id (only field 10 is used).
            'uat', #user attribute (same as user id except for field 10).
            'sig', #signature
            'rev', #revocation signature
            'fpr', #fingerprint: (fingerprint is in field 10)
            'pkd', #public key data (special field format, see below)
            'grp', #reserved for gpgsm
            'rvk', #revocation key
            'tru', #trust database information
            'spk', #signature subpacket

fields = [k.keys()[0] for k in fields_explanation]

_GPGRecordLine = namedtuple('GPGRecordLine', fields)
'''This class is meant to hold gpg records that gpg --with-colons 
produces, i.e.

sig:::1:965089CE6B95F882:2012-04-05::::Carlos Alberto Lopez Perez <>:10x:

however, the format documented in doc/DETAILS lists 16 fields whereas 
the signature records seem to have 13 fields only. It is unclear for 
now which fields are missing.

class GPGRecordLine(_GPGRecordLine):
    '''This is a tuple but can handle fewer args than needed by automatically filling up the missing values.
    That's done quite hackily but it seems to work. Improvements are welcome.
    def __new__(_cls, *args, **kwargs):
        field_len = len(_GPGRecordLine._fields)
        args_len = len(args)
        if  args_len < field_len:
            needed_elements = field_len - args_len
            additional_args = ['' for e in xrange(needed_elements)]
            normalised_args = list(args) + additional_args
            normalised_args = args

        return _GPGRecordLine(*normalised_args, **kwargs)
    def many_from_colon_lines(cls, lines, filter=types_of_record):
        '''Generates a new RecordLine from a given gpg --with-colons output.
        You filter for some packet types by specifying the filter argument.
        By default, it holds the list of all possible packet type which is
        defined by "type_of_record".
        for line in lines.splitlines():
            fields = line.split(':')
            filtered_lines = filter
            if not fields[0] in filtered_lines:
                log.debug('Parsing %s which is not a %s',
                    fields, filtered_lines)
                record_line = GPGRecordLine(*fields)
                log.debug("Parsed into the following: %s", record_line)
                yield record_line