def parse_notation(data, hashed, encode_non_readable=b64encode): flags = utils.long_to_int(data, 0) notation_name_length = utils.short_to_int(data, 4) notation_value_length = utils.short_to_int(data, 6) name_end = 8 + notation_name_length value_end = name_end + notation_value_length notation_name = data[8:name_end].decode('utf8', 'replace') notation_name, notation_namespace = \ (notation_name.split('@', 1) + [None])[:2] notation_value = data[name_end:value_end] if flags & 0xffffff: # None of these flags are defined. They MUST be zero. return None human_readable = (flags >> 24) & 0xff == 0x80 if human_readable: # human readable notation_value = notation_value.decode('utf8', 'replace') else: notation_value = encode_non_readable(notation_value) return { u'name': notation_name, u'namespace': notation_namespace, u'human_readable': human_readable, u'value': notation_value, u'hashed': hashed, }
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_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) == 4 time = utils.long_to_int(sub_data, 0) return cls(critical, time)