# 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 ._errors import unwrap, APIException from ._types import type_name, byte_cls from .algos import _ForceNullParameters, DigestAlgorithm, EncryptionAlgorithm, RSAESOAEPParams, RSASSAPSSParams 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 # http://www.secg.org/SEC2-Ver-1.0.pdf '1.2.840.10045.3.1.1': 'secp192r1', '1.2.840.10045.3.1.7': 'secp256r1', '1.3.132.0.1': 'sect163k1', '1.3.132.0.2': 'sect163r1', '1.3.132.0.3': 'sect239k1', '1.3.132.0.4': 'sect113r1', '1.3.132.0.5': 'sect113r2', '1.3.132.0.6': 'secp112r1', '1.3.132.0.7': 'secp112r2', '1.3.132.0.8': 'secp160r1', '1.3.132.0.9': 'secp160k1', '1.3.132.0.10': 'secp256k1', '1.3.132.0.15': 'sect163r2', '1.3.132.0.16': 'sect283k1', '1.3.132.0.17': 'sect283r1', '1.3.132.0.22': 'sect131r1', '1.3.132.0.23': 'sect131r2', '1.3.132.0.24': 'sect193r1', '1.3.132.0.25': 'sect193r2', '1.3.132.0.26': 'sect233k1', '1.3.132.0.27': 'sect233r1', '1.3.132.0.28': 'secp128r1', '1.3.132.0.29': 'secp128r2', '1.3.132.0.30': 'secp160r2', '1.3.132.0.31': 'secp192k1', '1.3.132.0.32': 'secp224k1', '1.3.132.0.33': 'secp224r1', '1.3.132.0.34': 'secp384r1', '1.3.132.0.35': 'secp521r1', '1.3.132.0.36': 'sect409k1', '1.3.132.0.37': 'sect409r1', '1.3.132.0.38': 'sect571k1', '1.3.132.0.39': 'sect571r1', # https://tools.ietf.org/html/rfc5639#section-4.1 '1.3.36.3.3.2.8.1.1.1': 'brainpoolp160r1', '1.3.36.3.3.2.8.1.1.2': 'brainpoolp160t1', '1.3.36.3.3.2.8.1.1.3': 'brainpoolp192r1', '1.3.36.3.3.2.8.1.1.4': 'brainpoolp192t1', '1.3.36.3.3.2.8.1.1.5': 'brainpoolp224r1', '1.3.36.3.3.2.8.1.1.6': 'brainpoolp224t1', '1.3.36.3.3.2.8.1.1.7': 'brainpoolp256r1', '1.3.36.3.3.2.8.1.1.8': 'brainpoolp256t1', '1.3.36.3.3.2.8.1.1.9': 'brainpoolp320r1', '1.3.36.3.3.2.8.1.1.10': 'brainpoolp320t1', '1.3.36.3.3.2.8.1.1.11': 'brainpoolp384r1', '1.3.36.3.3.2.8.1.1.12': 'brainpoolp384t1', '1.3.36.3.3.2.8.1.1.13': 'brainpoolp512r1', '1.3.36.3.3.2.8.1.1.14': 'brainpoolp512t1', } _key_sizes = { # Order values used to compute these sourced from # http://cr.openjdk.java.net/~vinnie/7194075/webrev-3/src/share/classes/sun/security/ec/CurveDB.java.html '1.2.840.10045.3.0.1': 21, '1.2.840.10045.3.0.2': 21, '1.2.840.10045.3.0.3': 21, '1.2.840.10045.3.0.4': 21, '1.2.840.10045.3.0.5': 24, '1.2.840.10045.3.0.6': 24, '1.2.840.10045.3.0.7': 24, '1.2.840.10045.3.0.8': 24, '1.2.840.10045.3.0.9': 24, '1.2.840.10045.3.0.10': 25, '1.2.840.10045.3.0.11': 30, '1.2.840.10045.3.0.12': 30, '1.2.840.10045.3.0.13': 30, '1.2.840.10045.3.0.14': 30, '1.2.840.10045.3.0.15': 30, '1.2.840.10045.3.0.16': 33, '1.2.840.10045.3.0.17': 37, '1.2.840.10045.3.0.18': 45, '1.2.840.10045.3.0.19': 45, '1.2.840.10045.3.0.20': 53, '1.2.840.10045.3.1.2': 24, '1.2.840.10045.3.1.3': 24, '1.2.840.10045.3.1.4': 30, '1.2.840.10045.3.1.5': 30, '1.2.840.10045.3.1.6': 30, # Order values used to compute these sourced from # http://www.secg.org/SEC2-Ver-1.0.pdf # ceil(n.bit_length() / 8) '1.2.840.10045.3.1.1': 24, '1.2.840.10045.3.1.7': 32, '1.3.132.0.1': 21, '1.3.132.0.2': 21, '1.3.132.0.3': 30, '1.3.132.0.4': 15, '1.3.132.0.5': 15, '1.3.132.0.6': 14, '1.3.132.0.7': 14, '1.3.132.0.8': 21, '1.3.132.0.9': 21, '1.3.132.0.10': 32, '1.3.132.0.15': 21, '1.3.132.0.16': 36, '1.3.132.0.17': 36, '1.3.132.0.22': 17, '1.3.132.0.23': 17, '1.3.132.0.24': 25, '1.3.132.0.25': 25, '1.3.132.0.26': 29, '1.3.132.0.27': 30, '1.3.132.0.28': 16, '1.3.132.0.29': 16, '1.3.132.0.30': 21, '1.3.132.0.31': 24, '1.3.132.0.32': 29, '1.3.132.0.33': 28, '1.3.132.0.34': 48, '1.3.132.0.35': 66, '1.3.132.0.36': 51, '1.3.132.0.37': 52, '1.3.132.0.38': 72, '1.3.132.0.39': 72, # Order values used to compute these sourced from # https://tools.ietf.org/html/rfc5639#section-3 # ceil(q.bit_length() / 8) '1.3.36.3.3.2.8.1.1.1': 20, '1.3.36.3.3.2.8.1.1.2': 20, '1.3.36.3.3.2.8.1.1.3': 24, '1.3.36.3.3.2.8.1.1.4': 24, '1.3.36.3.3.2.8.1.1.5': 28, '1.3.36.3.3.2.8.1.1.6': 28, '1.3.36.3.3.2.8.1.1.7': 32, '1.3.36.3.3.2.8.1.1.8': 32, '1.3.36.3.3.2.8.1.1.9': 40, '1.3.36.3.3.2.8.1.1.10': 40, '1.3.36.3.3.2.8.1.1.11': 48, '1.3.36.3.3.2.8.1.1.12': 48, '1.3.36.3.3.2.8.1.1.13': 64, '1.3.36.3.3.2.8.1.1.14': 64, } @classmethod def register(cls, name, oid, key_size): """ Registers a new named elliptic curve that is not included in the default list of named curves :param name: A unicode string of the curve name :param oid: A unicode string of the dotted format OID :param key_size: An integer of the number of bytes the private key should be encoded to """ cls._map[oid] = name if cls._reverse_map is not None: cls._reverse_map[name] = oid cls._key_sizes[oid] = key_size class ECDomainParameters(Choice): """ Source: http://www.secg.org/sec1-v2.pdf page 102 """ _alternatives = [ ('specified', SpecifiedECDomain), ('named', NamedCurve), ('implicit_ca', Null), ] @property def key_size(self): if self.name == 'implicit_ca': raise ValueError(unwrap( ''' Unable to calculate key_size from ECDomainParameters that are implicitly defined by the CA key ''' )) if self.name == 'specified': order = self.chosen['order'].native return math.ceil(math.log(order, 2.0) / 8.0) oid = self.chosen.dotted if oid not in NamedCurve._key_sizes: raise ValueError(unwrap( ''' The asn1crypto.keys.NamedCurve %s does not have a registered key length, please call asn1crypto.keys.NamedCurve.register() ''', repr(oid) )) return NamedCurve._key_sizes[oid] 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}), ] # Ensures the key is set to the correct length when encoding _key_size = None # This is necessary to ensure the private_key IntegerOctetString is encoded properly def __setitem__(self, key, value): res = super(ECPrivateKey, self).__setitem__(key, value) if key == 'private_key': if self._key_size is None: # Infer the key_size from the existing private key if possible pkey_contents = self['private_key'].contents if isinstance(pkey_contents, byte_cls) and len(pkey_contents) > 1: self.set_key_size(len(self['private_key'].contents)) elif self._key_size is not None: self._update_key_size() elif key == 'parameters' and isinstance(self['parameters'], ECDomainParameters) and \ self['parameters'].name != 'implicit_ca': self.set_key_size(self['parameters'].key_size) return res def set_key_size(self, key_size): """ Sets the key_size to ensure the private key is encoded to the proper length :param key_size: An integer byte length to encode the private_key to """ self._key_size = key_size self._update_key_size() def _update_key_size(self): """ Ensure the private_key explicit encoding width is set """ if self._key_size is not None and isinstance(self['private_key'], IntegerOctetString): self['private_key'].set_encoded_width(self._key_size) 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/rfc4055#page-8 '1.2.840.113549.1.1.10': 'rsassa_pss', # 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/rfc8410#section-9 '1.3.101.110': 'x25519', '1.3.101.111': 'x448', '1.3.101.112': 'ed25519', '1.3.101.113': 'ed448', } 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, 'rsassa_pss': RSASSAPSSParams, } 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, 'rsassa_pss': RSAPrivateKey, 'dsa': Integer, 'ec': ECPrivateKey, # These should be treated as opaque octet strings according # to RFC 8410 'x25519': OctetString, 'x448': OctetString, 'ed25519': OctetString, 'ed448': OctetString, }[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' or algorithm == 'rsassa_pss': 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 # This is necessary to ensure any contained ECPrivateKey is the # correct size def __setitem__(self, key, value): res = super(PrivateKeyInfo, self).__setitem__(key, value) algorithm = self['private_key_algorithm'] # When possible, use the parameter info to make sure the private key encoding # retains any necessary leading bytes, instead of them being dropped if (key == 'private_key_algorithm' or key == 'private_key') and \ algorithm['algorithm'].native == 'ec' and \ isinstance(algorithm['parameters'], ECDomainParameters) and \ algorithm['parameters'].name != 'implicit_ca' and \ isinstance(self['private_key'], ParsableOctetString) and \ isinstance(self['private_key'].parsed, ECPrivateKey): self['private_key'].parsed.set_key_size(algorithm['parameters'].key_size) return res def unwrap(self): """ Unwraps the private key into an RSAPrivateKey, DSAPrivateKey or ECPrivateKey object :return: An RSAPrivateKey, DSAPrivateKey or ECPrivateKey object """ raise APIException( 'asn1crypto.keys.PrivateKeyInfo().unwrap() has been removed, ' 'please use oscrypto.asymmetric.PrivateKey().unwrap() instead') @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", "rsassa_pss", "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' or self.algorithm == 'rsassa_pss': 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. """ raise APIException( 'asn1crypto.keys.PrivateKeyInfo().public_key has been removed, ' 'please use oscrypto.asymmetric.PrivateKey().public_key.unwrap() instead') @property def public_key_info(self): """ :return: A PublicKeyInfo object derived from this private key. """ raise APIException( 'asn1crypto.keys.PrivateKeyInfo().public_key_info has been removed, ' 'please use oscrypto.asymmetric.PrivateKey().public_key.asn1 instead') @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) """ raise APIException( 'asn1crypto.keys.PrivateKeyInfo().fingerprint has been removed, ' 'please use oscrypto.asymmetric.PrivateKey().fingerprint instead') 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/rfc4055#page-8 '1.2.840.113549.1.1.10': 'rsassa_pss', # 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', # https://tools.ietf.org/html/rfc8410#section-9 '1.3.101.110': 'x25519', '1.3.101.111': 'x448', '1.3.101.112': 'ed25519', '1.3.101.113': 'ed448', } 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, 'rsassa_pss': RSASSAPSSParams, } 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, 'rsassa_pss': 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, # These should be treated as opaque bit strings according # to RFC 8410, and need not even be valid ASN.1 'x25519': (OctetBitString, None), 'x448': (OctetBitString, None), 'ed25519': (OctetBitString, None), 'ed448': (OctetBitString, None), }[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' and algorithm != 'rsassa_pss': 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 """ raise APIException( 'asn1crypto.keys.PublicKeyInfo().unwrap() has been removed, ' 'please use oscrypto.asymmetric.PublicKey().unwrap() instead') @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", "rsassa_pss", "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 = int(((len(self['public_key'].native) - 1) / 2) * 8) else: if self.algorithm == 'rsa' or self.algorithm == 'rsassa_pss': 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) """ raise APIException( 'asn1crypto.keys.PublicKeyInfo().fingerprint has been removed, ' 'please use oscrypto.asymmetric.PublicKey().fingerprint instead')