You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1249 lines
34 KiB

# 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