# coding: utf-8 """ ASN.1 type classes for public and private keys. Exports the following items: - DSAPrivateKey() - ECPrivateKey() - EncryptedPrivateKeyInfo() - PrivateKeyInfo() - PublicKeyInfo() - RSAPrivateKey() - RSAPublicKey() Other type classes are defined that help compose the types listed above. """ from __future__ import unicode_literals, division, absolute_import, print_function import hashlib import math from ._elliptic_curve import ( SECP192R1_BASE_POINT, SECP224R1_BASE_POINT, SECP256R1_BASE_POINT, SECP384R1_BASE_POINT, SECP521R1_BASE_POINT, PrimeCurve, PrimePoint, ) from ._errors import unwrap from ._types import type_name, str_cls, byte_cls from .algos import _ForceNullParameters, DigestAlgorithm, EncryptionAlgorithm, RSAESOAEPParams from .core import ( Any, Asn1Value, BitString, Choice, Integer, IntegerOctetString, Null, ObjectIdentifier, OctetBitString, OctetString, ParsableOctetString, ParsableOctetBitString, Sequence, SequenceOf, SetOf, ) from .util import int_from_bytes, int_to_bytes class OtherPrimeInfo(Sequence): """ Source: https://tools.ietf.org/html/rfc3447#page-46 """ _fields = [ ('prime', Integer), ('exponent', Integer), ('coefficient', Integer), ] class OtherPrimeInfos(SequenceOf): """ Source: https://tools.ietf.org/html/rfc3447#page-46 """ _child_spec = OtherPrimeInfo class RSAPrivateKeyVersion(Integer): """ Original Name: Version Source: https://tools.ietf.org/html/rfc3447#page-45 """ _map = { 0: 'two-prime', 1: 'multi', } class RSAPrivateKey(Sequence): """ Source: https://tools.ietf.org/html/rfc3447#page-45 """ _fields = [ ('version', RSAPrivateKeyVersion), ('modulus', Integer), ('public_exponent', Integer), ('private_exponent', Integer), ('prime1', Integer), ('prime2', Integer), ('exponent1', Integer), ('exponent2', Integer), ('coefficient', Integer), ('other_prime_infos', OtherPrimeInfos, {'optional': True}) ] class RSAPublicKey(Sequence): """ Source: https://tools.ietf.org/html/rfc3447#page-44 """ _fields = [ ('modulus', Integer), ('public_exponent', Integer) ] class DSAPrivateKey(Sequence): """ The ASN.1 structure that OpenSSL uses to store a DSA private key that is not part of a PKCS#8 structure. Reversed engineered from english-language description on linked OpenSSL documentation page. Original Name: None Source: https://www.openssl.org/docs/apps/dsa.html """ _fields = [ ('version', Integer), ('p', Integer), ('q', Integer), ('g', Integer), ('public_key', Integer), ('private_key', Integer), ] class _ECPoint(): """ In both PublicKeyInfo and PrivateKeyInfo, the EC public key is a byte string that is encoded as a bit string. This class adds convenience methods for converting to and from the byte string to a pair of integers that are the X and Y coordinates. """ @classmethod def from_coords(cls, x, y): """ Creates an ECPoint object from the X and Y integer coordinates of the point :param x: The X coordinate, as an integer :param y: The Y coordinate, as an integer :return: An ECPoint object """ x_bytes = int(math.ceil(math.log(x, 2) / 8.0)) y_bytes = int(math.ceil(math.log(y, 2) / 8.0)) num_bytes = max(x_bytes, y_bytes) byte_string = b'\x04' byte_string += int_to_bytes(x, width=num_bytes) byte_string += int_to_bytes(y, width=num_bytes) return cls(byte_string) def to_coords(self): """ Returns the X and Y coordinates for this EC point, as native Python integers :return: A 2-element tuple containing integers (X, Y) """ data = self.native first_byte = data[0:1] # Uncompressed if first_byte == b'\x04': remaining = data[1:] field_len = len(remaining) // 2 x = int_from_bytes(remaining[0:field_len]) y = int_from_bytes(remaining[field_len:]) return (x, y) if first_byte not in set([b'\x02', b'\x03']): raise ValueError(unwrap( ''' Invalid EC public key - first byte is incorrect ''' )) raise ValueError(unwrap( ''' Compressed representations of EC public keys are not supported due to patent US6252960 ''' )) class ECPoint(OctetString, _ECPoint): pass class ECPointBitString(OctetBitString, _ECPoint): pass class SpecifiedECDomainVersion(Integer): """ Source: http://www.secg.org/sec1-v2.pdf page 104 """ _map = { 1: 'ecdpVer1', 2: 'ecdpVer2', 3: 'ecdpVer3', } class FieldType(ObjectIdentifier): """ Original Name: None Source: http://www.secg.org/sec1-v2.pdf page 101 """ _map = { '1.2.840.10045.1.1': 'prime_field', '1.2.840.10045.1.2': 'characteristic_two_field', } class CharacteristicTwoBasis(ObjectIdentifier): """ Original Name: None Source: http://www.secg.org/sec1-v2.pdf page 102 """ _map = { '1.2.840.10045.1.2.1.1': 'gn_basis', '1.2.840.10045.1.2.1.2': 'tp_basis', '1.2.840.10045.1.2.1.3': 'pp_basis', } class Pentanomial(Sequence): """ Source: http://www.secg.org/sec1-v2.pdf page 102 """ _fields = [ ('k1', Integer), ('k2', Integer), ('k3', Integer), ] class CharacteristicTwo(Sequence): """ Original Name: Characteristic-two Source: http://www.secg.org/sec1-v2.pdf page 101 """ _fields = [ ('m', Integer), ('basis', CharacteristicTwoBasis), ('parameters', Any), ] _oid_pair = ('basis', 'parameters') _oid_specs = { 'gn_basis': Null, 'tp_basis': Integer, 'pp_basis': Pentanomial, } class FieldID(Sequence): """ Source: http://www.secg.org/sec1-v2.pdf page 100 """ _fields = [ ('field_type', FieldType), ('parameters', Any), ] _oid_pair = ('field_type', 'parameters') _oid_specs = { 'prime_field': Integer, 'characteristic_two_field': CharacteristicTwo, } class Curve(Sequence): """ Source: http://www.secg.org/sec1-v2.pdf page 104 """ _fields = [ ('a', OctetString), ('b', OctetString), ('seed', OctetBitString, {'optional': True}), ] class SpecifiedECDomain(Sequence): """ Source: http://www.secg.org/sec1-v2.pdf page 103 """ _fields = [ ('version', SpecifiedECDomainVersion), ('field_id', FieldID), ('curve', Curve), ('base', ECPoint), ('order', Integer), ('cofactor', Integer, {'optional': True}), ('hash', DigestAlgorithm, {'optional': True}), ] class NamedCurve(ObjectIdentifier): """ Various named curves Original Name: None Source: https://tools.ietf.org/html/rfc3279#page-23, https://tools.ietf.org/html/rfc5480#page-5 """ _map = { # https://tools.ietf.org/html/rfc3279#page-23 '1.2.840.10045.3.0.1': 'c2pnb163v1', '1.2.840.10045.3.0.2': 'c2pnb163v2', '1.2.840.10045.3.0.3': 'c2pnb163v3', '1.2.840.10045.3.0.4': 'c2pnb176w1', '1.2.840.10045.3.0.5': 'c2tnb191v1', '1.2.840.10045.3.0.6': 'c2tnb191v2', '1.2.840.10045.3.0.7': 'c2tnb191v3', '1.2.840.10045.3.0.8': 'c2onb191v4', '1.2.840.10045.3.0.9': 'c2onb191v5', '1.2.840.10045.3.0.10': 'c2pnb208w1', '1.2.840.10045.3.0.11': 'c2tnb239v1', '1.2.840.10045.3.0.12': 'c2tnb239v2', '1.2.840.10045.3.0.13': 'c2tnb239v3', '1.2.840.10045.3.0.14': 'c2onb239v4', '1.2.840.10045.3.0.15': 'c2onb239v5', '1.2.840.10045.3.0.16': 'c2pnb272w1', '1.2.840.10045.3.0.17': 'c2pnb304w1', '1.2.840.10045.3.0.18': 'c2tnb359v1', '1.2.840.10045.3.0.19': 'c2pnb368w1', '1.2.840.10045.3.0.20': 'c2tnb431r1', '1.2.840.10045.3.1.2': 'prime192v2', '1.2.840.10045.3.1.3': 'prime192v3', '1.2.840.10045.3.1.4': 'prime239v1', '1.2.840.10045.3.1.5': 'prime239v2', '1.2.840.10045.3.1.6': 'prime239v3', # https://tools.ietf.org/html/rfc5480#page-5 '1.3.132.0.1': 'sect163k1', '1.3.132.0.15': 'sect163r2', '1.2.840.10045.3.1.1': 'secp192r1', '1.3.132.0.33': 'secp224r1', '1.3.132.0.26': 'sect233k1', '1.2.840.10045.3.1.7': 'secp256r1', '1.3.132.0.27': 'sect233r1', '1.3.132.0.16': 'sect283k1', '1.3.132.0.17': 'sect283r1', '1.3.132.0.34': 'secp384r1', '1.3.132.0.36': 'sect409k1', '1.3.132.0.37': 'sect409r1', '1.3.132.0.35': 'secp521r1', '1.3.132.0.38': 'sect571k1', '1.3.132.0.39': 'sect571r1', } class ECDomainParameters(Choice): """ Source: http://www.secg.org/sec1-v2.pdf page 102 """ _alternatives = [ ('specified', SpecifiedECDomain), ('named', NamedCurve), ('implicit_ca', Null), ] class ECPrivateKeyVersion(Integer): """ Original Name: None Source: http://www.secg.org/sec1-v2.pdf page 108 """ _map = { 1: 'ecPrivkeyVer1', } class ECPrivateKey(Sequence): """ Source: http://www.secg.org/sec1-v2.pdf page 108 """ _fields = [ ('version', ECPrivateKeyVersion), ('private_key', IntegerOctetString), ('parameters', ECDomainParameters, {'explicit': 0, 'optional': True}), ('public_key', ECPointBitString, {'explicit': 1, 'optional': True}), ] class DSAParams(Sequence): """ Parameters for a DSA public or private key Original Name: Dss-Parms Source: https://tools.ietf.org/html/rfc3279#page-9 """ _fields = [ ('p', Integer), ('q', Integer), ('g', Integer), ] class Attribute(Sequence): """ Source: https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-X.501-198811-S!!PDF-E&type=items page 8 """ _fields = [ ('type', ObjectIdentifier), ('values', SetOf, {'spec': Any}), ] class Attributes(SetOf): """ Source: https://tools.ietf.org/html/rfc5208#page-3 """ _child_spec = Attribute class PrivateKeyAlgorithmId(ObjectIdentifier): """ These OIDs for various public keys are reused when storing private keys inside of a PKCS#8 structure Original Name: None Source: https://tools.ietf.org/html/rfc3279 """ _map = { # https://tools.ietf.org/html/rfc3279#page-19 '1.2.840.113549.1.1.1': 'rsa', # https://tools.ietf.org/html/rfc3279#page-18 '1.2.840.10040.4.1': 'dsa', # https://tools.ietf.org/html/rfc3279#page-13 '1.2.840.10045.2.1': 'ec', } class PrivateKeyAlgorithm(_ForceNullParameters, Sequence): """ Original Name: PrivateKeyAlgorithmIdentifier Source: https://tools.ietf.org/html/rfc5208#page-3 """ _fields = [ ('algorithm', PrivateKeyAlgorithmId), ('parameters', Any, {'optional': True}), ] _oid_pair = ('algorithm', 'parameters') _oid_specs = { 'dsa': DSAParams, 'ec': ECDomainParameters, } class PrivateKeyInfo(Sequence): """ Source: https://tools.ietf.org/html/rfc5208#page-3 """ _fields = [ ('version', Integer), ('private_key_algorithm', PrivateKeyAlgorithm), ('private_key', ParsableOctetString), ('attributes', Attributes, {'implicit': 0, 'optional': True}), ] def _private_key_spec(self): algorithm = self['private_key_algorithm']['algorithm'].native return { 'rsa': RSAPrivateKey, 'dsa': Integer, 'ec': ECPrivateKey, }[algorithm] _spec_callbacks = { 'private_key': _private_key_spec } _algorithm = None _bit_size = None _public_key = None _fingerprint = None @classmethod def wrap(cls, private_key, algorithm): """ Wraps a private key in a PrivateKeyInfo structure :param private_key: A byte string or Asn1Value object of the private key :param algorithm: A unicode string of "rsa", "dsa" or "ec" :return: A PrivateKeyInfo object """ if not isinstance(private_key, byte_cls) and not isinstance(private_key, Asn1Value): raise TypeError(unwrap( ''' private_key must be a byte string or Asn1Value, not %s ''', type_name(private_key) )) if algorithm == 'rsa': if not isinstance(private_key, RSAPrivateKey): private_key = RSAPrivateKey.load(private_key) params = Null() elif algorithm == 'dsa': if not isinstance(private_key, DSAPrivateKey): private_key = DSAPrivateKey.load(private_key) params = DSAParams() params['p'] = private_key['p'] params['q'] = private_key['q'] params['g'] = private_key['g'] public_key = private_key['public_key'] private_key = private_key['private_key'] elif algorithm == 'ec': if not isinstance(private_key, ECPrivateKey): private_key = ECPrivateKey.load(private_key) else: private_key = private_key.copy() params = private_key['parameters'] del private_key['parameters'] else: raise ValueError(unwrap( ''' algorithm must be one of "rsa", "dsa", "ec", not %s ''', repr(algorithm) )) private_key_algo = PrivateKeyAlgorithm() private_key_algo['algorithm'] = PrivateKeyAlgorithmId(algorithm) private_key_algo['parameters'] = params container = cls() container._algorithm = algorithm container['version'] = Integer(0) container['private_key_algorithm'] = private_key_algo container['private_key'] = private_key # Here we save the DSA public key if possible since it is not contained # within the PKCS#8 structure for a DSA key if algorithm == 'dsa': container._public_key = public_key return container def _compute_public_key(self): """ Computes the public key corresponding to the current private key. :return: For RSA keys, an RSAPublicKey object. For DSA keys, an Integer object. For EC keys, an ECPointBitString. """ if self.algorithm == 'dsa': params = self['private_key_algorithm']['parameters'] return Integer(pow( params['g'].native, self['private_key'].parsed.native, params['p'].native )) if self.algorithm == 'rsa': key = self['private_key'].parsed return RSAPublicKey({ 'modulus': key['modulus'], 'public_exponent': key['public_exponent'], }) if self.algorithm == 'ec': curve_type, details = self.curve if curve_type == 'implicit_ca': raise ValueError(unwrap( ''' Unable to compute public key for EC key using Implicit CA parameters ''' )) if curve_type == 'specified': if details['field_id']['field_type'] == 'characteristic_two_field': raise ValueError(unwrap( ''' Unable to compute public key for EC key over a characteristic two field ''' )) curve = PrimeCurve( details['field_id']['parameters'], int_from_bytes(details['curve']['a']), int_from_bytes(details['curve']['b']) ) base_x, base_y = self['private_key_algorithm']['parameters'].chosen['base'].to_coords() base_point = PrimePoint(curve, base_x, base_y) elif curve_type == 'named': if details not in ('secp192r1', 'secp224r1', 'secp256r1', 'secp384r1', 'secp521r1'): raise ValueError(unwrap( ''' Unable to compute public key for EC named curve %s, parameters not currently included ''', details )) base_point = { 'secp192r1': SECP192R1_BASE_POINT, 'secp224r1': SECP224R1_BASE_POINT, 'secp256r1': SECP256R1_BASE_POINT, 'secp384r1': SECP384R1_BASE_POINT, 'secp521r1': SECP521R1_BASE_POINT, }[details] public_point = base_point * self['private_key'].parsed['private_key'].native return ECPointBitString.from_coords(public_point.x, public_point.y) def unwrap(self): """ Unwraps the private key into an RSAPrivateKey, DSAPrivateKey or ECPrivateKey object :return: An RSAPrivateKey, DSAPrivateKey or ECPrivateKey object """ if self.algorithm == 'rsa': return self['private_key'].parsed if self.algorithm == 'dsa': params = self['private_key_algorithm']['parameters'] return DSAPrivateKey({ 'version': 0, 'p': params['p'], 'q': params['q'], 'g': params['g'], 'public_key': self.public_key, 'private_key': self['private_key'].parsed, }) if self.algorithm == 'ec': output = self['private_key'].parsed output['parameters'] = self['private_key_algorithm']['parameters'] output['public_key'] = self.public_key return output @property def curve(self): """ Returns information about the curve used for an EC key :raises: ValueError - when the key is not an EC key :return: A two-element tuple, with the first element being a unicode string of "implicit_ca", "specified" or "named". If the first element is "implicit_ca", the second is None. If "specified", the second is an OrderedDict that is the native version of SpecifiedECDomain. If "named", the second is a unicode string of the curve name. """ if self.algorithm != 'ec': raise ValueError(unwrap( ''' Only EC keys have a curve, this key is %s ''', self.algorithm.upper() )) params = self['private_key_algorithm']['parameters'] chosen = params.chosen if params.name == 'implicit_ca': value = None else: value = chosen.native return (params.name, value) @property def hash_algo(self): """ Returns the name of the family of hash algorithms used to generate a DSA key :raises: ValueError - when the key is not a DSA key :return: A unicode string of "sha1" or "sha2" """ if self.algorithm != 'dsa': raise ValueError(unwrap( ''' Only DSA keys are generated using a hash algorithm, this key is %s ''', self.algorithm.upper() )) byte_len = math.log(self['private_key_algorithm']['parameters']['q'].native, 2) / 8 return 'sha1' if byte_len <= 20 else 'sha2' @property def algorithm(self): """ :return: A unicode string of "rsa", "dsa" or "ec" """ if self._algorithm is None: self._algorithm = self['private_key_algorithm']['algorithm'].native return self._algorithm @property def bit_size(self): """ :return: The bit size of the private key, as an integer """ if self._bit_size is None: if self.algorithm == 'rsa': prime = self['private_key'].parsed['modulus'].native elif self.algorithm == 'dsa': prime = self['private_key_algorithm']['parameters']['p'].native elif self.algorithm == 'ec': prime = self['private_key'].parsed['private_key'].native self._bit_size = int(math.ceil(math.log(prime, 2))) modulus = self._bit_size % 8 if modulus != 0: self._bit_size += 8 - modulus return self._bit_size @property def byte_size(self): """ :return: The byte size of the private key, as an integer """ return int(math.ceil(self.bit_size / 8)) @property def public_key(self): """ :return: If an RSA key, an RSAPublicKey object. If a DSA key, an Integer object. If an EC key, an ECPointBitString object. """ if self._public_key is None: if self.algorithm == 'ec': key = self['private_key'].parsed if key['public_key']: self._public_key = key['public_key'].untag() else: self._public_key = self._compute_public_key() else: self._public_key = self._compute_public_key() return self._public_key @property def public_key_info(self): """ :return: A PublicKeyInfo object derived from this private key. """ return PublicKeyInfo({ 'algorithm': { 'algorithm': self.algorithm, 'parameters': self['private_key_algorithm']['parameters'] }, 'public_key': self.public_key }) @property def fingerprint(self): """ Creates a fingerprint that can be compared with a public key to see if the two form a pair. This fingerprint is not compatible with fingerprints generated by any other software. :return: A byte string that is a sha256 hash of selected components (based on the key type) """ if self._fingerprint is None: params = self['private_key_algorithm']['parameters'] key = self['private_key'].parsed if self.algorithm == 'rsa': to_hash = '%d:%d' % ( key['modulus'].native, key['public_exponent'].native, ) elif self.algorithm == 'dsa': public_key = self.public_key to_hash = '%d:%d:%d:%d' % ( params['p'].native, params['q'].native, params['g'].native, public_key.native, ) elif self.algorithm == 'ec': public_key = key['public_key'].native if public_key is None: public_key = self.public_key.native if params.name == 'named': to_hash = '%s:' % params.chosen.native to_hash = to_hash.encode('utf-8') to_hash += public_key elif params.name == 'implicit_ca': to_hash = public_key elif params.name == 'specified': to_hash = '%s:' % params.chosen['field_id']['parameters'].native to_hash = to_hash.encode('utf-8') to_hash += b':' + params.chosen['curve']['a'].native to_hash += b':' + params.chosen['curve']['b'].native to_hash += public_key if isinstance(to_hash, str_cls): to_hash = to_hash.encode('utf-8') self._fingerprint = hashlib.sha256(to_hash).digest() return self._fingerprint class EncryptedPrivateKeyInfo(Sequence): """ Source: https://tools.ietf.org/html/rfc5208#page-4 """ _fields = [ ('encryption_algorithm', EncryptionAlgorithm), ('encrypted_data', OctetString), ] # These structures are from https://tools.ietf.org/html/rfc3279 class ValidationParms(Sequence): """ Source: https://tools.ietf.org/html/rfc3279#page-10 """ _fields = [ ('seed', BitString), ('pgen_counter', Integer), ] class DomainParameters(Sequence): """ Source: https://tools.ietf.org/html/rfc3279#page-10 """ _fields = [ ('p', Integer), ('g', Integer), ('q', Integer), ('j', Integer, {'optional': True}), ('validation_params', ValidationParms, {'optional': True}), ] class PublicKeyAlgorithmId(ObjectIdentifier): """ Original Name: None Source: https://tools.ietf.org/html/rfc3279 """ _map = { # https://tools.ietf.org/html/rfc3279#page-19 '1.2.840.113549.1.1.1': 'rsa', # https://tools.ietf.org/html/rfc3447#page-47 '1.2.840.113549.1.1.7': 'rsaes_oaep', # https://tools.ietf.org/html/rfc3279#page-18 '1.2.840.10040.4.1': 'dsa', # https://tools.ietf.org/html/rfc3279#page-13 '1.2.840.10045.2.1': 'ec', # https://tools.ietf.org/html/rfc3279#page-10 '1.2.840.10046.2.1': 'dh', } class PublicKeyAlgorithm(_ForceNullParameters, Sequence): """ Original Name: AlgorithmIdentifier Source: https://tools.ietf.org/html/rfc5280#page-18 """ _fields = [ ('algorithm', PublicKeyAlgorithmId), ('parameters', Any, {'optional': True}), ] _oid_pair = ('algorithm', 'parameters') _oid_specs = { 'dsa': DSAParams, 'ec': ECDomainParameters, 'dh': DomainParameters, 'rsaes_oaep': RSAESOAEPParams, } class PublicKeyInfo(Sequence): """ Original Name: SubjectPublicKeyInfo Source: https://tools.ietf.org/html/rfc5280#page-17 """ _fields = [ ('algorithm', PublicKeyAlgorithm), ('public_key', ParsableOctetBitString), ] def _public_key_spec(self): algorithm = self['algorithm']['algorithm'].native return { 'rsa': RSAPublicKey, 'rsaes_oaep': RSAPublicKey, 'dsa': Integer, # We override the field spec with ECPoint so that users can easily # decompose the byte string into the constituent X and Y coords 'ec': (ECPointBitString, None), 'dh': Integer, }[algorithm] _spec_callbacks = { 'public_key': _public_key_spec } _algorithm = None _bit_size = None _fingerprint = None _sha1 = None _sha256 = None @classmethod def wrap(cls, public_key, algorithm): """ Wraps a public key in a PublicKeyInfo structure :param public_key: A byte string or Asn1Value object of the public key :param algorithm: A unicode string of "rsa" :return: A PublicKeyInfo object """ if not isinstance(public_key, byte_cls) and not isinstance(public_key, Asn1Value): raise TypeError(unwrap( ''' public_key must be a byte string or Asn1Value, not %s ''', type_name(public_key) )) if algorithm != 'rsa': raise ValueError(unwrap( ''' algorithm must "rsa", not %s ''', repr(algorithm) )) algo = PublicKeyAlgorithm() algo['algorithm'] = PublicKeyAlgorithmId(algorithm) algo['parameters'] = Null() container = cls() container['algorithm'] = algo if isinstance(public_key, Asn1Value): public_key = public_key.untag().dump() container['public_key'] = ParsableOctetBitString(public_key) return container def unwrap(self): """ Unwraps an RSA public key into an RSAPublicKey object. Does not support DSA or EC public keys since they do not have an unwrapped form. :return: An RSAPublicKey object """ if self.algorithm == 'rsa': return self['public_key'].parsed key_type = self.algorithm.upper() a_an = 'an' if key_type == 'EC' else 'a' raise ValueError(unwrap( ''' Only RSA public keys may be unwrapped - this key is %s %s public key ''', a_an, key_type )) @property def curve(self): """ Returns information about the curve used for an EC key :raises: ValueError - when the key is not an EC key :return: A two-element tuple, with the first element being a unicode string of "implicit_ca", "specified" or "named". If the first element is "implicit_ca", the second is None. If "specified", the second is an OrderedDict that is the native version of SpecifiedECDomain. If "named", the second is a unicode string of the curve name. """ if self.algorithm != 'ec': raise ValueError(unwrap( ''' Only EC keys have a curve, this key is %s ''', self.algorithm.upper() )) params = self['algorithm']['parameters'] chosen = params.chosen if params.name == 'implicit_ca': value = None else: value = chosen.native return (params.name, value) @property def hash_algo(self): """ Returns the name of the family of hash algorithms used to generate a DSA key :raises: ValueError - when the key is not a DSA key :return: A unicode string of "sha1" or "sha2" or None if no parameters are present """ if self.algorithm != 'dsa': raise ValueError(unwrap( ''' Only DSA keys are generated using a hash algorithm, this key is %s ''', self.algorithm.upper() )) parameters = self['algorithm']['parameters'] if parameters.native is None: return None byte_len = math.log(parameters['q'].native, 2) / 8 return 'sha1' if byte_len <= 20 else 'sha2' @property def algorithm(self): """ :return: A unicode string of "rsa", "dsa" or "ec" """ if self._algorithm is None: self._algorithm = self['algorithm']['algorithm'].native return self._algorithm @property def bit_size(self): """ :return: The bit size of the public key, as an integer """ if self._bit_size is None: if self.algorithm == 'ec': self._bit_size = ((len(self['public_key'].native) - 1) / 2) * 8 else: if self.algorithm == 'rsa': prime = self['public_key'].parsed['modulus'].native elif self.algorithm == 'dsa': prime = self['algorithm']['parameters']['p'].native self._bit_size = int(math.ceil(math.log(prime, 2))) modulus = self._bit_size % 8 if modulus != 0: self._bit_size += 8 - modulus return self._bit_size @property def byte_size(self): """ :return: The byte size of the public key, as an integer """ return int(math.ceil(self.bit_size / 8)) @property def sha1(self): """ :return: The SHA1 hash of the DER-encoded bytes of this public key info """ if self._sha1 is None: self._sha1 = hashlib.sha1(byte_cls(self['public_key'])).digest() return self._sha1 @property def sha256(self): """ :return: The SHA-256 hash of the DER-encoded bytes of this public key info """ if self._sha256 is None: self._sha256 = hashlib.sha256(byte_cls(self['public_key'])).digest() return self._sha256 @property def fingerprint(self): """ Creates a fingerprint that can be compared with a private key to see if the two form a pair. This fingerprint is not compatible with fingerprints generated by any other software. :return: A byte string that is a sha256 hash of selected components (based on the key type) """ if self._fingerprint is None: key_type = self['algorithm']['algorithm'].native params = self['algorithm']['parameters'] if key_type == 'rsa': key = self['public_key'].parsed to_hash = '%d:%d' % ( key['modulus'].native, key['public_exponent'].native, ) elif key_type == 'dsa': key = self['public_key'].parsed to_hash = '%d:%d:%d:%d' % ( params['p'].native, params['q'].native, params['g'].native, key.native, ) elif key_type == 'ec': key = self['public_key'] if params.name == 'named': to_hash = '%s:' % params.chosen.native to_hash = to_hash.encode('utf-8') to_hash += key.native elif params.name == 'implicit_ca': to_hash = key.native elif params.name == 'specified': to_hash = '%s:' % params.chosen['field_id']['parameters'].native to_hash = to_hash.encode('utf-8') to_hash += b':' + params.chosen['curve']['a'].native to_hash += b':' + params.chosen['curve']['b'].native to_hash += key.native if isinstance(to_hash, str_cls): to_hash = to_hash.encode('utf-8') self._fingerprint = hashlib.sha256(to_hash).digest() return self._fingerprint