1143 lines
33 KiB
Python
1143 lines
33 KiB
Python
# coding: utf-8
|
|
|
|
"""
|
|
ASN.1 type classes for various algorithms using in various aspects of public
|
|
key cryptography. Exports the following items:
|
|
|
|
- AlgorithmIdentifier()
|
|
- AnyAlgorithmIdentifier()
|
|
- DigestAlgorithm()
|
|
- DigestInfo()
|
|
- DSASignature()
|
|
- EncryptionAlgorithm()
|
|
- HmacAlgorithm()
|
|
- KdfAlgorithm()
|
|
- Pkcs5MacAlgorithm()
|
|
- SignedDigestAlgorithm()
|
|
|
|
Other type classes are defined that help compose the types listed above.
|
|
"""
|
|
|
|
from __future__ import unicode_literals, division, absolute_import, print_function
|
|
|
|
from ._errors import unwrap
|
|
from ._int import fill_width
|
|
from .util import int_from_bytes, int_to_bytes
|
|
from .core import (
|
|
Any,
|
|
Choice,
|
|
Integer,
|
|
Null,
|
|
ObjectIdentifier,
|
|
OctetString,
|
|
Sequence,
|
|
Void,
|
|
)
|
|
|
|
|
|
# Structures and OIDs in this file are pulled from
|
|
# https://tools.ietf.org/html/rfc3279, https://tools.ietf.org/html/rfc4055,
|
|
# https://tools.ietf.org/html/rfc5758, https://tools.ietf.org/html/rfc7292,
|
|
# http://www.emc.com/collateral/white-papers/h11302-pkcs5v2-1-password-based-cryptography-standard-wp.pdf
|
|
|
|
class AlgorithmIdentifier(Sequence):
|
|
_fields = [
|
|
('algorithm', ObjectIdentifier),
|
|
('parameters', Any, {'optional': True}),
|
|
]
|
|
|
|
|
|
class _ForceNullParameters(object):
|
|
"""
|
|
Various structures based on AlgorithmIdentifier require that the parameters
|
|
field be core.Null() for certain OIDs. This mixin ensures that happens.
|
|
"""
|
|
|
|
# The following attribute, plus the parameters spec callback and custom
|
|
# __setitem__ are all to handle a situation where parameters should not be
|
|
# optional and must be Null for certain OIDs. More info at
|
|
# https://tools.ietf.org/html/rfc4055#page-15 and
|
|
# https://tools.ietf.org/html/rfc4055#section-2.1
|
|
_null_algos = set([
|
|
'1.2.840.113549.1.1.1', # rsassa_pkcs1v15 / rsaes_pkcs1v15 / rsa
|
|
'1.2.840.113549.1.1.11', # sha256_rsa
|
|
'1.2.840.113549.1.1.12', # sha384_rsa
|
|
'1.2.840.113549.1.1.13', # sha512_rsa
|
|
'1.2.840.113549.1.1.14', # sha224_rsa
|
|
'1.3.14.3.2.26', # sha1
|
|
'2.16.840.1.101.3.4.2.4', # sha224
|
|
'2.16.840.1.101.3.4.2.1', # sha256
|
|
'2.16.840.1.101.3.4.2.2', # sha384
|
|
'2.16.840.1.101.3.4.2.3', # sha512
|
|
])
|
|
|
|
def _parameters_spec(self):
|
|
if self._oid_pair == ('algorithm', 'parameters'):
|
|
algo = self['algorithm'].native
|
|
if algo in self._oid_specs:
|
|
return self._oid_specs[algo]
|
|
|
|
if self['algorithm'].dotted in self._null_algos:
|
|
return Null
|
|
|
|
return None
|
|
|
|
_spec_callbacks = {
|
|
'parameters': _parameters_spec
|
|
}
|
|
|
|
# We have to override this since the spec callback uses the value of
|
|
# algorithm to determine the parameter spec, however default values are
|
|
# assigned before setting a field, so a default value can't be based on
|
|
# another field value (unless it is a default also). Thus we have to
|
|
# manually check to see if the algorithm was set and parameters is unset,
|
|
# and then fix the value as appropriate.
|
|
def __setitem__(self, key, value):
|
|
res = super(_ForceNullParameters, self).__setitem__(key, value)
|
|
if key != 'algorithm':
|
|
return res
|
|
if self['algorithm'].dotted not in self._null_algos:
|
|
return res
|
|
if self['parameters'].__class__ != Void:
|
|
return res
|
|
self['parameters'] = Null()
|
|
return res
|
|
|
|
|
|
class HmacAlgorithmId(ObjectIdentifier):
|
|
_map = {
|
|
'1.3.14.3.2.10': 'des_mac',
|
|
'1.2.840.113549.2.7': 'sha1',
|
|
'1.2.840.113549.2.8': 'sha224',
|
|
'1.2.840.113549.2.9': 'sha256',
|
|
'1.2.840.113549.2.10': 'sha384',
|
|
'1.2.840.113549.2.11': 'sha512',
|
|
'1.2.840.113549.2.12': 'sha512_224',
|
|
'1.2.840.113549.2.13': 'sha512_256',
|
|
}
|
|
|
|
|
|
class HmacAlgorithm(Sequence):
|
|
_fields = [
|
|
('algorithm', HmacAlgorithmId),
|
|
('parameters', Any, {'optional': True}),
|
|
]
|
|
|
|
|
|
class DigestAlgorithmId(ObjectIdentifier):
|
|
_map = {
|
|
'1.2.840.113549.2.2': 'md2',
|
|
'1.2.840.113549.2.5': 'md5',
|
|
'1.3.14.3.2.26': 'sha1',
|
|
'2.16.840.1.101.3.4.2.4': 'sha224',
|
|
'2.16.840.1.101.3.4.2.1': 'sha256',
|
|
'2.16.840.1.101.3.4.2.2': 'sha384',
|
|
'2.16.840.1.101.3.4.2.3': 'sha512',
|
|
'2.16.840.1.101.3.4.2.5': 'sha512_224',
|
|
'2.16.840.1.101.3.4.2.6': 'sha512_256',
|
|
}
|
|
|
|
|
|
class DigestAlgorithm(_ForceNullParameters, Sequence):
|
|
_fields = [
|
|
('algorithm', DigestAlgorithmId),
|
|
('parameters', Any, {'optional': True}),
|
|
]
|
|
|
|
|
|
# This structure is what is signed with a SignedDigestAlgorithm
|
|
class DigestInfo(Sequence):
|
|
_fields = [
|
|
('digest_algorithm', DigestAlgorithm),
|
|
('digest', OctetString),
|
|
]
|
|
|
|
|
|
class MaskGenAlgorithmId(ObjectIdentifier):
|
|
_map = {
|
|
'1.2.840.113549.1.1.8': 'mgf1',
|
|
}
|
|
|
|
|
|
class MaskGenAlgorithm(Sequence):
|
|
_fields = [
|
|
('algorithm', MaskGenAlgorithmId),
|
|
('parameters', Any, {'optional': True}),
|
|
]
|
|
|
|
_oid_pair = ('algorithm', 'parameters')
|
|
_oid_specs = {
|
|
'mgf1': DigestAlgorithm
|
|
}
|
|
|
|
|
|
class TrailerField(Integer):
|
|
_map = {
|
|
1: 'trailer_field_bc',
|
|
}
|
|
|
|
|
|
class RSASSAPSSParams(Sequence):
|
|
_fields = [
|
|
(
|
|
'hash_algorithm',
|
|
DigestAlgorithm,
|
|
{
|
|
'explicit': 0,
|
|
'default': {'algorithm': 'sha1'},
|
|
}
|
|
),
|
|
(
|
|
'mask_gen_algorithm',
|
|
MaskGenAlgorithm,
|
|
{
|
|
'explicit': 1,
|
|
'default': {
|
|
'algorithm': 'mgf1',
|
|
'parameters': {'algorithm': 'sha1'},
|
|
},
|
|
}
|
|
),
|
|
(
|
|
'salt_length',
|
|
Integer,
|
|
{
|
|
'explicit': 2,
|
|
'default': 20,
|
|
}
|
|
),
|
|
(
|
|
'trailer_field',
|
|
TrailerField,
|
|
{
|
|
'explicit': 3,
|
|
'default': 'trailer_field_bc',
|
|
}
|
|
),
|
|
]
|
|
|
|
|
|
class SignedDigestAlgorithmId(ObjectIdentifier):
|
|
_map = {
|
|
'1.3.14.3.2.3': 'md5_rsa',
|
|
'1.3.14.3.2.29': 'sha1_rsa',
|
|
'1.3.14.7.2.3.1': 'md2_rsa',
|
|
'1.2.840.113549.1.1.2': 'md2_rsa',
|
|
'1.2.840.113549.1.1.4': 'md5_rsa',
|
|
'1.2.840.113549.1.1.5': 'sha1_rsa',
|
|
'1.2.840.113549.1.1.14': 'sha224_rsa',
|
|
'1.2.840.113549.1.1.11': 'sha256_rsa',
|
|
'1.2.840.113549.1.1.12': 'sha384_rsa',
|
|
'1.2.840.113549.1.1.13': 'sha512_rsa',
|
|
'1.2.840.113549.1.1.10': 'rsassa_pss',
|
|
'1.2.840.10040.4.3': 'sha1_dsa',
|
|
'1.3.14.3.2.13': 'sha1_dsa',
|
|
'1.3.14.3.2.27': 'sha1_dsa',
|
|
'2.16.840.1.101.3.4.3.1': 'sha224_dsa',
|
|
'2.16.840.1.101.3.4.3.2': 'sha256_dsa',
|
|
'1.2.840.10045.4.1': 'sha1_ecdsa',
|
|
'1.2.840.10045.4.3.1': 'sha224_ecdsa',
|
|
'1.2.840.10045.4.3.2': 'sha256_ecdsa',
|
|
'1.2.840.10045.4.3.3': 'sha384_ecdsa',
|
|
'1.2.840.10045.4.3.4': 'sha512_ecdsa',
|
|
# For when the digest is specified elsewhere in a Sequence
|
|
'1.2.840.113549.1.1.1': 'rsassa_pkcs1v15',
|
|
'1.2.840.10040.4.1': 'dsa',
|
|
'1.2.840.10045.4': 'ecdsa',
|
|
}
|
|
|
|
_reverse_map = {
|
|
'dsa': '1.2.840.10040.4.1',
|
|
'ecdsa': '1.2.840.10045.4',
|
|
'md2_rsa': '1.2.840.113549.1.1.2',
|
|
'md5_rsa': '1.2.840.113549.1.1.4',
|
|
'rsassa_pkcs1v15': '1.2.840.113549.1.1.1',
|
|
'rsassa_pss': '1.2.840.113549.1.1.10',
|
|
'sha1_dsa': '1.2.840.10040.4.3',
|
|
'sha1_ecdsa': '1.2.840.10045.4.1',
|
|
'sha1_rsa': '1.2.840.113549.1.1.5',
|
|
'sha224_dsa': '2.16.840.1.101.3.4.3.1',
|
|
'sha224_ecdsa': '1.2.840.10045.4.3.1',
|
|
'sha224_rsa': '1.2.840.113549.1.1.14',
|
|
'sha256_dsa': '2.16.840.1.101.3.4.3.2',
|
|
'sha256_ecdsa': '1.2.840.10045.4.3.2',
|
|
'sha256_rsa': '1.2.840.113549.1.1.11',
|
|
'sha384_ecdsa': '1.2.840.10045.4.3.3',
|
|
'sha384_rsa': '1.2.840.113549.1.1.12',
|
|
'sha512_ecdsa': '1.2.840.10045.4.3.4',
|
|
'sha512_rsa': '1.2.840.113549.1.1.13',
|
|
}
|
|
|
|
|
|
class SignedDigestAlgorithm(_ForceNullParameters, Sequence):
|
|
_fields = [
|
|
('algorithm', SignedDigestAlgorithmId),
|
|
('parameters', Any, {'optional': True}),
|
|
]
|
|
|
|
_oid_pair = ('algorithm', 'parameters')
|
|
_oid_specs = {
|
|
'rsassa_pss': RSASSAPSSParams,
|
|
}
|
|
|
|
@property
|
|
def signature_algo(self):
|
|
"""
|
|
:return:
|
|
A unicode string of "rsassa_pkcs1v15", "rsassa_pss", "dsa" or
|
|
"ecdsa"
|
|
"""
|
|
|
|
algorithm = self['algorithm'].native
|
|
|
|
algo_map = {
|
|
'md2_rsa': 'rsassa_pkcs1v15',
|
|
'md5_rsa': 'rsassa_pkcs1v15',
|
|
'sha1_rsa': 'rsassa_pkcs1v15',
|
|
'sha224_rsa': 'rsassa_pkcs1v15',
|
|
'sha256_rsa': 'rsassa_pkcs1v15',
|
|
'sha384_rsa': 'rsassa_pkcs1v15',
|
|
'sha512_rsa': 'rsassa_pkcs1v15',
|
|
'rsassa_pkcs1v15': 'rsassa_pkcs1v15',
|
|
'rsassa_pss': 'rsassa_pss',
|
|
'sha1_dsa': 'dsa',
|
|
'sha224_dsa': 'dsa',
|
|
'sha256_dsa': 'dsa',
|
|
'dsa': 'dsa',
|
|
'sha1_ecdsa': 'ecdsa',
|
|
'sha224_ecdsa': 'ecdsa',
|
|
'sha256_ecdsa': 'ecdsa',
|
|
'sha384_ecdsa': 'ecdsa',
|
|
'sha512_ecdsa': 'ecdsa',
|
|
'ecdsa': 'ecdsa',
|
|
}
|
|
if algorithm in algo_map:
|
|
return algo_map[algorithm]
|
|
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Signature algorithm not known for %s
|
|
''',
|
|
algorithm
|
|
))
|
|
|
|
@property
|
|
def hash_algo(self):
|
|
"""
|
|
:return:
|
|
A unicode string of "md2", "md5", "sha1", "sha224", "sha256",
|
|
"sha384", "sha512", "sha512_224", "sha512_256"
|
|
"""
|
|
|
|
algorithm = self['algorithm'].native
|
|
|
|
algo_map = {
|
|
'md2_rsa': 'md2',
|
|
'md5_rsa': 'md5',
|
|
'sha1_rsa': 'sha1',
|
|
'sha224_rsa': 'sha224',
|
|
'sha256_rsa': 'sha256',
|
|
'sha384_rsa': 'sha384',
|
|
'sha512_rsa': 'sha512',
|
|
'sha1_dsa': 'sha1',
|
|
'sha224_dsa': 'sha224',
|
|
'sha256_dsa': 'sha256',
|
|
'sha1_ecdsa': 'sha1',
|
|
'sha224_ecdsa': 'sha224',
|
|
'sha256_ecdsa': 'sha256',
|
|
'sha384_ecdsa': 'sha384',
|
|
'sha512_ecdsa': 'sha512',
|
|
}
|
|
if algorithm in algo_map:
|
|
return algo_map[algorithm]
|
|
|
|
if algorithm == 'rsassa_pss':
|
|
return self['parameters']['hash_algorithm']['algorithm'].native
|
|
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Hash algorithm not known for %s
|
|
''',
|
|
algorithm
|
|
))
|
|
|
|
|
|
class Pbkdf2Salt(Choice):
|
|
_alternatives = [
|
|
('specified', OctetString),
|
|
('other_source', AlgorithmIdentifier),
|
|
]
|
|
|
|
|
|
class Pbkdf2Params(Sequence):
|
|
_fields = [
|
|
('salt', Pbkdf2Salt),
|
|
('iteration_count', Integer),
|
|
('key_length', Integer, {'optional': True}),
|
|
('prf', HmacAlgorithm, {'default': {'algorithm': 'sha1'}}),
|
|
]
|
|
|
|
|
|
class KdfAlgorithmId(ObjectIdentifier):
|
|
_map = {
|
|
'1.2.840.113549.1.5.12': 'pbkdf2'
|
|
}
|
|
|
|
|
|
class KdfAlgorithm(Sequence):
|
|
_fields = [
|
|
('algorithm', KdfAlgorithmId),
|
|
('parameters', Any, {'optional': True}),
|
|
]
|
|
_oid_pair = ('algorithm', 'parameters')
|
|
_oid_specs = {
|
|
'pbkdf2': Pbkdf2Params
|
|
}
|
|
|
|
|
|
class DHParameters(Sequence):
|
|
"""
|
|
Original Name: DHParameter
|
|
Source: ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-3.asc section 9
|
|
"""
|
|
|
|
_fields = [
|
|
('p', Integer),
|
|
('g', Integer),
|
|
('private_value_length', Integer, {'optional': True}),
|
|
]
|
|
|
|
|
|
class KeyExchangeAlgorithmId(ObjectIdentifier):
|
|
_map = {
|
|
'1.2.840.113549.1.3.1': 'dh',
|
|
}
|
|
|
|
|
|
class KeyExchangeAlgorithm(Sequence):
|
|
_fields = [
|
|
('algorithm', KeyExchangeAlgorithmId),
|
|
('parameters', Any, {'optional': True}),
|
|
]
|
|
_oid_pair = ('algorithm', 'parameters')
|
|
_oid_specs = {
|
|
'dh': DHParameters,
|
|
}
|
|
|
|
|
|
class Rc2Params(Sequence):
|
|
_fields = [
|
|
('rc2_parameter_version', Integer, {'optional': True}),
|
|
('iv', OctetString),
|
|
]
|
|
|
|
|
|
class Rc5ParamVersion(Integer):
|
|
_map = {
|
|
16: 'v1-0'
|
|
}
|
|
|
|
|
|
class Rc5Params(Sequence):
|
|
_fields = [
|
|
('version', Rc5ParamVersion),
|
|
('rounds', Integer),
|
|
('block_size_in_bits', Integer),
|
|
('iv', OctetString, {'optional': True}),
|
|
]
|
|
|
|
|
|
class Pbes1Params(Sequence):
|
|
_fields = [
|
|
('salt', OctetString),
|
|
('iterations', Integer),
|
|
]
|
|
|
|
|
|
class PSourceAlgorithmId(ObjectIdentifier):
|
|
_map = {
|
|
'1.2.840.113549.1.1.9': 'p_specified',
|
|
}
|
|
|
|
|
|
class PSourceAlgorithm(Sequence):
|
|
_fields = [
|
|
('algorithm', PSourceAlgorithmId),
|
|
('parameters', Any, {'optional': True}),
|
|
]
|
|
|
|
_oid_pair = ('algorithm', 'parameters')
|
|
_oid_specs = {
|
|
'p_specified': OctetString
|
|
}
|
|
|
|
|
|
class RSAESOAEPParams(Sequence):
|
|
_fields = [
|
|
(
|
|
'hash_algorithm',
|
|
DigestAlgorithm,
|
|
{
|
|
'explicit': 0,
|
|
'default': {'algorithm': 'sha1'}
|
|
}
|
|
),
|
|
(
|
|
'mask_gen_algorithm',
|
|
MaskGenAlgorithm,
|
|
{
|
|
'explicit': 1,
|
|
'default': {
|
|
'algorithm': 'mgf1',
|
|
'parameters': {'algorithm': 'sha1'}
|
|
}
|
|
}
|
|
),
|
|
(
|
|
'p_source_algorithm',
|
|
PSourceAlgorithm,
|
|
{
|
|
'explicit': 2,
|
|
'default': {
|
|
'algorithm': 'p_specified',
|
|
'parameters': b''
|
|
}
|
|
}
|
|
),
|
|
]
|
|
|
|
|
|
class DSASignature(Sequence):
|
|
"""
|
|
An ASN.1 class for translating between the OS crypto library's
|
|
representation of an (EC)DSA signature and the ASN.1 structure that is part
|
|
of various RFCs.
|
|
|
|
Original Name: DSS-Sig-Value
|
|
Source: https://tools.ietf.org/html/rfc3279#section-2.2.2
|
|
"""
|
|
|
|
_fields = [
|
|
('r', Integer),
|
|
('s', Integer),
|
|
]
|
|
|
|
@classmethod
|
|
def from_p1363(cls, data):
|
|
"""
|
|
Reads a signature from a byte string encoding accordint to IEEE P1363,
|
|
which is used by Microsoft's BCryptSignHash() function.
|
|
|
|
:param data:
|
|
A byte string from BCryptSignHash()
|
|
|
|
:return:
|
|
A DSASignature object
|
|
"""
|
|
|
|
r = int_from_bytes(data[0:len(data) // 2])
|
|
s = int_from_bytes(data[len(data) // 2:])
|
|
return cls({'r': r, 's': s})
|
|
|
|
def to_p1363(self):
|
|
"""
|
|
Dumps a signature to a byte string compatible with Microsoft's
|
|
BCryptVerifySignature() function.
|
|
|
|
:return:
|
|
A byte string compatible with BCryptVerifySignature()
|
|
"""
|
|
|
|
r_bytes = int_to_bytes(self['r'].native)
|
|
s_bytes = int_to_bytes(self['s'].native)
|
|
|
|
int_byte_length = max(len(r_bytes), len(s_bytes))
|
|
r_bytes = fill_width(r_bytes, int_byte_length)
|
|
s_bytes = fill_width(s_bytes, int_byte_length)
|
|
|
|
return r_bytes + s_bytes
|
|
|
|
|
|
class EncryptionAlgorithmId(ObjectIdentifier):
|
|
_map = {
|
|
'1.3.14.3.2.7': 'des',
|
|
'1.2.840.113549.3.7': 'tripledes_3key',
|
|
'1.2.840.113549.3.2': 'rc2',
|
|
'1.2.840.113549.3.9': 'rc5',
|
|
# From http://csrc.nist.gov/groups/ST/crypto_apps_infra/csor/algorithms.html#AES
|
|
'2.16.840.1.101.3.4.1.1': 'aes128_ecb',
|
|
'2.16.840.1.101.3.4.1.2': 'aes128_cbc',
|
|
'2.16.840.1.101.3.4.1.3': 'aes128_ofb',
|
|
'2.16.840.1.101.3.4.1.4': 'aes128_cfb',
|
|
'2.16.840.1.101.3.4.1.5': 'aes128_wrap',
|
|
'2.16.840.1.101.3.4.1.6': 'aes128_gcm',
|
|
'2.16.840.1.101.3.4.1.7': 'aes128_ccm',
|
|
'2.16.840.1.101.3.4.1.8': 'aes128_wrap_pad',
|
|
'2.16.840.1.101.3.4.1.21': 'aes192_ecb',
|
|
'2.16.840.1.101.3.4.1.22': 'aes192_cbc',
|
|
'2.16.840.1.101.3.4.1.23': 'aes192_ofb',
|
|
'2.16.840.1.101.3.4.1.24': 'aes192_cfb',
|
|
'2.16.840.1.101.3.4.1.25': 'aes192_wrap',
|
|
'2.16.840.1.101.3.4.1.26': 'aes192_gcm',
|
|
'2.16.840.1.101.3.4.1.27': 'aes192_ccm',
|
|
'2.16.840.1.101.3.4.1.28': 'aes192_wrap_pad',
|
|
'2.16.840.1.101.3.4.1.41': 'aes256_ecb',
|
|
'2.16.840.1.101.3.4.1.42': 'aes256_cbc',
|
|
'2.16.840.1.101.3.4.1.43': 'aes256_ofb',
|
|
'2.16.840.1.101.3.4.1.44': 'aes256_cfb',
|
|
'2.16.840.1.101.3.4.1.45': 'aes256_wrap',
|
|
'2.16.840.1.101.3.4.1.46': 'aes256_gcm',
|
|
'2.16.840.1.101.3.4.1.47': 'aes256_ccm',
|
|
'2.16.840.1.101.3.4.1.48': 'aes256_wrap_pad',
|
|
# From PKCS#5
|
|
'1.2.840.113549.1.5.13': 'pbes2',
|
|
'1.2.840.113549.1.5.1': 'pbes1_md2_des',
|
|
'1.2.840.113549.1.5.3': 'pbes1_md5_des',
|
|
'1.2.840.113549.1.5.4': 'pbes1_md2_rc2',
|
|
'1.2.840.113549.1.5.6': 'pbes1_md5_rc2',
|
|
'1.2.840.113549.1.5.10': 'pbes1_sha1_des',
|
|
'1.2.840.113549.1.5.11': 'pbes1_sha1_rc2',
|
|
# From PKCS#12
|
|
'1.2.840.113549.1.12.1.1': 'pkcs12_sha1_rc4_128',
|
|
'1.2.840.113549.1.12.1.2': 'pkcs12_sha1_rc4_40',
|
|
'1.2.840.113549.1.12.1.3': 'pkcs12_sha1_tripledes_3key',
|
|
'1.2.840.113549.1.12.1.4': 'pkcs12_sha1_tripledes_2key',
|
|
'1.2.840.113549.1.12.1.5': 'pkcs12_sha1_rc2_128',
|
|
'1.2.840.113549.1.12.1.6': 'pkcs12_sha1_rc2_40',
|
|
# PKCS#1 v2.2
|
|
'1.2.840.113549.1.1.1': 'rsaes_pkcs1v15',
|
|
'1.2.840.113549.1.1.7': 'rsaes_oaep',
|
|
}
|
|
|
|
|
|
class EncryptionAlgorithm(_ForceNullParameters, Sequence):
|
|
_fields = [
|
|
('algorithm', EncryptionAlgorithmId),
|
|
('parameters', Any, {'optional': True}),
|
|
]
|
|
|
|
_oid_pair = ('algorithm', 'parameters')
|
|
_oid_specs = {
|
|
'des': OctetString,
|
|
'tripledes_3key': OctetString,
|
|
'rc2': Rc2Params,
|
|
'rc5': Rc5Params,
|
|
'aes128_cbc': OctetString,
|
|
'aes192_cbc': OctetString,
|
|
'aes256_cbc': OctetString,
|
|
'aes128_ofb': OctetString,
|
|
'aes192_ofb': OctetString,
|
|
'aes256_ofb': OctetString,
|
|
# From PKCS#5
|
|
'pbes1_md2_des': Pbes1Params,
|
|
'pbes1_md5_des': Pbes1Params,
|
|
'pbes1_md2_rc2': Pbes1Params,
|
|
'pbes1_md5_rc2': Pbes1Params,
|
|
'pbes1_sha1_des': Pbes1Params,
|
|
'pbes1_sha1_rc2': Pbes1Params,
|
|
# From PKCS#12
|
|
'pkcs12_sha1_rc4_128': Pbes1Params,
|
|
'pkcs12_sha1_rc4_40': Pbes1Params,
|
|
'pkcs12_sha1_tripledes_3key': Pbes1Params,
|
|
'pkcs12_sha1_tripledes_2key': Pbes1Params,
|
|
'pkcs12_sha1_rc2_128': Pbes1Params,
|
|
'pkcs12_sha1_rc2_40': Pbes1Params,
|
|
# PKCS#1 v2.2
|
|
'rsaes_oaep': RSAESOAEPParams,
|
|
}
|
|
|
|
@property
|
|
def kdf(self):
|
|
"""
|
|
Returns the name of the key derivation function to use.
|
|
|
|
:return:
|
|
A unicode from of one of the following: "pbkdf1", "pbkdf2",
|
|
"pkcs12_kdf"
|
|
"""
|
|
|
|
encryption_algo = self['algorithm'].native
|
|
|
|
if encryption_algo == 'pbes2':
|
|
return self['parameters']['key_derivation_func']['algorithm'].native
|
|
|
|
if encryption_algo.find('.') == -1:
|
|
if encryption_algo.find('_') != -1:
|
|
encryption_algo, _ = encryption_algo.split('_', 1)
|
|
|
|
if encryption_algo == 'pbes1':
|
|
return 'pbkdf1'
|
|
|
|
if encryption_algo == 'pkcs12':
|
|
return 'pkcs12_kdf'
|
|
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Encryption algorithm "%s" does not have a registered key
|
|
derivation function
|
|
''',
|
|
encryption_algo
|
|
))
|
|
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Unrecognized encryption algorithm "%s", can not determine key
|
|
derivation function
|
|
''',
|
|
encryption_algo
|
|
))
|
|
|
|
@property
|
|
def kdf_hmac(self):
|
|
"""
|
|
Returns the HMAC algorithm to use with the KDF.
|
|
|
|
:return:
|
|
A unicode string of one of the following: "md2", "md5", "sha1",
|
|
"sha224", "sha256", "sha384", "sha512"
|
|
"""
|
|
|
|
encryption_algo = self['algorithm'].native
|
|
|
|
if encryption_algo == 'pbes2':
|
|
return self['parameters']['key_derivation_func']['parameters']['prf']['algorithm'].native
|
|
|
|
if encryption_algo.find('.') == -1:
|
|
if encryption_algo.find('_') != -1:
|
|
_, hmac_algo, _ = encryption_algo.split('_', 2)
|
|
return hmac_algo
|
|
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Encryption algorithm "%s" does not have a registered key
|
|
derivation function
|
|
''',
|
|
encryption_algo
|
|
))
|
|
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Unrecognized encryption algorithm "%s", can not determine key
|
|
derivation hmac algorithm
|
|
''',
|
|
encryption_algo
|
|
))
|
|
|
|
@property
|
|
def kdf_salt(self):
|
|
"""
|
|
Returns the byte string to use as the salt for the KDF.
|
|
|
|
:return:
|
|
A byte string
|
|
"""
|
|
|
|
encryption_algo = self['algorithm'].native
|
|
|
|
if encryption_algo == 'pbes2':
|
|
salt = self['parameters']['key_derivation_func']['parameters']['salt']
|
|
|
|
if salt.name == 'other_source':
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Can not determine key derivation salt - the
|
|
reserved-for-future-use other source salt choice was
|
|
specified in the PBKDF2 params structure
|
|
'''
|
|
))
|
|
|
|
return salt.native
|
|
|
|
if encryption_algo.find('.') == -1:
|
|
if encryption_algo.find('_') != -1:
|
|
return self['parameters']['salt'].native
|
|
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Encryption algorithm "%s" does not have a registered key
|
|
derivation function
|
|
''',
|
|
encryption_algo
|
|
))
|
|
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Unrecognized encryption algorithm "%s", can not determine key
|
|
derivation salt
|
|
''',
|
|
encryption_algo
|
|
))
|
|
|
|
@property
|
|
def kdf_iterations(self):
|
|
"""
|
|
Returns the number of iterations that should be run via the KDF.
|
|
|
|
:return:
|
|
An integer
|
|
"""
|
|
|
|
encryption_algo = self['algorithm'].native
|
|
|
|
if encryption_algo == 'pbes2':
|
|
return self['parameters']['key_derivation_func']['parameters']['iteration_count'].native
|
|
|
|
if encryption_algo.find('.') == -1:
|
|
if encryption_algo.find('_') != -1:
|
|
return self['parameters']['iterations'].native
|
|
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Encryption algorithm "%s" does not have a registered key
|
|
derivation function
|
|
''',
|
|
encryption_algo
|
|
))
|
|
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Unrecognized encryption algorithm "%s", can not determine key
|
|
derivation iterations
|
|
''',
|
|
encryption_algo
|
|
))
|
|
|
|
@property
|
|
def key_length(self):
|
|
"""
|
|
Returns the key length to pass to the cipher/kdf. The PKCS#5 spec does
|
|
not specify a way to store the RC5 key length, however this tends not
|
|
to be a problem since OpenSSL does not support RC5 in PKCS#8 and OS X
|
|
does not provide an RC5 cipher for use in the Security Transforms
|
|
library.
|
|
|
|
:raises:
|
|
ValueError - when the key length can not be determined
|
|
|
|
:return:
|
|
An integer representing the length in bytes
|
|
"""
|
|
|
|
encryption_algo = self['algorithm'].native
|
|
|
|
if encryption_algo[0:3] == 'aes':
|
|
return {
|
|
'aes128_': 16,
|
|
'aes192_': 24,
|
|
'aes256_': 32,
|
|
}[encryption_algo[0:7]]
|
|
|
|
cipher_lengths = {
|
|
'des': 8,
|
|
'tripledes_3key': 24,
|
|
}
|
|
|
|
if encryption_algo in cipher_lengths:
|
|
return cipher_lengths[encryption_algo]
|
|
|
|
if encryption_algo == 'rc2':
|
|
rc2_params = self['parameters'].parsed['encryption_scheme']['parameters'].parsed
|
|
rc2_parameter_version = rc2_params['rc2_parameter_version'].native
|
|
|
|
# See page 24 of
|
|
# http://www.emc.com/collateral/white-papers/h11302-pkcs5v2-1-password-based-cryptography-standard-wp.pdf
|
|
encoded_key_bits_map = {
|
|
160: 5, # 40-bit
|
|
120: 8, # 64-bit
|
|
58: 16, # 128-bit
|
|
}
|
|
|
|
if rc2_parameter_version in encoded_key_bits_map:
|
|
return encoded_key_bits_map[rc2_parameter_version]
|
|
|
|
if rc2_parameter_version >= 256:
|
|
return rc2_parameter_version
|
|
|
|
if rc2_parameter_version is None:
|
|
return 4 # 32-bit default
|
|
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Invalid RC2 parameter version found in EncryptionAlgorithm
|
|
parameters
|
|
'''
|
|
))
|
|
|
|
if encryption_algo == 'pbes2':
|
|
key_length = self['parameters']['key_derivation_func']['parameters']['key_length'].native
|
|
if key_length is not None:
|
|
return key_length
|
|
|
|
# If the KDF params don't specify the key size, we can infer it from
|
|
# the encryption scheme for all schemes except for RC5. However, in
|
|
# practical terms, neither OpenSSL or OS X support RC5 for PKCS#8
|
|
# so it is unlikely to be an issue that is run into.
|
|
|
|
return self['parameters']['encryption_scheme'].key_length
|
|
|
|
if encryption_algo.find('.') == -1:
|
|
return {
|
|
'pbes1_md2_des': 8,
|
|
'pbes1_md5_des': 8,
|
|
'pbes1_md2_rc2': 8,
|
|
'pbes1_md5_rc2': 8,
|
|
'pbes1_sha1_des': 8,
|
|
'pbes1_sha1_rc2': 8,
|
|
'pkcs12_sha1_rc4_128': 16,
|
|
'pkcs12_sha1_rc4_40': 5,
|
|
'pkcs12_sha1_tripledes_3key': 24,
|
|
'pkcs12_sha1_tripledes_2key': 16,
|
|
'pkcs12_sha1_rc2_128': 16,
|
|
'pkcs12_sha1_rc2_40': 5,
|
|
}[encryption_algo]
|
|
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Unrecognized encryption algorithm "%s"
|
|
''',
|
|
encryption_algo
|
|
))
|
|
|
|
@property
|
|
def encryption_mode(self):
|
|
"""
|
|
Returns the name of the encryption mode to use.
|
|
|
|
:return:
|
|
A unicode string from one of the following: "cbc", "ecb", "ofb",
|
|
"cfb", "wrap", "gcm", "ccm", "wrap_pad"
|
|
"""
|
|
|
|
encryption_algo = self['algorithm'].native
|
|
|
|
if encryption_algo[0:7] in set(['aes128_', 'aes192_', 'aes256_']):
|
|
return encryption_algo[7:]
|
|
|
|
if encryption_algo[0:6] == 'pbes1_':
|
|
return 'cbc'
|
|
|
|
if encryption_algo[0:7] == 'pkcs12_':
|
|
return 'cbc'
|
|
|
|
if encryption_algo in set(['des', 'tripledes_3key', 'rc2', 'rc5']):
|
|
return 'cbc'
|
|
|
|
if encryption_algo == 'pbes2':
|
|
return self['parameters']['encryption_scheme'].encryption_mode
|
|
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Unrecognized encryption algorithm "%s"
|
|
''',
|
|
encryption_algo
|
|
))
|
|
|
|
@property
|
|
def encryption_cipher(self):
|
|
"""
|
|
Returns the name of the symmetric encryption cipher to use. The key
|
|
length can be retrieved via the .key_length property to disabiguate
|
|
between different variations of TripleDES, AES, and the RC* ciphers.
|
|
|
|
:return:
|
|
A unicode string from one of the following: "rc2", "rc5", "des",
|
|
"tripledes", "aes"
|
|
"""
|
|
|
|
encryption_algo = self['algorithm'].native
|
|
|
|
if encryption_algo[0:7] in set(['aes128_', 'aes192_', 'aes256_']):
|
|
return 'aes'
|
|
|
|
if encryption_algo in set(['des', 'rc2', 'rc5']):
|
|
return encryption_algo
|
|
|
|
if encryption_algo == 'tripledes_3key':
|
|
return 'tripledes'
|
|
|
|
if encryption_algo == 'pbes2':
|
|
return self['parameters']['encryption_scheme'].encryption_cipher
|
|
|
|
if encryption_algo.find('.') == -1:
|
|
return {
|
|
'pbes1_md2_des': 'des',
|
|
'pbes1_md5_des': 'des',
|
|
'pbes1_md2_rc2': 'rc2',
|
|
'pbes1_md5_rc2': 'rc2',
|
|
'pbes1_sha1_des': 'des',
|
|
'pbes1_sha1_rc2': 'rc2',
|
|
'pkcs12_sha1_rc4_128': 'rc4',
|
|
'pkcs12_sha1_rc4_40': 'rc4',
|
|
'pkcs12_sha1_tripledes_3key': 'tripledes',
|
|
'pkcs12_sha1_tripledes_2key': 'tripledes',
|
|
'pkcs12_sha1_rc2_128': 'rc2',
|
|
'pkcs12_sha1_rc2_40': 'rc2',
|
|
}[encryption_algo]
|
|
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Unrecognized encryption algorithm "%s"
|
|
''',
|
|
encryption_algo
|
|
))
|
|
|
|
@property
|
|
def encryption_block_size(self):
|
|
"""
|
|
Returns the block size of the encryption cipher, in bytes.
|
|
|
|
:return:
|
|
An integer that is the block size in bytes
|
|
"""
|
|
|
|
encryption_algo = self['algorithm'].native
|
|
|
|
if encryption_algo[0:7] in set(['aes128_', 'aes192_', 'aes256_']):
|
|
return 16
|
|
|
|
cipher_map = {
|
|
'des': 8,
|
|
'tripledes_3key': 8,
|
|
'rc2': 8,
|
|
}
|
|
if encryption_algo in cipher_map:
|
|
return cipher_map[encryption_algo]
|
|
|
|
if encryption_algo == 'rc5':
|
|
return self['parameters'].parsed['block_size_in_bits'].native / 8
|
|
|
|
if encryption_algo == 'pbes2':
|
|
return self['parameters']['encryption_scheme'].encryption_block_size
|
|
|
|
if encryption_algo.find('.') == -1:
|
|
return {
|
|
'pbes1_md2_des': 8,
|
|
'pbes1_md5_des': 8,
|
|
'pbes1_md2_rc2': 8,
|
|
'pbes1_md5_rc2': 8,
|
|
'pbes1_sha1_des': 8,
|
|
'pbes1_sha1_rc2': 8,
|
|
'pkcs12_sha1_rc4_128': 0,
|
|
'pkcs12_sha1_rc4_40': 0,
|
|
'pkcs12_sha1_tripledes_3key': 8,
|
|
'pkcs12_sha1_tripledes_2key': 8,
|
|
'pkcs12_sha1_rc2_128': 8,
|
|
'pkcs12_sha1_rc2_40': 8,
|
|
}[encryption_algo]
|
|
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Unrecognized encryption algorithm "%s"
|
|
''',
|
|
encryption_algo
|
|
))
|
|
|
|
@property
|
|
def encryption_iv(self):
|
|
"""
|
|
Returns the byte string of the initialization vector for the encryption
|
|
scheme. Only the PBES2 stores the IV in the params. For PBES1, the IV
|
|
is derived from the KDF and this property will return None.
|
|
|
|
:return:
|
|
A byte string or None
|
|
"""
|
|
|
|
encryption_algo = self['algorithm'].native
|
|
|
|
if encryption_algo in set(['rc2', 'rc5']):
|
|
return self['parameters'].parsed['iv'].native
|
|
|
|
# For DES/Triple DES and AES the IV is the entirety of the parameters
|
|
octet_string_iv_oids = set([
|
|
'des',
|
|
'tripledes_3key',
|
|
'aes128_cbc',
|
|
'aes192_cbc',
|
|
'aes256_cbc',
|
|
'aes128_ofb',
|
|
'aes192_ofb',
|
|
'aes256_ofb',
|
|
])
|
|
if encryption_algo in octet_string_iv_oids:
|
|
return self['parameters'].native
|
|
|
|
if encryption_algo == 'pbes2':
|
|
return self['parameters']['encryption_scheme'].encryption_iv
|
|
|
|
# All of the PBES1 algos use their KDF to create the IV. For the pbkdf1,
|
|
# the KDF is told to generate a key that is an extra 8 bytes long, and
|
|
# that is used for the IV. For the PKCS#12 KDF, it is called with an id
|
|
# of 2 to generate the IV. In either case, we can't return the IV
|
|
# without knowing the user's password.
|
|
if encryption_algo.find('.') == -1:
|
|
return None
|
|
|
|
raise ValueError(unwrap(
|
|
'''
|
|
Unrecognized encryption algorithm "%s"
|
|
''',
|
|
encryption_algo
|
|
))
|
|
|
|
|
|
class Pbes2Params(Sequence):
|
|
_fields = [
|
|
('key_derivation_func', KdfAlgorithm),
|
|
('encryption_scheme', EncryptionAlgorithm),
|
|
]
|
|
|
|
|
|
class Pbmac1Params(Sequence):
|
|
_fields = [
|
|
('key_derivation_func', KdfAlgorithm),
|
|
('message_auth_scheme', HmacAlgorithm),
|
|
]
|
|
|
|
|
|
class Pkcs5MacId(ObjectIdentifier):
|
|
_map = {
|
|
'1.2.840.113549.1.5.14': 'pbmac1',
|
|
}
|
|
|
|
|
|
class Pkcs5MacAlgorithm(Sequence):
|
|
_fields = [
|
|
('algorithm', Pkcs5MacId),
|
|
('parameters', Any),
|
|
]
|
|
|
|
_oid_pair = ('algorithm', 'parameters')
|
|
_oid_specs = {
|
|
'pbmac1': Pbmac1Params,
|
|
}
|
|
|
|
|
|
EncryptionAlgorithm._oid_specs['pbes2'] = Pbes2Params
|
|
|
|
|
|
class AnyAlgorithmId(ObjectIdentifier):
|
|
_map = {}
|
|
|
|
def _setup(self):
|
|
_map = self.__class__._map
|
|
for other_cls in (EncryptionAlgorithmId, SignedDigestAlgorithmId, DigestAlgorithmId):
|
|
for oid, name in other_cls._map.items():
|
|
_map[oid] = name
|
|
|
|
|
|
class AnyAlgorithmIdentifier(_ForceNullParameters, Sequence):
|
|
_fields = [
|
|
('algorithm', AnyAlgorithmId),
|
|
('parameters', Any, {'optional': True}),
|
|
]
|
|
|
|
_oid_pair = ('algorithm', 'parameters')
|
|
_oid_specs = {}
|
|
|
|
def _setup(self):
|
|
Sequence._setup(self)
|
|
specs = self.__class__._oid_specs
|
|
for other_cls in (EncryptionAlgorithm, SignedDigestAlgorithm):
|
|
for oid, spec in other_cls._oid_specs.items():
|
|
specs[oid] = spec
|