Viewing file: keys.py (34.36 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# 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
|