def from_subpacket_content(cls, type_, critical, sub_data): assert len(sub_data) == 22 tag = sub_data assert tag & 0x80 sensitive = bool(tag & 0x40) public_key_algorithm = int(sub_data[1]) fingerprint = utils.bytearray_to_hex(sub_data, 2, 20) return cls(critical, fingerprint, public_key_algorithm, sensitive=sensitive)
def from_subpacket_content(cls, type_, critical, sub_data): assert len(sub_data) > 2 public_key_algorithm = int(sub_data[0]) hash_algorithm = int(sub_data[1]) hash_length = utils.hash_lengths.get(hash_algorithm, None) if hash_length is None: hash_length = len(sub_data) - 2 else: assert hash_length == len(sub_data) - 2 hash_ = utils.bytearray_to_hex(sub_data, 2, len(sub_data) - 2) return cls(critical, public_key_algorithm, hash_algorithm, hash_, hash_length=hash_length)
def __init__(self, critical, fingerprint, public_key_algorithm, sensitive=False): if isinstance(fingerprint, (bytes, bytearray)): assert len(fingerprint) == 20 fingerprint = utils.bytearray_to_hex(fingerprint, 0, 20) else: assert len(fingerprint) == 40 SignatureSubpacket.__init__(self, constants.REVOCATION_KEY_SUBPACKET_TYPE, critical) self.fingerprint = fingerprint self.public_key_algorithm = public_key_algorithm self.sensitive = sensitive
def from_bytes(cls, symmetric_algorithm, data, offset=0): # GnuPG string-to-key # According to g10/parse-packet.c near line 1832, the 101 packet # type is a special GnuPG extension. This S2K extension is # 6 bytes in total: # # Octet 0: 101 # Octet 1: hash algorithm # Octet 2-4: "GNU" # Octet 5: mode integer # Octet 6-n: serial number serial_number = None serial_len = None hash_algorithm = data[offset] offset += 1 gnu = data[offset:offset + 3] offset += 3 if gnu != bytearray(b"GNU"): raise ValueError( "S2K parsing error: expected 'GNU', got %s" % gnu) mode = data[offset] mode += 1000 offset += 1 if mode == 1001: # GnuPG dummy pass elif mode == 1002: # OpenPGP card serial_len = data[offset] offset += 1 if serial_len < 0: raise ValueError( "Unexpected serial number length: %d" % serial_len) serial_number = utils.bytearray_to_hex(data, offset, serial_len) offset += serial_len else: raise ValueError( "Unsupported GnuPG S2K extension, encountered mode %d" % mode) return cls(hash_algorithm, mode, symmetric_algorithm, serial_number, serial_len), offset
def parse_signature_packet(p, parent_type, parent_key_ids=None, sig_hashed=False, # For testing parse_signature_subpacket=parse_signature_subpacket ): """Parse a single pgpdump signature packet into a Python dictionary of values. sig_hashed should be set to True for signature packets which are embedded as hashed signature subpackets. """ signature = {} signature['validated'] = None signature['hash_algorithm_type'] = p.raw_hash_algorithm signature['pub_algorithm_type'] = p.raw_pub_algorithm signature['sig_type'] = p.raw_sig_type signature['sig_version'] = p.sig_version signature['hash2'] = utils.bytearray_to_hex(p.hash2) # "If a subpacket is not hashed, then the information in it cannot be # considered definitive because it is not part of the signature proper." # # For convenience, include the fields that are always hashed. signature['hashed'] = [ 'hash_algorithm_type', 'pub_algorithm_type', 'sig_type', 'sig_version', ] if p.sig_version in (2, 3): # Only trust these explicitly if the version is < 4. If the version is # 4 or greater, these values may have come from unhashed subpackets # and could have been manipulated. signature['key_ids'] = [{ 'key_id': p.key_id.upper(), 'hashed': True, }] signature['selfsig'] = p.key_id.upper() in parent_key_ids signature['creation_time'] = p.raw_creation_time signature['hashed'].extend([ 'creation_time', ]) elif p.sig_version >= 4: hashed = {} # Parse Key IDs first so we know if it's a selfsignature or not for sub in p.subpackets: if sub.subtype == 16: parse_signature_subpacket(sub, signature, parent_type) sig_key_ids = set(map( lambda k: k['key_id'], signature.get('key_ids', []) )) signature['selfsig'] = bool(sig_key_ids & set(parent_key_ids)) for sub in p.subpackets: if sub.subtype == 16: continue parse_signature_subpacket(sub, signature, parent_type) if sub.subtype not in (6, 16, 20, 32): hashed[sub.subtype] = sig_hashed or sub.hashed for k, v in hashed.items(): if not k or not v: continue keys = subpacket_type_to_keys(k) signature['hashed'].extend(keys) else: raise UnsupportedSignatureVersion(p.sig_version) return signature
def parse_signature_subpacket(sub, signature, signature_owner_type, signature_hashed=False, # For testing validate_subpacket_regex=validate_subpacket_regex, parse_notation=parse_notation, parse_embedded_signature=parse_embedded_signature): if sub.subtype == 2: if not sub.hashed: # "MUST be present in the hashed area." # Specification does not say what behavior should be if it # is present in the unhashed area. GnuPG & SKS ignore the # signature subpacket. return signature['creation_time'] = utils.long_to_int(sub.data, 0) elif sub.subtype == 3: # Raw expiration time is actually the time from the creation time # that expiration occurs in seconds, rather than a unix timestamp # as its name might imply exp_time = utils.long_to_int(sub.data, 0) if exp_time != 0: signature['expiration_seconds'] = exp_time elif sub.subtype == 4: exportable = bool(sub.data[0]) if not exportable: # "Non-exportable, or "local", certifications are signatures # made by a user to mark a key as valid within that user's # implementation only." # # "[...]" # # "The receiver of a transported key "imports" it, and likewise # trims any local certifications. In normal operation, there # won't be any, assuming the import is performed on an # exported key. However, there are instances where this can # reasonably happen. For example, if an implementation allows # keys to be imported from a key database in addition to an # exported key, then this situation can arise." # # "Some implementations do not represent the interest of a # single user (for example, a key server). Such # implementations always trim local certifications from any # key they handle." raise LocalCertificationSignature signature['exportable'] = exportable elif sub.subtype == 5: signature['trust_level'] = int(sub.data[0]) signature['trust_amount'] = int(sub.data[1]) elif sub.subtype == 6: # Null-terminated regex = sub.data[:-1].decode('ascii', 'replace') try: validate_subpacket_regex(regex) except RegexValueError: return signature.setdefault('regexes', []) # The specifcation doesn't cover it, but it is reasonable that a # signature might specify multiple regular expressions. # # "In most cases, an implementation SHOULD use the last subpacket # in the signature, but MAY use any conflict resolution scheme # that makes more sense." signature['regexes'].append({ 'regex': regex, 'hashed': signature_hashed or sub.hashed }) elif sub.subtype == 7: signature['revocable'] = bool(sub.data[0]) elif sub.subtype == 9: # "This is found only on a self-signature." if not signature['selfsig']: return # Raw expiration time is actually the time from the creation time # that expiration occurs in seconds, rather than a unix timestamp # as its name might imply exp_time = utils.long_to_int(sub.data, 0) if exp_time == 0: return signature['key_expiration_seconds'] = exp_time elif sub.subtype == 11: # "This is found only on a self-signature." if not signature['selfsig']: return signature['preferred_sym_algorithms'] = list(map(int, sub.data)) elif sub.subtype == 12: if not bool(sub.data[0] & 0x80): # 0x80 MUST be set return # If this sensitive key is included when we receive it, it must # be for a reason. It would not be exported to be given to us # otherwise. # # "If this flag is set, implementations SHOULD NOT export this # signature to other users except in cases where the data needs # to be available: when the signature is being sent to the # designated revoker, or when it is accompanied by a revocation # signature from that revoker." signature['revocation_key_sensitive'] = bool(sub.data[0] & 0x40) signature['revocation_key_pub_algorithm_type'] = sub.data[1] signature['revocation_key'] = \ utils.bytearray_to_hex(sub.data[2:]) elif sub.subtype == 16: # "Some apparent conflicts may actually make sense -- for example, # suppose a keyholder has a V3 key and a V4 key that share the # same RSA key material. Either of these keys can verify a # signature created by the other, and it may be reasonable for a # signature to contain an issuer subpacket for each key, as a way # of explicitly tying those keys to the signature." signature.setdefault('key_ids', []) signature['key_ids'].append({ 'key_id': utils.bytearray_to_hex(sub.data, 0, 8).upper(), 'hashed': signature_hashed or sub.hashed }) elif sub.subtype == 20: notation = parse_notation(sub.data, signature_hashed or sub.hashed) if notation: if sub.critical: # "If there is a critical notation, the criticality # applies to that specific notation and not to notations # in general." raise CannotParseCriticalNotation(notation['name']) signature.setdefault('notations', []) signature['notations'].append(notation) elif sub.subtype == 21: # "This is found only on a self-signature." if not signature['selfsig']: return signature['preferred_hash_algorithms'] = list(map(int, sub.data)) elif sub.subtype == 22: # "This is found only on a self-signature." if not signature['selfsig']: return signature['preferred_compression_algorithms'] = \ list(map(int, sub.data)) elif sub.subtype == 23: if not signature['selfsig']: # "This is found only on a self-signature." return # "All undefined flags MUST be zero." if sub.data[0] & 0x7f or any(sub.data[1:]): return signature['key_server_no_modify'] = bool(sub.data[0] & 0x80) elif sub.subtype == 24: signature['preferred_key_server'] = \ sub.data.decode('utf8', 'replace') elif sub.subtype == 25: # "This is a flag in a User ID's self-signature" # ... # "there are two different and independent "primaries" -- one for # User IDs, and one for User Attributes." if not signature['selfsig']: return if signature_owner_type not in (13, 17): return # Store the actual integer value. If we have multiple "primaries" # we can use this to resolve priority. # # "If more than one User ID in a key is marked as primary, the # implementation may resolve the ambiguity in any way it sees # fit, but it is RECOMMENDED that priority be given to the User # ID with the most recent self-signature." signature['primary'] = int(sub.data[0]) elif sub.subtype == 26: signature['policy_uri'] = sub.data.decode('utf8', 'replace') elif sub.subtype == 27: flags = sub.data[0] signature['may_certify_others'] = bool(flags & 0x01) signature['may_sign_data'] = bool(flags & 0x02) signature['may_encrypt_comms'] = bool(flags & 0x04) signature['may_encrypt_storage'] = bool(flags & 0x08) signature['may_be_used_for_auth'] = bool(flags & 0x20) # "The "split key" (0x10) and "group key" (0x80) flags are placed # on a self-signature only; they are meaningless on a # certification signature. They SHOULD be placed only on a # direct-key signature (type 0x1F) or a subkey signature # (type 0x18), one that refers to the key the flag applies to." if signature['selfsig'] and signature['sig_type'] in (0x1f, 0x18): signature['may_have_been_split'] = bool(flags & 0x10) signature['may_have_multiple_owners'] = bool(flags & 0x80) elif sub.subtype == 28: signature['user_id'] = sub.data.decode('utf8', 'replace') elif sub.subtype == 29: # "This subpacket is used only in key revocation and certification # revocation signatures." if signature['sig_type'] not in (0x20, 0x28): return revocation_code = int(sub.data[0]) if (revocation_code in (0, 1, 2, 3, 32) or (revocation_code > 99 and revocation_code < 111)): signature['revocation_code'] = revocation_code signature['revocation_reason'] = \ sub.data[1:].decode('utf8', 'replace') elif sub.subtype == 30: # "This subpacket is similar to a preferences subpacket, and only # appears in a self-signature." if not signature['selfsig']: return if sub.data[0] & 0xfe: return if any(sub.data[1:]): return supports_modification = sub.data[0] & 0x01 signature['supports_modification_detection'] = supports_modification elif sub.subtype == 31: signature['target_pub_key_algorithm'] = sub.data[0] signature['target_hash_algorithm'] = sub.data[1] # Store the target hash as hex signature['target_hash'] = utils.bytearray_to_hex(sub.data[2:]) elif sub.subtype == 32: subsignature = parse_embedded_signature( sub.data, signature_hashed or sub.hashed) signature.setdefault('embedded_signatures', []) signature['embedded_signatures'].append(subsignature) # Unparseable signature subpackets # "If a subpacket is encountered that is marked critical but is # unknown to the evaluating software, the evaluator SHOULD consider # the signature to be in error." elif sub.subtype > 99 and sub.subtype < 111: # private / experimental if sub.critical: raise CannotParseCritical(sub.subtype) elif sub.subtype in (0, 1, 8, 13, 14, 15, 17, 18, 19): # "An implementation SHOULD ignore any subpacket of a type that it # does not recognize." # raise ReservedSignatureSubpacket(sub.subtype) if sub.critical: raise CannotParseCritical(sub.subtype) else: # "An implementation SHOULD ignore any subpacket of a type that it # does not recognize." # raise InvalidSignatureSubpacket(sub.subtype) if sub.critical: raise CannotParseCritical(sub.subtype)
def from_subpacket_content(cls, type_, critical, sub_data): assert len(sub_data) == 8 key_id = utils.bytearray_to_hex(sub_data, 0, 16) return cls(critical, key_id)
def content(self): tag = 0x80 + (0x40 if self.sensitive else 0x00) data = bytearray([tag, int(self.public_key_algorithm)] + utils.bytearray_to_hex(self.fingerprint, 20)) return data
def from_subpacket_content(cls, type_, critical, sub_data): strong_request = bool(sub_data[0] & 0x80) public_key_algorithm = sub_data[1] fingerprint = utils.bytearray_to_hex(sub_data, 2, 20) return cls(critical, strong_request, public_key_algorithm, fingerprint)
def parse_signature_subpacket( sub, signature, signature_owner_type, signature_hashed=False, # For testing validate_subpacket_regex=validate_subpacket_regex, parse_notation=parse_notation, parse_embedded_signature=parse_embedded_signature): if sub.subtype == 2: if not sub.hashed: # "MUST be present in the hashed area." # Specification does not say what behavior should be if it # is present in the unhashed area. GnuPG & SKS ignore the # signature subpacket. return signature['creation_time'] = utils.long_to_int(sub.data, 0) elif sub.subtype == 3: # Raw expiration time is actually the time from the creation time # that expiration occurs in seconds, rather than a unix timestamp # as its name might imply exp_time = utils.long_to_int(sub.data, 0) if exp_time != 0: signature['expiration_seconds'] = exp_time elif sub.subtype == 4: exportable = bool(sub.data[0]) if not exportable: # "Non-exportable, or "local", certifications are signatures # made by a user to mark a key as valid within that user's # implementation only." # # "[...]" # # "The receiver of a transported key "imports" it, and likewise # trims any local certifications. In normal operation, there # won't be any, assuming the import is performed on an # exported key. However, there are instances where this can # reasonably happen. For example, if an implementation allows # keys to be imported from a key database in addition to an # exported key, then this situation can arise." # # "Some implementations do not represent the interest of a # single user (for example, a key server). Such # implementations always trim local certifications from any # key they handle." raise LocalCertificationSignature signature['exportable'] = exportable elif sub.subtype == 5: signature['trust_level'] = int(sub.data[0]) signature['trust_amount'] = int(sub.data[1]) elif sub.subtype == 6: # Null-terminated regex = sub.data[:-1].decode('ascii', 'replace') try: validate_subpacket_regex(regex) except RegexValueError: return signature.setdefault('regexes', []) # The specifcation doesn't cover it, but it is reasonable that a # signature might specify multiple regular expressions. # # "In most cases, an implementation SHOULD use the last subpacket # in the signature, but MAY use any conflict resolution scheme # that makes more sense." signature['regexes'].append({ 'regex': regex, 'hashed': signature_hashed or sub.hashed }) elif sub.subtype == 7: signature['revocable'] = bool(sub.data[0]) elif sub.subtype == 9: # "This is found only on a self-signature." if not signature['selfsig']: return # Raw expiration time is actually the time from the creation time # that expiration occurs in seconds, rather than a unix timestamp # as its name might imply exp_time = utils.long_to_int(sub.data, 0) if exp_time == 0: return signature['key_expiration_seconds'] = exp_time elif sub.subtype == 11: # "This is found only on a self-signature." if not signature['selfsig']: return signature['preferred_sym_algorithms'] = list(map(int, sub.data)) elif sub.subtype == 12: if not bool(sub.data[0] & 0x80): # 0x80 MUST be set return # If this sensitive key is included when we receive it, it must # be for a reason. It would not be exported to be given to us # otherwise. # # "If this flag is set, implementations SHOULD NOT export this # signature to other users except in cases where the data needs # to be available: when the signature is being sent to the # designated revoker, or when it is accompanied by a revocation # signature from that revoker." signature['revocation_key_sensitive'] = bool(sub.data[0] & 0x40) signature['revocation_key_pub_algorithm_type'] = sub.data[1] signature['revocation_key'] = \ utils.bytearray_to_hex(sub.data[2:]) elif sub.subtype == 16: # "Some apparent conflicts may actually make sense -- for example, # suppose a keyholder has a V3 key and a V4 key that share the # same RSA key material. Either of these keys can verify a # signature created by the other, and it may be reasonable for a # signature to contain an issuer subpacket for each key, as a way # of explicitly tying those keys to the signature." signature.setdefault('key_ids', []) signature['key_ids'].append({ 'key_id': utils.bytearray_to_hex(sub.data, 0, 8).upper(), 'hashed': signature_hashed or sub.hashed }) elif sub.subtype == 20: notation = parse_notation(sub.data, signature_hashed or sub.hashed) if notation: if sub.critical: # "If there is a critical notation, the criticality # applies to that specific notation and not to notations # in general." raise CannotParseCriticalNotation(notation['name']) signature.setdefault('notations', []) signature['notations'].append(notation) elif sub.subtype == 21: # "This is found only on a self-signature." if not signature['selfsig']: return signature['preferred_hash_algorithms'] = list(map(int, sub.data)) elif sub.subtype == 22: # "This is found only on a self-signature." if not signature['selfsig']: return signature['preferred_compression_algorithms'] = \ list(map(int, sub.data)) elif sub.subtype == 23: if not signature['selfsig']: # "This is found only on a self-signature." return # "All undefined flags MUST be zero." if sub.data[0] & 0x7f or any(sub.data[1:]): return signature['key_server_no_modify'] = bool(sub.data[0] & 0x80) elif sub.subtype == 24: signature['preferred_key_server'] = \ sub.data.decode('utf8', 'replace') elif sub.subtype == 25: # "This is a flag in a User ID's self-signature" # ... # "there are two different and independent "primaries" -- one for # User IDs, and one for User Attributes." if not signature['selfsig']: return if signature_owner_type not in (13, 17): return # Store the actual integer value. If we have multiple "primaries" # we can use this to resolve priority. # # "If more than one User ID in a key is marked as primary, the # implementation may resolve the ambiguity in any way it sees # fit, but it is RECOMMENDED that priority be given to the User # ID with the most recent self-signature." signature['primary'] = int(sub.data[0]) elif sub.subtype == 26: signature['policy_uri'] = sub.data.decode('utf8', 'replace') elif sub.subtype == 27: flags = sub.data[0] signature['may_certify_others'] = bool(flags & 0x01) signature['may_sign_data'] = bool(flags & 0x02) signature['may_encrypt_comms'] = bool(flags & 0x04) signature['may_encrypt_storage'] = bool(flags & 0x08) signature['may_be_used_for_auth'] = bool(flags & 0x20) # "The "split key" (0x10) and "group key" (0x80) flags are placed # on a self-signature only; they are meaningless on a # certification signature. They SHOULD be placed only on a # direct-key signature (type 0x1F) or a subkey signature # (type 0x18), one that refers to the key the flag applies to." if signature['selfsig'] and signature['sig_type'] in (0x1f, 0x18): signature['may_have_been_split'] = bool(flags & 0x10) signature['may_have_multiple_owners'] = bool(flags & 0x80) elif sub.subtype == 28: signature['user_id'] = sub.data.decode('utf8', 'replace') elif sub.subtype == 29: # "This subpacket is used only in key revocation and certification # revocation signatures." if signature['sig_type'] not in (0x20, 0x28): return revocation_code = int(sub.data[0]) if (revocation_code in (0, 1, 2, 3, 32) or (revocation_code > 99 and revocation_code < 111)): signature['revocation_code'] = revocation_code signature['revocation_reason'] = \ sub.data[1:].decode('utf8', 'replace') elif sub.subtype == 30: # "This subpacket is similar to a preferences subpacket, and only # appears in a self-signature." if not signature['selfsig']: return if sub.data[0] & 0xfe: return if any(sub.data[1:]): return supports_modification = sub.data[0] & 0x01 signature['supports_modification_detection'] = supports_modification elif sub.subtype == 31: signature['target_pub_key_algorithm'] = sub.data[0] signature['target_hash_algorithm'] = sub.data[1] # Store the target hash as hex signature['target_hash'] = utils.bytearray_to_hex(sub.data[2:]) elif sub.subtype == 32: subsignature = parse_embedded_signature(sub.data, signature_hashed or sub.hashed) signature.setdefault('embedded_signatures', []) signature['embedded_signatures'].append(subsignature) # Unparseable signature subpackets # "If a subpacket is encountered that is marked critical but is # unknown to the evaluating software, the evaluator SHOULD consider # the signature to be in error." elif sub.subtype > 99 and sub.subtype < 111: # private / experimental if sub.critical: raise CannotParseCritical(sub.subtype) elif sub.subtype in (0, 1, 8, 13, 14, 15, 17, 18, 19): # "An implementation SHOULD ignore any subpacket of a type that it # does not recognize." # raise ReservedSignatureSubpacket(sub.subtype) if sub.critical: raise CannotParseCritical(sub.subtype) else: # "An implementation SHOULD ignore any subpacket of a type that it # does not recognize." # raise InvalidSignatureSubpacket(sub.subtype) if sub.critical: raise CannotParseCritical(sub.subtype)
def parse_signature_packet( p, parent_type, parent_key_ids=None, sig_hashed=False, # For testing parse_signature_subpacket=parse_signature_subpacket): """Parse a single pgpdump signature packet into a Python dictionary of values. sig_hashed should be set to True for signature packets which are embedded as hashed signature subpackets. """ signature = {} signature['validated'] = None signature['hash_algorithm_type'] = p.raw_hash_algorithm signature['pub_algorithm_type'] = p.raw_pub_algorithm signature['sig_type'] = p.raw_sig_type signature['sig_version'] = p.sig_version signature['hash2'] = utils.bytearray_to_hex(p.hash2) # "If a subpacket is not hashed, then the information in it cannot be # considered definitive because it is not part of the signature proper." # # For convenience, include the fields that are always hashed. signature['hashed'] = [ 'hash_algorithm_type', 'pub_algorithm_type', 'sig_type', 'sig_version', ] if p.sig_version in (2, 3): # Only trust these explicitly if the version is < 4. If the version is # 4 or greater, these values may have come from unhashed subpackets # and could have been manipulated. signature['key_ids'] = [{ 'key_id': p.key_id.upper(), 'hashed': True, }] signature['selfsig'] = p.key_id.upper() in parent_key_ids signature['creation_time'] = p.raw_creation_time signature['hashed'].extend([ 'creation_time', ]) elif p.sig_version >= 4: hashed = {} # Parse Key IDs first so we know if it's a selfsignature or not for sub in p.subpackets: if sub.subtype == 16: parse_signature_subpacket(sub, signature, parent_type) sig_key_ids = set( map(lambda k: k['key_id'], signature.get('key_ids', []))) signature['selfsig'] = bool(sig_key_ids & set(parent_key_ids)) for sub in p.subpackets: if sub.subtype == 16: continue parse_signature_subpacket(sub, signature, parent_type) if sub.subtype not in (6, 16, 20, 32): hashed[sub.subtype] = sig_hashed or sub.hashed for k, v in hashed.items(): if not k or not v: continue keys = subpacket_type_to_keys(k) signature['hashed'].extend(keys) else: raise UnsupportedSignatureVersion(p.sig_version) return signature