diff -Nru asn1crypto-0.22.0/asn1crypto/algos.py asn1crypto-0.24.0/asn1crypto/algos.py --- asn1crypto-0.22.0/asn1crypto/algos.py 2017-01-23 16:50:58.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto/algos.py 2017-11-22 16:10:10.000000000 +0000 @@ -5,6 +5,7 @@ key cryptography. Exports the following items: - AlgorithmIdentifier() + - AnyAlgorithmIdentifier() - DigestAlgorithm() - DigestInfo() - DSASignature() @@ -182,8 +183,7 @@ 'hash_algorithm', DigestAlgorithm, { - 'tag_type': 'explicit', - 'tag': 0, + 'explicit': 0, 'default': {'algorithm': 'sha1'}, } ), @@ -191,8 +191,7 @@ 'mask_gen_algorithm', MaskGenAlgorithm, { - 'tag_type': 'explicit', - 'tag': 1, + 'explicit': 1, 'default': { 'algorithm': 'mgf1', 'parameters': {'algorithm': 'sha1'}, @@ -203,8 +202,7 @@ 'salt_length', Integer, { - 'tag_type': 'explicit', - 'tag': 2, + 'explicit': 2, 'default': 20, } ), @@ -212,8 +210,7 @@ 'trailer_field', TrailerField, { - 'tag_type': 'explicit', - 'tag': 3, + 'explicit': 3, 'default': 'trailer_field_bc', } ), @@ -481,8 +478,7 @@ 'hash_algorithm', DigestAlgorithm, { - 'tag_type': 'explicit', - 'tag': 0, + 'explicit': 0, 'default': {'algorithm': 'sha1'} } ), @@ -490,8 +486,7 @@ 'mask_gen_algorithm', MaskGenAlgorithm, { - 'tag_type': 'explicit', - 'tag': 1, + 'explicit': 1, 'default': { 'algorithm': 'mgf1', 'parameters': {'algorithm': 'sha1'} @@ -502,8 +497,7 @@ 'p_source_algorithm', PSourceAlgorithm, { - 'tag_type': 'explicit', - 'tag': 2, + 'explicit': 2, 'default': { 'algorithm': 'p_specified', 'parameters': b'' @@ -1120,3 +1114,30 @@ 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 diff -Nru asn1crypto-0.22.0/asn1crypto/cms.py asn1crypto-0.24.0/asn1crypto/cms.py --- asn1crypto-0.22.0/asn1crypto/cms.py 2017-02-10 10:06:05.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto/cms.py 2017-11-20 12:22:36.000000000 +0000 @@ -15,6 +15,8 @@ - SignedData() Other type classes are defined that help compose the types listed above. + +Most CMS structures in the wild are formatted as ContentInfo encapsulating one of the other types. """ from __future__ import unicode_literals, division, absolute_import, print_function @@ -99,6 +101,8 @@ '1.2.840.113549.1.9.6': 'counter_signature', # https://tools.ietf.org/html/rfc3161#page-20 '1.2.840.113549.1.9.16.2.14': 'signature_time_stamp_token', + # https://tools.ietf.org/html/rfc6211#page-5 + '1.2.840.113549.1.9.52': 'cms_algorithm_protection', } @@ -123,6 +127,14 @@ } +class CMSAlgorithmProtection(Sequence): + _fields = [ + ('digest_algorithm', DigestAlgorithm), + ('signature_algorithm', SignedDigestAlgorithm, {'implicit': 1, 'optional': True}), + ('mac_algorithm', HmacAlgorithm, {'implicit': 2, 'optional': True}), + ] + + class SetOfContentType(SetOf): _child_spec = ContentType @@ -139,6 +151,10 @@ _child_spec = Any +class SetOfCMSAlgorithmProtection(SetOf): + _child_spec = CMSAlgorithmProtection + + class CMSAttribute(Sequence): _fields = [ ('type', CMSAttributeType), @@ -176,8 +192,8 @@ class AttCertSubject(Choice): _alternatives = [ - ('base_certificate_id', IssuerSerial, {'tag_type': 'explicit', 'tag': 0}), - ('subject_name', GeneralNames, {'tag_type': 'explicit', 'tag': 1}), + ('base_certificate_id', IssuerSerial, {'explicit': 0}), + ('subject_name', GeneralNames, {'explicit': 1}), ] @@ -229,24 +245,24 @@ class Holder(Sequence): _fields = [ - ('base_certificate_id', IssuerSerial, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), - ('entity_name', GeneralNames, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), - ('object_digest_info', ObjectDigestInfo, {'tag_type': 'implicit', 'tag': 2, 'optional': True}), + ('base_certificate_id', IssuerSerial, {'implicit': 0, 'optional': True}), + ('entity_name', GeneralNames, {'implicit': 1, 'optional': True}), + ('object_digest_info', ObjectDigestInfo, {'implicit': 2, 'optional': True}), ] class V2Form(Sequence): _fields = [ ('issuer_name', GeneralNames, {'optional': True}), - ('base_certificate_id', IssuerSerial, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), - ('object_digest_info', ObjectDigestInfo, {'tag_type': 'explicit', 'tag': 1, 'optional': True}), + ('base_certificate_id', IssuerSerial, {'explicit': 0, 'optional': True}), + ('object_digest_info', ObjectDigestInfo, {'explicit': 1, 'optional': True}), ] class AttCertIssuer(Choice): _alternatives = [ ('v1_form', GeneralNames), - ('v2_form', V2Form, {'tag_type': 'explicit', 'tag': 0}), + ('v2_form', V2Form, {'explicit': 0}), ] @@ -264,7 +280,7 @@ class IetfAttrSyntax(Sequence): _fields = [ - ('policy_authority', GeneralNames, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), + ('policy_authority', GeneralNames, {'implicit': 0, 'optional': True}), ('values', IetfAttrValues), ] @@ -287,8 +303,8 @@ class RoleSyntax(Sequence): _fields = [ - ('role_authority', GeneralNames, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), - ('role_name', GeneralName, {'tag_type': 'implicit', 'tag': 1}), + ('role_authority', GeneralNames, {'implicit': 0, 'optional': True}), + ('role_name', GeneralName, {'implicit': 1}), ] @@ -309,8 +325,8 @@ class SecurityCategory(Sequence): _fields = [ - ('type', ObjectIdentifier, {'tag_type': 'implicit', 'tag': 0}), - ('value', Any, {'tag_type': 'implicit', 'tag': 1}), + ('type', ObjectIdentifier, {'implicit': 0}), + ('value', Any, {'implicit': 1}), ] @@ -320,9 +336,9 @@ class Clearance(Sequence): _fields = [ - ('policy_id', ObjectIdentifier, {'tag_type': 'implicit', 'tag': 0}), - ('class_list', ClassList, {'tag_type': 'implicit', 'tag': 1, 'default': 'unclassified'}), - ('security_categories', SetOfSecurityCategory, {'tag_type': 'implicit', 'tag': 2, 'optional': True}), + ('policy_id', ObjectIdentifier, {'implicit': 0}), + ('class_list', ClassList, {'implicit': 1, 'default': 'unclassified'}), + ('security_categories', SetOfSecurityCategory, {'implicit': 2, 'optional': True}), ] @@ -366,8 +382,8 @@ class TimingPolicy(Sequence): _fields = [ ('policy_id', SequenceOf, {'spec': ObjectIdentifier}), - ('max_offset', BigTime, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), - ('max_delay', BigTime, {'tag_type': 'explicit', 'tag': 1, 'optional': True}), + ('max_offset', BigTime, {'explicit': 0, 'optional': True}), + ('max_delay', BigTime, {'explicit': 1, 'optional': True}), ] @@ -452,10 +468,10 @@ class CertificateChoices(Choice): _alternatives = [ ('certificate', Certificate), - ('extended_certificate', ExtendedCertificate, {'tag_type': 'implicit', 'tag': 0}), - ('v1_attr_cert', AttributeCertificateV1, {'tag_type': 'implicit', 'tag': 1}), - ('v2_attr_cert', AttributeCertificateV2, {'tag_type': 'implicit', 'tag': 2}), - ('other', OtherCertificateFormat, {'tag_type': 'implicit', 'tag': 3}), + ('extended_certificate', ExtendedCertificate, {'implicit': 0}), + ('v1_attr_cert', AttributeCertificateV1, {'implicit': 1}), + ('v2_attr_cert', AttributeCertificateV2, {'implicit': 2}), + ('other', OtherCertificateFormat, {'implicit': 3}), ] def validate(self, class_, tag, contents): @@ -491,7 +507,7 @@ class ContentInfo(Sequence): _fields = [ ('content_type', ContentType), - ('content', Any, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), + ('content', Any, {'explicit': 0, 'optional': True}), ] _oid_pair = ('content_type', 'content') @@ -505,7 +521,7 @@ class EncapsulatedContentInfo(Sequence): _fields = [ ('content_type', ContentType), - ('content', ParsableOctetString, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), + ('content', ParsableOctetString, {'explicit': 0, 'optional': True}), ] _oid_pair = ('content_type', 'content') @@ -522,7 +538,7 @@ class SignerIdentifier(Choice): _alternatives = [ ('issuer_and_serial_number', IssuerAndSerialNumber), - ('subject_key_identifier', OctetString, {'tag_type': 'implicit', 'tag': 0}), + ('subject_key_identifier', OctetString, {'implicit': 0}), ] @@ -536,7 +552,7 @@ class SCVPReqRes(Sequence): _fields = [ - ('request', ContentInfo, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), + ('request', ContentInfo, {'explicit': 0, 'optional': True}), ('response', ContentInfo), ] @@ -564,7 +580,7 @@ class RevocationInfoChoice(Choice): _alternatives = [ ('crl', CertificateList), - ('other', OtherRevocationInfoFormat, {'tag_type': 'implciit', 'tag': 1}), + ('other', OtherRevocationInfoFormat, {'implicit': 1}), ] @@ -577,10 +593,10 @@ ('version', CMSVersion), ('sid', SignerIdentifier), ('digest_algorithm', DigestAlgorithm), - ('signed_attrs', CMSAttributes, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), + ('signed_attrs', CMSAttributes, {'implicit': 0, 'optional': True}), ('signature_algorithm', SignedDigestAlgorithm), ('signature', OctetString), - ('unsigned_attrs', CMSAttributes, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('unsigned_attrs', CMSAttributes, {'implicit': 1, 'optional': True}), ] @@ -593,8 +609,8 @@ ('version', CMSVersion), ('digest_algorithms', DigestAlgorithms), ('encap_content_info', None), - ('certificates', CertificateSet, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), - ('crls', RevocationInfoChoices, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('certificates', CertificateSet, {'implicit': 0, 'optional': True}), + ('crls', RevocationInfoChoices, {'implicit': 1, 'optional': True}), ('signer_infos', SignerInfos), ] @@ -619,15 +635,15 @@ class OriginatorInfo(Sequence): _fields = [ - ('certs', CertificateSet, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), - ('crls', RevocationInfoChoices, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('certs', CertificateSet, {'implicit': 0, 'optional': True}), + ('crls', RevocationInfoChoices, {'implicit': 1, 'optional': True}), ] class RecipientIdentifier(Choice): _alternatives = [ ('issuer_and_serial_number', IssuerAndSerialNumber), - ('subject_key_identifier', OctetString, {'tag_type': 'implicit', 'tag': 0}), + ('subject_key_identifier', OctetString, {'implicit': 0}), ] @@ -662,8 +678,8 @@ class OriginatorIdentifierOrKey(Choice): _alternatives = [ ('issuer_and_serial_number', IssuerAndSerialNumber), - ('subject_key_identifier', OctetString, {'tag_type': 'implicit', 'tag': 0}), - ('originator_key', PublicKeyInfo, {'tag_type': 'implicit', 'tag': 1}), + ('subject_key_identifier', OctetString, {'implicit': 0}), + ('originator_key', PublicKeyInfo, {'implicit': 1}), ] @@ -685,7 +701,7 @@ class KeyAgreementRecipientIdentifier(Choice): _alternatives = [ ('issuer_and_serial_number', IssuerAndSerialNumber), - ('r_key_id', RecipientKeyIdentifier, {'tag_type': 'implicit', 'tag': 0}), + ('r_key_id', RecipientKeyIdentifier, {'implicit': 0}), ] @@ -703,8 +719,8 @@ class KeyAgreeRecipientInfo(Sequence): _fields = [ ('version', CMSVersion), - ('originator', OriginatorIdentifierOrKey, {'tag_type': 'explicit', 'tag': 0}), - ('ukm', OctetString, {'tag_type': 'explicit', 'tag': 1, 'optional': True}), + ('originator', OriginatorIdentifierOrKey, {'explicit': 0}), + ('ukm', OctetString, {'explicit': 1, 'optional': True}), ('key_encryption_algorithm', KeyEncryptionAlgorithm), ('recipient_encrypted_keys', RecipientEncryptedKeys), ] @@ -730,7 +746,7 @@ class PasswordRecipientInfo(Sequence): _fields = [ ('version', CMSVersion), - ('key_derivation_algorithm', KdfAlgorithm, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), + ('key_derivation_algorithm', KdfAlgorithm, {'implicit': 0, 'optional': True}), ('key_encryption_algorithm', KeyEncryptionAlgorithm), ('encrypted_key', OctetString), ] @@ -746,10 +762,10 @@ class RecipientInfo(Choice): _alternatives = [ ('ktri', KeyTransRecipientInfo), - ('kari', KeyAgreeRecipientInfo, {'tag_type': 'implicit', 'tag': 1}), - ('kekri', KEKRecipientInfo, {'tag_type': 'implicit', 'tag': 2}), - ('pwri', PasswordRecipientInfo, {'tag_type': 'implicit', 'tag': 3}), - ('ori', OtherRecipientInfo, {'tag_type': 'implicit', 'tag': 4}), + ('kari', KeyAgreeRecipientInfo, {'implicit': 1}), + ('kekri', KEKRecipientInfo, {'implicit': 2}), + ('pwri', PasswordRecipientInfo, {'implicit': 3}), + ('ori', OtherRecipientInfo, {'implicit': 4}), ] @@ -761,17 +777,17 @@ _fields = [ ('content_type', ContentType), ('content_encryption_algorithm', EncryptionAlgorithm), - ('encrypted_content', OctetString, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), + ('encrypted_content', OctetString, {'implicit': 0, 'optional': True}), ] class EnvelopedData(Sequence): _fields = [ ('version', CMSVersion), - ('originator_info', OriginatorInfo, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), + ('originator_info', OriginatorInfo, {'implicit': 0, 'optional': True}), ('recipient_infos', RecipientInfos), ('encrypted_content_info', EncryptedContentInfo), - ('unprotected_attrs', CMSAttributes, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('unprotected_attrs', CMSAttributes, {'implicit': 1, 'optional': True}), ] @@ -781,8 +797,8 @@ ('recipient_infos', RecipientInfos), ('digest_algorithms', DigestAlgorithms), ('encrypted_content_info', EncryptedContentInfo), - ('certificates', CertificateSet, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), - ('crls', CertificateRevocationLists, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('certificates', CertificateSet, {'implicit': 0, 'optional': True}), + ('crls', CertificateRevocationLists, {'implicit': 1, 'optional': True}), ('signer_infos', SignerInfos), ] @@ -818,35 +834,35 @@ _fields = [ ('version', CMSVersion), ('encrypted_content_info', EncryptedContentInfo), - ('unprotected_attrs', CMSAttributes, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('unprotected_attrs', CMSAttributes, {'implicit': 1, 'optional': True}), ] class AuthenticatedData(Sequence): _fields = [ ('version', CMSVersion), - ('originator_info', OriginatorInfo, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), + ('originator_info', OriginatorInfo, {'implicit': 0, 'optional': True}), ('recipient_infos', RecipientInfos), ('mac_algorithm', HmacAlgorithm), - ('digest_algorithm', DigestAlgorithm, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('digest_algorithm', DigestAlgorithm, {'implicit': 1, 'optional': True}), # This does not require the _spec_callbacks approach of SignedData and # DigestedData since AuthenticatedData was not part of PKCS#7 ('encap_content_info', EncapsulatedContentInfo), - ('auth_attrs', CMSAttributes, {'tag_type': 'implicit', 'tag': 2, 'optional': True}), + ('auth_attrs', CMSAttributes, {'implicit': 2, 'optional': True}), ('mac', OctetString), - ('unauth_attrs', CMSAttributes, {'tag_type': 'implicit', 'tag': 3, 'optional': True}), + ('unauth_attrs', CMSAttributes, {'implicit': 3, 'optional': True}), ] class AuthEnvelopedData(Sequence): _fields = [ ('version', CMSVersion), - ('originator_info', OriginatorInfo, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), + ('originator_info', OriginatorInfo, {'implicit': 0, 'optional': True}), ('recipient_infos', RecipientInfos), ('auth_encrypted_content_info', EncryptedContentInfo), - ('auth_attrs', CMSAttributes, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('auth_attrs', CMSAttributes, {'implicit': 1, 'optional': True}), ('mac', OctetString), - ('unauth_attrs', CMSAttributes, {'tag_type': 'implicit', 'tag': 2, 'optional': True}), + ('unauth_attrs', CMSAttributes, {'implicit': 2, 'optional': True}), ] @@ -912,4 +928,5 @@ 'signing_time': SetOfTime, 'counter_signature': SignerInfos, 'signature_time_stamp_token': SetOfContentInfo, + 'cms_algorithm_protection': SetOfCMSAlgorithmProtection, } diff -Nru asn1crypto-0.22.0/asn1crypto/core.py asn1crypto-0.24.0/asn1crypto/core.py --- asn1crypto-0.22.0/asn1crypto/core.py 2017-03-11 11:28:29.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto/core.py 2017-11-21 17:02:30.000000000 +0000 @@ -101,7 +101,7 @@ } -_OID_RE = re.compile('^\d+(\.\d+)*$') +_OID_RE = re.compile(r'^\d+(\.\d+)*$') # A global tracker to ensure that _setup() is called for every class, even @@ -183,13 +183,12 @@ # structures where a string value is encoded using an incorrect tag _bad_tag = None - # A unicode string or None - "explicit" or "implicit" for - # tagged values, None for normal - tag_type = None - - # If "explicit"ly tagged, the class and tag for the wrapped header - explicit_class = None - explicit_tag = None + # If the value has been implicitly tagged + implicit = False + + # If explicitly tagged, a tuple of 2-element tuples containing the + # class int and tag int, from innermost to outermost + explicit = None # The BER/DER header bytes _header = None @@ -230,15 +229,29 @@ value, _ = _parse_build(encoded_data, spec=spec, spec_params=kwargs, strict=strict) return value - def __init__(self, tag_type=None, class_=None, tag=None, optional=None, default=None, contents=None): + def __init__(self, explicit=None, implicit=None, no_explicit=False, tag_type=None, class_=None, tag=None, + optional=None, default=None, contents=None): """ The optional parameter is not used, but rather included so we don't have to delete it from the parameter dictionary when passing as keyword args + :param explicit: + An int tag number for explicit tagging, or a 2-element tuple of + class and tag. + + :param implicit: + An int tag number for implicit tagging, or a 2-element tuple of + class and tag. + + :param no_explicit: + If explicit tagging info should be removed from this instance. + Used internally to allow contructing the underlying value that + has been wrapped in an explicit tag. + :param tag_type: None for normal values, or one of "implicit", "explicit" for tagged - values + values. Deprecated in favor of explicit and implicit params. :param class_: The class for the value - defaults to "universal" if tag_type is @@ -247,10 +260,11 @@ - "application" - "context" - "private" + Deprecated in favor of explicit and implicit params. :param tag: The integer tag to override - usually this is used with tag_type or - class_ + class_. Deprecated in favor of explicit and implicit params. :param optional: Dummy parameter that allows "optional" key in spec param dicts @@ -262,53 +276,112 @@ A byte string of the encoded contents of the value :raises: - ValueError - when tag_type, class_ or tag are invalid values + ValueError - when implicit, explicit, tag_type, class_ or tag are invalid values """ try: if self.__class__ not in _SETUP_CLASSES: cls = self.__class__ + # Allow explicit to be specified as a simple 2-element tuple + # instead of requiring the user make a nested tuple + if cls.explicit is not None and isinstance(cls.explicit[0], int_types): + cls.explicit = (cls.explicit, ) if hasattr(cls, '_setup'): self._setup() _SETUP_CLASSES[cls] = True + # Normalize tagging values + if explicit is not None: + if isinstance(explicit, int_types): + if class_ is None: + class_ = 'context' + explicit = (class_, explicit) + # Prevent both explicit and tag_type == 'explicit' + if tag_type == 'explicit': + tag_type = None + tag = None + + if implicit is not None: + if isinstance(implicit, int_types): + if class_ is None: + class_ = 'context' + implicit = (class_, implicit) + # Prevent both implicit and tag_type == 'implicit' + if tag_type == 'implicit': + tag_type = None + tag = None + + # Convert old tag_type API to explicit/implicit params if tag_type is not None: - if tag_type not in ('implicit', 'explicit'): + if class_ is None: + class_ = 'context' + if tag_type == 'explicit': + explicit = (class_, tag) + elif tag_type == 'implicit': + implicit = (class_, tag) + else: raise ValueError(unwrap( ''' tag_type must be one of "implicit", "explicit", not %s ''', repr(tag_type) )) - self.tag_type = tag_type - if class_ is None: - class_ = 'context' + if explicit is not None: + # Ensure we have a tuple of 2-element tuples + if len(explicit) == 2 and isinstance(explicit[1], int_types): + explicit = (explicit, ) + for class_, tag in explicit: + invalid_class = None + if isinstance(class_, int_types): + if class_ not in CLASS_NUM_TO_NAME_MAP: + invalid_class = class_ + else: + if class_ not in CLASS_NAME_TO_NUM_MAP: + invalid_class = class_ + class_ = CLASS_NAME_TO_NUM_MAP[class_] + if invalid_class is not None: + raise ValueError(unwrap( + ''' + explicit class must be one of "universal", "application", + "context", "private", not %s + ''', + repr(invalid_class) + )) + if tag is not None: + if not isinstance(tag, int_types): + raise TypeError(unwrap( + ''' + explicit tag must be an integer, not %s + ''', + type_name(tag) + )) + if self.explicit is None: + self.explicit = ((class_, tag), ) + else: + self.explicit = self.explicit + ((class_, tag), ) + + elif implicit is not None: + class_, tag = implicit if class_ not in CLASS_NAME_TO_NUM_MAP: raise ValueError(unwrap( ''' - class_ must be one of "universal", "application", + implicit class must be one of "universal", "application", "context", "private", not %s ''', repr(class_) )) - class_ = CLASS_NAME_TO_NUM_MAP[class_] - if tag is not None: if not isinstance(tag, int_types): raise TypeError(unwrap( ''' - tag must be an integer, not %s + implicit tag must be an integer, not %s ''', type_name(tag) )) - - if tag_type == 'implicit': - self.class_ = class_ - self.tag = tag - else: - self.explicit_class = class_ - self.explicit_tag = tag + self.class_ = CLASS_NAME_TO_NUM_MAP[class_] + self.tag = tag + self.implicit = True else: if class_ is not None: if class_ not in CLASS_NUM_TO_NAME_MAP: @@ -324,6 +397,9 @@ if tag is not None: self.tag = tag + if no_explicit: + self.explicit = None + if contents is not None: self.contents = contents @@ -337,7 +413,7 @@ def __str__(self): """ - Since str is differnt in Python 2 and 3, this calls the appropriate + Since str is different in Python 2 and 3, this calls the appropriate method, __unicode__() or __bytes__() :return: @@ -389,11 +465,10 @@ """ new_obj = self.__class__() - new_obj.tag_type = self.tag_type new_obj.class_ = self.class_ new_obj.tag = self.tag - new_obj.explicit_class = self.explicit_class - new_obj.explicit_tag = self.explicit_tag + new_obj.implicit = self.implicit + new_obj.explicit = self.explicit return new_obj def __copy__(self): @@ -434,21 +509,25 @@ return copy.deepcopy(self) - def retag(self, tag_type, tag): + def retag(self, tagging, tag=None): """ Copies the object, applying a new tagging to it - :param tag_type: - A unicode string of "implicit" or "explicit" + :param tagging: + A dict containing the keys "explicit" and "implicit". Legacy + API allows a unicode string of "implicit" or "explicit". :param tag: - A integer tag number + A integer tag number. Only used when tagging is a unicode string. :return: An Asn1Value object """ - new_obj = self.__class__(tag_type=tag_type, tag=tag) + # This is required to preserve the old API + if not isinstance(tagging, dict): + tagging = {tagging: tag} + new_obj = self.__class__(explicit=tagging.get('explicit'), implicit=tagging.get('implicit')) new_obj._copy(self, copy.deepcopy) return new_obj @@ -495,8 +574,8 @@ prefix = ' ' * nest_level - # This interacts with Any and moves the tag, tag_type, _header, contents, _footer - # to the parsed value so duplicate data isn't present + # This interacts with Any and moves the tag, implicit, explicit, _header, + # contents, _footer to the parsed value so duplicate data isn't present has_parsed = hasattr(self, 'parsed') _basic_debug(prefix, self) @@ -529,23 +608,15 @@ self.method = 0 header = _dump_header(self.class_, self.method, self.tag, self.contents) - trailer = b'' - if self.tag_type == 'explicit': - container = Asn1Value() - container.method = 1 - container.class_ = self.explicit_class - container.tag = self.explicit_tag - container.contents = header + self.contents + trailer - # Force the container to generate the header and footer - container.dump() - header = container._header + header - trailer += container._trailer + if self.explicit is not None: + for class_, tag in self.explicit: + header = _dump_header(class_, 1, tag, header + self.contents) + header self._header = header - self._trailer = trailer + self._trailer = b'' - return self._header + contents + self._trailer + return self._header + contents class ValueMap(): @@ -607,10 +678,9 @@ )) new_obj = other_class() - new_obj.tag_type = self.tag_type new_obj.class_ = self.class_ - new_obj.explicit_class = self.explicit_class - new_obj.explicit_tag = self.explicit_tag + new_obj.implicit = self.implicit + new_obj.explicit = self.explicit new_obj._header = self._header new_obj.contents = self.contents new_obj._trailer = self._trailer @@ -627,7 +697,7 @@ """ # Instance attribute indicating if an object was indefinite - # length when parsed – affects parsing and dumping + # length when parsed - affects parsing and dumping _indefinite = False # Class attribute that indicates the offset into self.contents @@ -774,7 +844,7 @@ if not isinstance(value, Asn1Value): raise TypeError(unwrap( ''' - value must be an instance of Ans1Value, not %s + value must be an instance of Asn1Value, not %s ''', type_name(value) )) @@ -835,11 +905,13 @@ if self._parsed is None or self._parsed[1:3] != (spec, spec_params): try: - passed_params = spec_params - if self.tag_type == 'explicit': - passed_params = {} if not spec_params else spec_params.copy() - passed_params['tag_type'] = self.tag_type - passed_params['tag'] = self.explicit_tag + passed_params = spec_params or {} + _tag_type_to_explicit_implicit(passed_params) + if self.explicit is not None: + if 'explicit' in passed_params: + passed_params['explicit'] = self.explicit + passed_params['explicit'] + else: + passed_params['explicit'] = self.explicit contents = self._header + self.contents + self._trailer parsed_value, _ = _parse_build( contents, @@ -850,8 +922,9 @@ # Once we've parsed the Any value, clear any attributes from this object # since they are now duplicate - self.tag_type = None self.tag = None + self.explicit = None + self.implicit = False self._header = b'' self.contents = contents self._trailer = b'' @@ -917,7 +990,7 @@ # # Option 2, same as Option 1, but with a dict of class params # - # ("name", Asn1ValueClass, {'tag_type': 'explicit', 'tag': 5}) + # ("name", Asn1ValueClass, {'explicit': 5}) _alternatives = None # A dict that maps tuples of (class_, tag) to an index in _alternatives @@ -964,7 +1037,7 @@ cls._id_map[id_] = index cls._name_map[info[0]] = index - def __init__(self, name=None, value=None, tag_type=None, **kwargs): + def __init__(self, name=None, value=None, **kwargs): """ Checks to ensure implicit tagging is not being used since it is incompatible with Choice, then forwards on to Asn1Value.__init__() @@ -978,18 +1051,16 @@ :param value: The alternative value to set - used with name - :param tag_type: - The tag_type of the value - None, "implicit" or "explicit" - :raises: - ValueError - when tag_type is "implicit" + ValueError - when implicit param is passed (or legacy tag_type param is "implicit") """ - kwargs['tag_type'] = tag_type + _tag_type_to_explicit_implicit(kwargs) + Asn1Value.__init__(self, **kwargs) try: - if tag_type == 'implicit': + if kwargs.get('implicit') is not None: raise ValueError(unwrap( ''' The Choice type can not be implicitly tagged even if in an @@ -1119,8 +1190,8 @@ id_ = (class_, tag) - if self.tag_type == 'explicit': - if (self.explicit_class, self.explicit_tag) != id_: + if self.explicit is not None: + if self.explicit[-1] != id_: raise ValueError(unwrap( ''' %s was explicitly tagged, but the value provided does not @@ -1202,10 +1273,10 @@ self.contents = self.chosen.dump(force=force) if self._header is None or force: - if self.tag_type == 'explicit': - self._header = _dump_header(self.explicit_class, 1, self.explicit_tag, self.contents) - else: - self._header = b'' + self._header = b'' + if self.explicit is not None: + for class_, tag in self.explicit: + self._header = _dump_header(class_, 1, tag, self._header + self.contents) + self._header return self._header + self.contents @@ -1287,7 +1358,7 @@ def __str__(self): """ - Since str is differnt in Python 2 and 3, this calls the appropriate + Since str is different in Python 2 and 3, this calls the appropriate method, __unicode__() or __bytes__() :return: @@ -1615,7 +1686,7 @@ # When tagging is going on, do the extra work of constructing new # objects to see if the dumped representation are the same - if self.tag_type is not None or other.tag_type is not None: + if self.implicit or self.explicit or other.implicit or other.explicit: return self.untag().dump() == other.untag().dump() return self.dump() == other.dump() @@ -2256,7 +2327,7 @@ Allows reconstructing indefinite length values :return: - A unicode string of bits – 1s and 0s + A unicode string of bits - 1s and 0s """ extra_bits = int_from_bytes(self.contents[0:1]) @@ -3005,7 +3076,7 @@ # # Option 2, same as Option 1, but with a dict of class params # - # ("name", Asn1ValueClass, {'tag_type': 'explicit', 'tag': 5}) + # ("name", Asn1ValueClass, {'explicit': 5}) _fields = [] # A dict with keys being the name of a field and the value being a unicode @@ -3389,10 +3460,10 @@ :return: A tuple containing the following elements: - unicode string of the field name - - Ans1Value class of the field spec + - Asn1Value class of the field spec - Asn1Value class of the value spec - None or dict of params to pass to the field spec - - None or Asn1Value class indicating the value spec was derived fomr an OID or a spec callback + - None or Asn1Value class indicating the value spec was derived from an OID or a spec callback """ name, field_spec, field_params = self._fields[index] @@ -3460,7 +3531,7 @@ raise ValueError(unwrap( ''' Can not set a native python value to %s, which has the - choice type of %s – value must be an instance of Asn1Value + choice type of %s - value must be an instance of Asn1Value ''', field_name, type_name(value_spec) @@ -3638,8 +3709,8 @@ """ Determines the spec to use for the field specified. Depending on how the spec is determined (_oid_pair or _spec_callbacks), it may be - necessary to set preceeding field values before calling this. Usually - specs, if dynamic, are controlled by a preceeding ObjectIdentifier + necessary to set preceding field values before calling this. Usually + specs, if dynamic, are controlled by a preceding ObjectIdentifier field. :param field_name: @@ -3758,6 +3829,19 @@ if force: self._set_contents(force=force) + if self._fields and self.children is not None: + for index, (field_name, _, params) in enumerate(self._fields): + if self.children[index] is not VOID: + continue + if 'default' in params or 'optional' in params: + continue + raise ValueError(unwrap( + ''' + Field "%s" is missing from structure + ''', + field_name + )) + return Asn1Value.dump(self) @@ -3900,7 +3984,7 @@ raise ValueError(unwrap( ''' Can not set a native python value to %s where the - _child_spec is Any – value must be an instance of Asn1Value + _child_spec is Any - value must be an instance of Asn1Value ''', type_name(self) )) @@ -3910,7 +3994,7 @@ raise ValueError(unwrap( ''' Can not set a native python value to %s where the - _child_spec is the choice type %s – value must be an + _child_spec is the choice type %s - value must be an instance of Asn1Value ''', type_name(self), @@ -3927,13 +4011,10 @@ return self._child_spec(value=value) params = {} - if self._child_spec.tag_type is not None: - params['tag_type'] = self._child_spec.tag_type - if params['tag_type'] == 'explicit': - params['tag'] = self._child_spec.explicit_tag - else: - params['tag'] = self._child_spec.tag - + if self._child_spec.explicit: + params['explicit'] = self._child_spec.explicit + if self._child_spec.implicit: + params['implicit'] = (self._child_spec.class_, self._child_spec.tag) return _fix_tagging(new_value, params) def __len__(self): @@ -4738,19 +4819,20 @@ method_name = METHOD_NUM_TO_NAME_MAP.get(self.method) class_name = CLASS_NUM_TO_NAME_MAP.get(self.class_) - if self.tag_type == 'explicit': - print( - '%s %s tag %s (explicitly tagged)' % - ( - prefix, - CLASS_NUM_TO_NAME_MAP.get(self.explicit_class), - self.explicit_tag + if self.explicit is not None: + for class_, tag in self.explicit: + print( + '%s %s tag %s (explicitly tagged)' % + ( + prefix, + CLASS_NUM_TO_NAME_MAP.get(class_), + tag + ) ) - ) if has_header: print('%s %s %s %s' % (prefix, method_name, class_name, self.tag)) - elif self.tag_type == 'implicit': + elif self.implicit: if has_header: print('%s %s %s tag %s (implicitly tagged)' % (prefix, method_name, class_name, self.tag)) @@ -4760,6 +4842,25 @@ print('%s Data: 0x%s' % (prefix, binascii.hexlify(self.contents or b'').decode('utf-8'))) +def _tag_type_to_explicit_implicit(params): + """ + Converts old-style "tag_type" and "tag" params to "explicit" and "implicit" + + :param params: + A dict of parameters to convert from tag_type/tag to explicit/implicit + """ + + if 'tag_type' in params: + if params['tag_type'] == 'explicit': + params['explicit'] = (params.get('class', 2), params['tag']) + elif params['tag_type'] == 'implicit': + params['implicit'] = (params.get('class', 2), params['tag']) + del params['tag_type'] + del params['tag'] + if 'class' in params: + del params['class'] + + def _fix_tagging(value, params): """ Checks if a value is properly tagged based on the spec, and re/untags as @@ -4775,26 +4876,28 @@ An Asn1Value that is properly tagged """ - if 'tag_type' in params: - required_tag_type = params['tag_type'] - retag = False + _tag_type_to_explicit_implicit(params) - if required_tag_type != value.tag_type: + retag = False + if 'implicit' not in params: + if value.implicit is not False: retag = True - - elif required_tag_type == 'explicit' and value.explicit_tag != params['tag']: + else: + if isinstance(params['implicit'], tuple): + class_, tag = params['implicit'] + else: + tag = params['implicit'] + class_ = 'context' + if value.implicit is False: retag = True - - elif required_tag_type == 'implicit' and value.tag != params['tag']: + elif value.class_ != CLASS_NAME_TO_NUM_MAP[class_] or value.tag != tag: retag = True - if retag: - return value.retag(params['tag_type'], params['tag']) - return value - - if value.tag_type: - return value.untag() + if params.get('explicit') != value.explicit: + retag = True + if retag: + return value.retag(params) return value @@ -4820,9 +4923,22 @@ required_class = spec.class_ required_tag = spec.tag - tag_type = params.get('tag_type', spec.tag_type) - if tag_type is not None: - required_class = 2 + _tag_type_to_explicit_implicit(params) + + if 'explicit' in params: + if isinstance(params['explicit'], tuple): + required_class, required_tag = params['explicit'] + else: + required_class = 2 + required_tag = params['explicit'] + elif 'implicit' in params: + if isinstance(params['implicit'], tuple): + required_class, required_tag = params['implicit'] + else: + required_class = 2 + required_tag = params['implicit'] + if required_class is not None and not isinstance(required_class, int_types): + required_class = CLASS_NAME_TO_NUM_MAP[required_class] required_class = params.get('class_', required_class) required_tag = params.get('tag', required_tag) @@ -4903,6 +5019,9 @@ An object of the type spec, or if not specified, a child of Asn1Value """ + if spec_params is not None: + _tag_type_to_explicit_implicit(spec_params) + if header is None: return VOID @@ -4910,116 +5029,140 @@ # If an explicit specification was passed in, make sure it matches if spec is not None: - if spec_params: - value = spec(contents=contents, **spec_params) + # If there is explicit tagging and contents, we have to split + # the header and trailer off before we do the parsing + no_explicit = spec_params and 'no_explicit' in spec_params + if not no_explicit and (spec.explicit or (spec_params and 'explicit' in spec_params)): + if spec_params: + value = spec(**spec_params) + else: + value = spec() + original_explicit = value.explicit + explicit_info = reversed(original_explicit) + parsed_class = class_ + parsed_method = method + parsed_tag = tag + to_parse = contents + explicit_header = header + explicit_trailer = trailer or b'' + for expected_class, expected_tag in explicit_info: + if parsed_class != expected_class: + raise ValueError(unwrap( + ''' + Error parsing %s - explicitly-tagged class should have been + %s, but %s was found + ''', + type_name(value), + CLASS_NUM_TO_NAME_MAP.get(expected_class), + CLASS_NUM_TO_NAME_MAP.get(parsed_class, parsed_class) + )) + if parsed_method != 1: + raise ValueError(unwrap( + ''' + Error parsing %s - explicitly-tagged method should have + been %s, but %s was found + ''', + type_name(value), + METHOD_NUM_TO_NAME_MAP.get(1), + METHOD_NUM_TO_NAME_MAP.get(parsed_method, parsed_method) + )) + if parsed_tag != expected_tag: + raise ValueError(unwrap( + ''' + Error parsing %s - explicitly-tagged tag should have been + %s, but %s was found + ''', + type_name(value), + expected_tag, + parsed_tag + )) + info, _ = _parse(to_parse, len(to_parse)) + parsed_class, parsed_method, parsed_tag, parsed_header, to_parse, parsed_trailer = info + explicit_header += parsed_header + explicit_trailer = parsed_trailer + explicit_trailer + + value = _build(*info, spec=spec, spec_params={'no_explicit': True}) + value._header = explicit_header + value._trailer = explicit_trailer + value.explicit = original_explicit + header_set = True else: - value = spec(contents=contents) - - if spec is Any: - pass + if spec_params: + value = spec(contents=contents, **spec_params) + else: + value = spec(contents=contents) - elif value.tag_type == 'explicit': - if class_ != value.explicit_class: - raise ValueError(unwrap( - ''' - Error parsing %s - explicitly-tagged class should have been - %s, but %s was found - ''', - type_name(value), - CLASS_NUM_TO_NAME_MAP.get(value.explicit_class), - CLASS_NUM_TO_NAME_MAP.get(class_, class_) - )) - if method != 1: - raise ValueError(unwrap( - ''' - Error parsing %s - explicitly-tagged method should have - been %s, but %s was found - ''', - type_name(value), - METHOD_NUM_TO_NAME_MAP.get(1), - METHOD_NUM_TO_NAME_MAP.get(method, method) - )) - if tag != value.explicit_tag: - raise ValueError(unwrap( - ''' - Error parsing %s - explicitly-tagged tag should have been - %s, but %s was found - ''', - type_name(value), - value.explicit_tag, - tag - )) - original_value = value - info, _ = _parse(contents, len(contents)) - value = _build(*info, spec=spec) - value._header = header + value._header - value._trailer += trailer or b'' - value.tag_type = 'explicit' - value.explicit_class = original_value.explicit_class - value.explicit_tag = original_value.explicit_tag - header_set = True + if spec is Any: + pass - elif isinstance(value, Choice): - value.validate(class_, tag, contents) - try: - # Force parsing the Choice now - value.contents = header + value.contents - header = b'' - value.parse() - except (ValueError, TypeError) as e: - args = e.args[1:] - e.args = (e.args[0] + '\n while parsing %s' % type_name(value),) + args - raise e + elif isinstance(value, Choice): + value.validate(class_, tag, contents) + try: + # Force parsing the Choice now + value.contents = header + value.contents + header = b'' + value.parse() + except (ValueError, TypeError) as e: + args = e.args[1:] + e.args = (e.args[0] + '\n while parsing %s' % type_name(value),) + args + raise e - else: - if class_ != value.class_: - raise ValueError(unwrap( - ''' - Error parsing %s - class should have been %s, but %s was - found - ''', - type_name(value), - CLASS_NUM_TO_NAME_MAP.get(value.class_), - CLASS_NUM_TO_NAME_MAP.get(class_, class_) - )) - if method != value.method: - # Allow parsing a primitive method as constructed if the value - # is indefinite length. This is to allow parsing BER. - ber_indef = method == 1 and value.method == 0 and trailer == b'\x00\x00' - if not ber_indef or not isinstance(value, Constructable): + else: + if class_ != value.class_: raise ValueError(unwrap( ''' - Error parsing %s - method should have been %s, but %s was found + Error parsing %s - class should have been %s, but %s was + found ''', type_name(value), - METHOD_NUM_TO_NAME_MAP.get(value.method), - METHOD_NUM_TO_NAME_MAP.get(method, method) + CLASS_NUM_TO_NAME_MAP.get(value.class_), + CLASS_NUM_TO_NAME_MAP.get(class_, class_) + )) + if method != value.method: + # Allow parsing a primitive method as constructed if the value + # is indefinite length. This is to allow parsing BER. + ber_indef = method == 1 and value.method == 0 and trailer == b'\x00\x00' + if not ber_indef or not isinstance(value, Constructable): + raise ValueError(unwrap( + ''' + Error parsing %s - method should have been %s, but %s was found + ''', + type_name(value), + METHOD_NUM_TO_NAME_MAP.get(value.method), + METHOD_NUM_TO_NAME_MAP.get(method, method) + )) + else: + value.method = method + value._indefinite = True + if tag != value.tag and tag != value._bad_tag: + raise ValueError(unwrap( + ''' + Error parsing %s - tag should have been %s, but %s was found + ''', + type_name(value), + value.tag, + tag )) - else: - value.method = method - value._indefinite = True - if tag != value.tag and tag != value._bad_tag: - raise ValueError(unwrap( - ''' - Error parsing %s - tag should have been %s, but %s was found - ''', - type_name(value), - value.tag, - tag - )) # For explicitly tagged, un-speced parsings, we use a generic container # since we will be parsing the contents and discarding the outer object # anyway a little further on - elif spec_params and 'tag_type' in spec_params and spec_params['tag_type'] == 'explicit': + elif spec_params and 'explicit' in spec_params: original_value = Asn1Value(contents=contents, **spec_params) - info, _ = _parse(contents, len(contents)) - value = _build(*info, spec=spec) + original_explicit = original_value.explicit + + to_parse = contents + explicit_header = header + explicit_trailer = trailer or b'' + for expected_class, expected_tag in reversed(original_explicit): + info, _ = _parse(to_parse, len(to_parse)) + _, _, _, parsed_header, to_parse, parsed_trailer = info + explicit_header += parsed_header + explicit_trailer = parsed_trailer + explicit_trailer + value = _build(*info, spec=spec, spec_params={'no_explicit': True}) value._header = header + value._header value._trailer += trailer or b'' - value.tag_type = 'explicit' - value.explicit_class = original_value.explicit_class - value.explicit_tag = original_value.explicit_tag + value.explicit = original_explicit header_set = True # If no spec was specified, allow anything and just process what diff -Nru asn1crypto-0.22.0/asn1crypto/crl.py asn1crypto-0.24.0/asn1crypto/crl.py --- asn1crypto-0.22.0/asn1crypto/crl.py 2015-10-24 20:52:24.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto/crl.py 2017-09-15 10:48:38.000000000 +0000 @@ -50,12 +50,12 @@ class IssuingDistributionPoint(Sequence): _fields = [ - ('distribution_point', DistributionPointName, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), - ('only_contains_user_certs', Boolean, {'tag_type': 'implicit', 'tag': 1, 'default': False}), - ('only_contains_ca_certs', Boolean, {'tag_type': 'implicit', 'tag': 2, 'default': False}), - ('only_some_reasons', ReasonFlags, {'tag_type': 'implicit', 'tag': 3, 'optional': True}), - ('indirect_crl', Boolean, {'tag_type': 'implicit', 'tag': 4, 'default': False}), - ('only_contains_attribute_certs', Boolean, {'tag_type': 'implicit', 'tag': 5, 'default': False}), + ('distribution_point', DistributionPointName, {'explicit': 0, 'optional': True}), + ('only_contains_user_certs', Boolean, {'implicit': 1, 'default': False}), + ('only_contains_ca_certs', Boolean, {'implicit': 2, 'default': False}), + ('only_some_reasons', ReasonFlags, {'implicit': 3, 'optional': True}), + ('indirect_crl', Boolean, {'implicit': 4, 'default': False}), + ('only_contains_attribute_certs', Boolean, {'implicit': 5, 'default': False}), ] @@ -278,9 +278,9 @@ ('signature', SignedDigestAlgorithm), ('issuer', Name), ('this_update', Time), - ('next_update', Time), + ('next_update', Time, {'optional': True}), ('revoked_certificates', RevokedCertificates, {'optional': True}), - ('crl_extensions', TBSCertListExtensions, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), + ('crl_extensions', TBSCertListExtensions, {'explicit': 0, 'optional': True}), ] diff -Nru asn1crypto-0.22.0/asn1crypto/csr.py asn1crypto-0.24.0/asn1crypto/csr.py --- asn1crypto-0.22.0/asn1crypto/csr.py 2015-11-05 21:06:04.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto/csr.py 2017-09-15 10:48:44.000000000 +0000 @@ -84,7 +84,7 @@ ('version', Version), ('subject', Name), ('subject_pk_info', PublicKeyInfo), - ('attributes', CRIAttributes, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), + ('attributes', CRIAttributes, {'implicit': 0, 'optional': True}), ] diff -Nru asn1crypto-0.22.0/asn1crypto/_elliptic_curve.py asn1crypto-0.24.0/asn1crypto/_elliptic_curve.py --- asn1crypto-0.22.0/asn1crypto/_elliptic_curve.py 2016-06-16 09:12:23.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto/_elliptic_curve.py 2017-11-07 21:32:35.000000000 +0000 @@ -160,10 +160,10 @@ p = self.curve.p - l = ((other.y - self.y) * inverse_mod(other.x - self.x, p)) % p + l_ = ((other.y - self.y) * inverse_mod(other.x - self.x, p)) % p - x3 = (l * l - self.x - other.x) % p - y3 = (l * (self.x - x3) - self.y) % p + x3 = (l_ * l_ - self.x - other.x) % p + y3 = (l_ * (self.x - x3) - self.y) % p return PrimePoint(self.curve, x3, y3) @@ -232,10 +232,10 @@ p = self.curve.p a = self.curve.a - l = ((3 * self.x * self.x + a) * inverse_mod(2 * self.y, p)) % p + l_ = ((3 * self.x * self.x + a) * inverse_mod(2 * self.y, p)) % p - x3 = (l * l - 2 * self.x) % p - y3 = (l * (self.x - x3) - self.y) % p + x3 = (l_ * l_ - 2 * self.x) % p + y3 = (l_ * (self.x - x3) - self.y) % p return PrimePoint(self.curve, x3, y3) diff -Nru asn1crypto-0.22.0/asn1crypto/_inet.py asn1crypto-0.24.0/asn1crypto/_inet.py --- asn1crypto-0.22.0/asn1crypto/_inet.py 2016-06-15 11:14:32.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto/_inet.py 2017-11-07 21:09:57.000000000 +0000 @@ -1,170 +1,170 @@ -# coding: utf-8 -from __future__ import unicode_literals, division, absolute_import, print_function - -import socket -import struct - -from ._errors import unwrap -from ._types import byte_cls, bytes_to_list, str_cls, type_name - - -def inet_ntop(address_family, packed_ip): - """ - Windows compatiblity shim for socket.inet_ntop(). - - :param address_family: - socket.AF_INET for IPv4 or socket.AF_INET6 for IPv6 - - :param packed_ip: - A byte string of the network form of an IP address - - :return: - A unicode string of the IP address - """ - - if address_family not in set([socket.AF_INET, socket.AF_INET6]): - raise ValueError(unwrap( - ''' - address_family must be socket.AF_INET (%s) or socket.AF_INET6 (%s), - not %s - ''', - repr(socket.AF_INET), - repr(socket.AF_INET6), - repr(address_family) - )) - - if not isinstance(packed_ip, byte_cls): - raise TypeError(unwrap( - ''' - packed_ip must be a byte string, not %s - ''', - type_name(packed_ip) - )) - - required_len = 4 if address_family == socket.AF_INET else 16 - if len(packed_ip) != required_len: - raise ValueError(unwrap( - ''' - packed_ip must be %d bytes long - is %d - ''', - required_len, - len(packed_ip) - )) - - if address_family == socket.AF_INET: - return '%d.%d.%d.%d' % tuple(bytes_to_list(packed_ip)) - - octets = struct.unpack(b'!HHHHHHHH', packed_ip) - - runs_of_zero = {} - longest_run = 0 - zero_index = None - for i, octet in enumerate(octets + (-1,)): - if octet != 0: - if zero_index is not None: - length = i - zero_index - if length not in runs_of_zero: - runs_of_zero[length] = zero_index - longest_run = max(longest_run, length) - zero_index = None - elif zero_index is None: - zero_index = i - - hexed = [hex(o)[2:] for o in octets] - - if longest_run < 2: - return ':'.join(hexed) - - zero_start = runs_of_zero[longest_run] - zero_end = zero_start + longest_run - - return ':'.join(hexed[:zero_start]) + '::' + ':'.join(hexed[zero_end:]) - - -def inet_pton(address_family, ip_string): - """ - Windows compatiblity shim for socket.inet_ntop(). - - :param address_family: - socket.AF_INET for IPv4 or socket.AF_INET6 for IPv6 - - :param ip_string: - A unicode string of an IP address - - :return: - A byte string of the network form of the IP address - """ - - if address_family not in set([socket.AF_INET, socket.AF_INET6]): - raise ValueError(unwrap( - ''' - address_family must be socket.AF_INET (%s) or socket.AF_INET6 (%s), - not %s - ''', - repr(socket.AF_INET), - repr(socket.AF_INET6), - repr(address_family) - )) - - if not isinstance(ip_string, str_cls): - raise TypeError(unwrap( - ''' - ip_string must be a unicode string, not %s - ''', - type_name(ip_string) - )) - - if address_family == socket.AF_INET: - octets = ip_string.split('.') - error = len(octets) != 4 - if not error: - ints = [] - for o in octets: - o = int(o) - if o > 255 or o < 0: - error = True - break - ints.append(o) - - if error: - raise ValueError(unwrap( - ''' - ip_string must be a dotted string with four integers in the - range of 0 to 255, got %s - ''', - repr(ip_string) - )) - - return struct.pack(b'!BBBB', *ints) - - error = False - omitted = ip_string.count('::') - if omitted > 1: - error = True - elif omitted == 0: - octets = ip_string.split(':') - error = len(octets) != 8 - else: - begin, end = ip_string.split('::') - begin_octets = begin.split(':') - end_octets = end.split(':') - missing = 8 - len(begin_octets) - len(end_octets) - octets = begin_octets + (['0'] * missing) + end_octets - - if not error: - ints = [] - for o in octets: - o = int(o, 16) - if o > 65535 or o < 0: - error = True - break - ints.append(o) - - return struct.pack(b'!HHHHHHHH', *ints) - - raise ValueError(unwrap( - ''' - ip_string must be a valid ipv6 string, got %s - ''', - repr(ip_string) - )) +# coding: utf-8 +from __future__ import unicode_literals, division, absolute_import, print_function + +import socket +import struct + +from ._errors import unwrap +from ._types import byte_cls, bytes_to_list, str_cls, type_name + + +def inet_ntop(address_family, packed_ip): + """ + Windows compatibility shim for socket.inet_ntop(). + + :param address_family: + socket.AF_INET for IPv4 or socket.AF_INET6 for IPv6 + + :param packed_ip: + A byte string of the network form of an IP address + + :return: + A unicode string of the IP address + """ + + if address_family not in set([socket.AF_INET, socket.AF_INET6]): + raise ValueError(unwrap( + ''' + address_family must be socket.AF_INET (%s) or socket.AF_INET6 (%s), + not %s + ''', + repr(socket.AF_INET), + repr(socket.AF_INET6), + repr(address_family) + )) + + if not isinstance(packed_ip, byte_cls): + raise TypeError(unwrap( + ''' + packed_ip must be a byte string, not %s + ''', + type_name(packed_ip) + )) + + required_len = 4 if address_family == socket.AF_INET else 16 + if len(packed_ip) != required_len: + raise ValueError(unwrap( + ''' + packed_ip must be %d bytes long - is %d + ''', + required_len, + len(packed_ip) + )) + + if address_family == socket.AF_INET: + return '%d.%d.%d.%d' % tuple(bytes_to_list(packed_ip)) + + octets = struct.unpack(b'!HHHHHHHH', packed_ip) + + runs_of_zero = {} + longest_run = 0 + zero_index = None + for i, octet in enumerate(octets + (-1,)): + if octet != 0: + if zero_index is not None: + length = i - zero_index + if length not in runs_of_zero: + runs_of_zero[length] = zero_index + longest_run = max(longest_run, length) + zero_index = None + elif zero_index is None: + zero_index = i + + hexed = [hex(o)[2:] for o in octets] + + if longest_run < 2: + return ':'.join(hexed) + + zero_start = runs_of_zero[longest_run] + zero_end = zero_start + longest_run + + return ':'.join(hexed[:zero_start]) + '::' + ':'.join(hexed[zero_end:]) + + +def inet_pton(address_family, ip_string): + """ + Windows compatibility shim for socket.inet_ntop(). + + :param address_family: + socket.AF_INET for IPv4 or socket.AF_INET6 for IPv6 + + :param ip_string: + A unicode string of an IP address + + :return: + A byte string of the network form of the IP address + """ + + if address_family not in set([socket.AF_INET, socket.AF_INET6]): + raise ValueError(unwrap( + ''' + address_family must be socket.AF_INET (%s) or socket.AF_INET6 (%s), + not %s + ''', + repr(socket.AF_INET), + repr(socket.AF_INET6), + repr(address_family) + )) + + if not isinstance(ip_string, str_cls): + raise TypeError(unwrap( + ''' + ip_string must be a unicode string, not %s + ''', + type_name(ip_string) + )) + + if address_family == socket.AF_INET: + octets = ip_string.split('.') + error = len(octets) != 4 + if not error: + ints = [] + for o in octets: + o = int(o) + if o > 255 or o < 0: + error = True + break + ints.append(o) + + if error: + raise ValueError(unwrap( + ''' + ip_string must be a dotted string with four integers in the + range of 0 to 255, got %s + ''', + repr(ip_string) + )) + + return struct.pack(b'!BBBB', *ints) + + error = False + omitted = ip_string.count('::') + if omitted > 1: + error = True + elif omitted == 0: + octets = ip_string.split(':') + error = len(octets) != 8 + else: + begin, end = ip_string.split('::') + begin_octets = begin.split(':') + end_octets = end.split(':') + missing = 8 - len(begin_octets) - len(end_octets) + octets = begin_octets + (['0'] * missing) + end_octets + + if not error: + ints = [] + for o in octets: + o = int(o, 16) + if o > 65535 or o < 0: + error = True + break + ints.append(o) + + return struct.pack(b'!HHHHHHHH', *ints) + + raise ValueError(unwrap( + ''' + ip_string must be a valid ipv6 string, got %s + ''', + repr(ip_string) + )) diff -Nru asn1crypto-0.22.0/asn1crypto/keys.py asn1crypto-0.24.0/asn1crypto/keys.py --- asn1crypto-0.22.0/asn1crypto/keys.py 2016-08-30 15:31:11.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto/keys.py 2017-11-22 16:15:11.000000000 +0000 @@ -30,7 +30,7 @@ ) from ._errors import unwrap from ._types import type_name, str_cls, byte_cls -from .algos import _ForceNullParameters, DigestAlgorithm, EncryptionAlgorithm +from .algos import _ForceNullParameters, DigestAlgorithm, EncryptionAlgorithm, RSAESOAEPParams from .core import ( Any, Asn1Value, @@ -412,8 +412,8 @@ _fields = [ ('version', ECPrivateKeyVersion), ('private_key', IntegerOctetString), - ('parameters', ECDomainParameters, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), - ('public_key', ECPointBitString, {'tag_type': 'explicit', 'tag': 1, 'optional': True}), + ('parameters', ECDomainParameters, {'explicit': 0, 'optional': True}), + ('public_key', ECPointBitString, {'explicit': 1, 'optional': True}), ] @@ -497,7 +497,7 @@ ('version', Integer), ('private_key_algorithm', PrivateKeyAlgorithm), ('private_key', ParsableOctetString), - ('attributes', Attributes, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), + ('attributes', Attributes, {'implicit': 0, 'optional': True}), ] def _private_key_spec(self): @@ -828,7 +828,7 @@ Creates a fingerprint that can be compared with a public key to see if the two form a pair. - This fingerprint is not compatiable with fingerprints generated by any + This fingerprint is not compatible with fingerprints generated by any other software. :return: @@ -930,6 +930,8 @@ _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 @@ -955,6 +957,7 @@ 'dsa': DSAParams, 'ec': ECDomainParameters, 'dh': DomainParameters, + 'rsaes_oaep': RSAESOAEPParams, } @@ -973,6 +976,7 @@ 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 @@ -1191,7 +1195,7 @@ Creates a fingerprint that can be compared with a private key to see if the two form a pair. - This fingerprint is not compatiable with fingerprints generated by any + This fingerprint is not compatible with fingerprints generated by any other software. :return: diff -Nru asn1crypto-0.22.0/asn1crypto/ocsp.py asn1crypto-0.24.0/asn1crypto/ocsp.py --- asn1crypto-0.22.0/asn1crypto/ocsp.py 2015-10-30 02:55:30.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto/ocsp.py 2017-09-15 10:49:11.000000000 +0000 @@ -84,7 +84,7 @@ class Request(Sequence): _fields = [ ('req_cert', CertId), - ('single_request_extensions', RequestExtensions, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), + ('single_request_extensions', RequestExtensions, {'explicit': 0, 'optional': True}), ] _processed_extensions = False @@ -192,10 +192,10 @@ class TBSRequest(Sequence): _fields = [ - ('version', Version, {'tag_type': 'explicit', 'tag': 0, 'default': 'v1'}), - ('requestor_name', GeneralName, {'tag_type': 'explicit', 'tag': 1, 'optional': True}), + ('version', Version, {'explicit': 0, 'default': 'v1'}), + ('requestor_name', GeneralName, {'explicit': 1, 'optional': True}), ('request_list', Requests), - ('request_extensions', TBSRequestExtensions, {'tag_type': 'explicit', 'tag': 2, 'optional': True}), + ('request_extensions', TBSRequestExtensions, {'explicit': 2, 'optional': True}), ] @@ -207,14 +207,14 @@ _fields = [ ('signature_algorithm', SignedDigestAlgorithm), ('signature', OctetBitString), - ('certs', Certificates, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), + ('certs', Certificates, {'explicit': 0, 'optional': True}), ] class OCSPRequest(Sequence): _fields = [ ('tbs_request', TBSRequest), - ('optional_signature', Signature, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), + ('optional_signature', Signature, {'explicit': 0, 'optional': True}), ] _processed_extensions = False @@ -314,31 +314,31 @@ class ResponderId(Choice): _alternatives = [ - ('by_name', Name, {'tag_type': 'explicit', 'tag': 1}), - ('by_key', OctetString, {'tag_type': 'explicit', 'tag': 2}), + ('by_name', Name, {'explicit': 1}), + ('by_key', OctetString, {'explicit': 2}), ] class RevokedInfo(Sequence): _fields = [ ('revocation_time', GeneralizedTime), - ('revocation_reason', CRLReason, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), + ('revocation_reason', CRLReason, {'explicit': 0, 'optional': True}), ] class CertStatus(Choice): _alternatives = [ - ('good', Null, {'tag_type': 'implicit', 'tag': 0}), - ('revoked', RevokedInfo, {'tag_type': 'implicit', 'tag': 1}), - ('unknown', Null, {'tag_type': 'implicit', 'tag': 2}), + ('good', Null, {'implicit': 0}), + ('revoked', RevokedInfo, {'implicit': 1}), + ('unknown', Null, {'implicit': 2}), ] class CrlId(Sequence): _fields = [ - ('crl_url', IA5String, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), - ('crl_num', Integer, {'tag_type': 'explicit', 'tag': 1, 'optional': True}), - ('crl_time', GeneralizedTime, {'tag_type': 'explicit', 'tag': 2, 'optional': True}), + ('crl_url', IA5String, {'explicit': 0, 'optional': True}), + ('crl_num', Integer, {'explicit': 1, 'optional': True}), + ('crl_time', GeneralizedTime, {'explicit': 2, 'optional': True}), ] @@ -351,6 +351,8 @@ '2.5.29.21': 'crl_reason', '2.5.29.24': 'invalidity_date', '2.5.29.29': 'certificate_issuer', + # https://tools.ietf.org/html/rfc6962.html#page-13 + '1.3.6.1.4.1.11129.2.4.5': 'signed_certificate_timestamp_list', } @@ -368,6 +370,7 @@ 'crl_reason': CRLReason, 'invalidity_date': GeneralizedTime, 'certificate_issuer': GeneralNames, + 'signed_certificate_timestamp_list': OctetString, } @@ -380,8 +383,8 @@ ('cert_id', CertId), ('cert_status', CertStatus), ('this_update', GeneralizedTime), - ('next_update', GeneralizedTime, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), - ('single_extensions', SingleResponseExtensions, {'tag_type': 'explicit', 'tag': 1, 'optional': True}), + ('next_update', GeneralizedTime, {'explicit': 0, 'optional': True}), + ('single_extensions', SingleResponseExtensions, {'explicit': 1, 'optional': True}), ] _processed_extensions = False @@ -526,11 +529,11 @@ class ResponseData(Sequence): _fields = [ - ('version', Version, {'tag_type': 'explicit', 'tag': 0, 'default': 'v1'}), + ('version', Version, {'explicit': 0, 'default': 'v1'}), ('responder_id', ResponderId), ('produced_at', GeneralizedTime), ('responses', Responses), - ('response_extensions', ResponseDataExtensions, {'tag_type': 'explicit', 'tag': 1, 'optional': True}), + ('response_extensions', ResponseDataExtensions, {'explicit': 1, 'optional': True}), ] @@ -539,7 +542,7 @@ ('tbs_response_data', ResponseData), ('signature_algorithm', SignedDigestAlgorithm), ('signature', OctetBitString), - ('certs', Certificates, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), + ('certs', Certificates, {'explicit': 0, 'optional': True}), ] @@ -558,7 +561,7 @@ class OCSPResponse(Sequence): _fields = [ ('response_status', OCSPResponseStatus), - ('response_bytes', ResponseBytes, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), + ('response_bytes', ResponseBytes, {'explicit': 0, 'optional': True}), ] _processed_extensions = False diff -Nru asn1crypto-0.22.0/asn1crypto/pdf.py asn1crypto-0.24.0/asn1crypto/pdf.py --- asn1crypto-0.22.0/asn1crypto/pdf.py 2016-07-11 01:34:11.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto/pdf.py 2017-09-15 10:49:21.000000000 +0000 @@ -63,9 +63,9 @@ class RevocationInfoArchival(Sequence): _fields = [ - ('crl', SequenceOfCertificateList, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), - ('ocsp', SequenceOfOCSPResponse, {'tag_type': 'explicit', 'tag': 1, 'optional': True}), - ('other_rev_info', SequenceOfOtherRevInfo, {'tag_type': 'explicit', 'tag': 2, 'optional': True}), + ('crl', SequenceOfCertificateList, {'explicit': 0, 'optional': True}), + ('ocsp', SequenceOfOCSPResponse, {'explicit': 1, 'optional': True}), + ('other_rev_info', SequenceOfOtherRevInfo, {'explicit': 2, 'optional': True}), ] diff -Nru asn1crypto-0.22.0/asn1crypto/pem.py asn1crypto-0.24.0/asn1crypto/pem.py --- asn1crypto-0.22.0/asn1crypto/pem.py 2015-10-08 13:43:00.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto/pem.py 2017-11-28 16:18:08.000000000 +0000 @@ -16,7 +16,7 @@ import sys from ._errors import unwrap -from ._types import type_name, str_cls, byte_cls +from ._types import type_name as _type_name, str_cls, byte_cls if sys.version_info < (3,): from cStringIO import StringIO as BytesIO @@ -41,7 +41,7 @@ ''' byte_string must be a byte string, not %s ''', - type_name(byte_string) + _type_name(byte_string) )) return byte_string.find(b'-----BEGIN') != -1 or byte_string.find(b'---- BEGIN') != -1 @@ -51,15 +51,15 @@ """ Armors a DER-encoded byte string in PEM - :param der_bytes: - A byte string to be armored - :param type_name: A unicode string that will be capitalized and placed in the header and footer of the block. E.g. "CERTIFICATE", "PRIVATE KEY", etc. This will appear as "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----". + :param der_bytes: + A byte string to be armored + :param headers: An OrderedDict of the header lines to write after the BEGIN line @@ -71,7 +71,7 @@ raise TypeError(unwrap( ''' der_bytes must be a byte string, not %s - ''' % type_name(der_bytes) + ''' % _type_name(der_bytes) )) if not isinstance(type_name, str_cls): @@ -79,7 +79,7 @@ ''' type_name must be a unicode string, not %s ''', - type_name(type_name) + _type_name(type_name) )) type_name = type_name.upper().encode('ascii') @@ -132,7 +132,7 @@ ''' pem_bytes must be a byte string, not %s ''', - type_name(pem_bytes) + _type_name(pem_bytes) )) # Valid states include: "trash", "headers", "body" diff -Nru asn1crypto-0.22.0/asn1crypto/_perf/_big_num_ctypes.py asn1crypto-0.24.0/asn1crypto/_perf/_big_num_ctypes.py --- asn1crypto-0.22.0/asn1crypto/_perf/_big_num_ctypes.py 2015-10-08 13:36:50.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto/_perf/_big_num_ctypes.py 2017-05-09 09:59:01.000000000 +0000 @@ -21,6 +21,8 @@ from __future__ import unicode_literals, division, absolute_import, print_function +import sys + from ctypes import CDLL, c_int, c_char_p, c_void_p from ctypes.util import find_library @@ -28,7 +30,9 @@ try: - libcrypto_path = find_library('crypto') + # On Python 2, the unicode string here may raise a UnicodeDecodeError as it + # tries to join a bytestring path to the unicode name "crypto" + libcrypto_path = find_library(b'crypto' if sys.version_info < (3,) else 'crypto') if not libcrypto_path: raise LibraryNotFoundError('The library libcrypto could not be found') diff -Nru asn1crypto-0.22.0/asn1crypto/pkcs12.py asn1crypto-0.24.0/asn1crypto/pkcs12.py --- asn1crypto-0.22.0/asn1crypto/pkcs12.py 2017-02-19 21:13:06.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto/pkcs12.py 2017-09-15 10:49:30.000000000 +0000 @@ -145,7 +145,7 @@ class CertBag(Sequence): _fields = [ ('cert_id', CertId), - ('cert_value', ParsableOctetString, {'tag_type': 'explicit', 'tag': 0}), + ('cert_value', ParsableOctetString, {'explicit': 0}), ] _oid_pair = ('cert_id', 'cert_value') @@ -157,14 +157,14 @@ class CrlBag(Sequence): _fields = [ ('crl_id', ObjectIdentifier), - ('crl_value', OctetString, {'tag_type': 'explicit', 'tag': 0}), + ('crl_value', OctetString, {'explicit': 0}), ] class SecretBag(Sequence): _fields = [ ('secret_type_id', ObjectIdentifier), - ('secret_value', OctetString, {'tag_type': 'explicit', 'tag': 0}), + ('secret_value', OctetString, {'explicit': 0}), ] @@ -175,7 +175,7 @@ class SafeBag(Sequence): _fields = [ ('bag_id', BagId), - ('bag_value', Any, {'tag_type': 'explicit', 'tag': 0}), + ('bag_value', Any, {'explicit': 0}), ('bag_attributes', Attributes, {'optional': True}), ] diff -Nru asn1crypto-0.22.0/asn1crypto/tsp.py asn1crypto-0.24.0/asn1crypto/tsp.py --- asn1crypto-0.22.0/asn1crypto/tsp.py 2016-08-27 19:58:53.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto/tsp.py 2017-09-15 10:49:49.000000000 +0000 @@ -74,8 +74,8 @@ class Accuracy(Sequence): _fields = [ ('seconds', Integer, {'optional': True}), - ('millis', Integer, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), - ('micros', Integer, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('millis', Integer, {'implicit': 0, 'optional': True}), + ('micros', Integer, {'implicit': 1, 'optional': True}), ] @@ -101,8 +101,8 @@ ('accuracy', Accuracy, {'optional': True}), ('ordering', Boolean, {'default': False}), ('nonce', Integer, {'optional': True}), - ('tsa', GeneralName, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), - ('extensions', Extensions, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('tsa', GeneralName, {'explicit': 0, 'optional': True}), + ('extensions', Extensions, {'implicit': 1, 'optional': True}), ] @@ -113,7 +113,7 @@ ('req_policy', ObjectIdentifier, {'optional': True}), ('nonce', Integer, {'optional': True}), ('cert_req', Boolean, {'default': False}), - ('extensions', Extensions, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), + ('extensions', Extensions, {'implicit': 0, 'optional': True}), ] @@ -201,9 +201,9 @@ class ArchiveTimeStamp(Sequence): _fields = [ - ('digest_algorithm', DigestAlgorithm, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), - ('attributes', Attributes, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), - ('reduced_hashtree', PartialHashtrees, {'tag_type': 'implicit', 'tag': 2, 'optional': True}), + ('digest_algorithm', DigestAlgorithm, {'implicit': 0, 'optional': True}), + ('attributes', Attributes, {'implicit': 1, 'optional': True}), + ('reduced_hashtree', PartialHashtrees, {'implicit': 2, 'optional': True}), ('time_stamp', ContentInfo), ] @@ -216,8 +216,8 @@ _fields = [ ('version', Version), ('digest_algorithms', DigestAlgorithms), - ('crypto_infos', Attributes, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), - ('encryption_info', EncryptionInfo, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('crypto_infos', Attributes, {'implicit': 0, 'optional': True}), + ('encryption_info', EncryptionInfo, {'implicit': 1, 'optional': True}), ('archive_time_stamp_sequence', ArchiveTimeStampSequence), ] @@ -231,9 +231,9 @@ class Evidence(Choice): _alternatives = [ - ('tst_evidence', TimeStampTokenEvidence, {'tag_type': 'implicit', 'tag': 0}), - ('ers_evidence', EvidenceRecord, {'tag_type': 'implicit', 'tag': 1}), - ('other_evidence', OtherEvidence, {'tag_type': 'implicit', 'tag': 2}), + ('tst_evidence', TimeStampTokenEvidence, {'implicit': 0}), + ('ers_evidence', EvidenceRecord, {'implicit': 1}), + ('other_evidence', OtherEvidence, {'implicit': 2}), ] diff -Nru asn1crypto-0.22.0/asn1crypto/version.py asn1crypto-0.24.0/asn1crypto/version.py --- asn1crypto-0.22.0/asn1crypto/version.py 2017-03-10 19:24:03.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto/version.py 2017-12-14 21:01:31.000000000 +0000 @@ -2,5 +2,5 @@ from __future__ import unicode_literals, division, absolute_import, print_function -__version__ = '0.22.0' -__version_info__ = (0, 22, 0) +__version__ = '0.24.0' +__version_info__ = (0, 24, 0) diff -Nru asn1crypto-0.22.0/asn1crypto/x509.py asn1crypto-0.24.0/asn1crypto/x509.py --- asn1crypto-0.22.0/asn1crypto/x509.py 2017-03-03 11:35:47.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto/x509.py 2017-12-14 21:01:31.000000000 +0000 @@ -15,6 +15,7 @@ from __future__ import unicode_literals, division, absolute_import, print_function +from contextlib import contextmanager from encodings import idna # noqa import hashlib import re @@ -27,7 +28,7 @@ from ._iri import iri_to_uri, uri_to_iri from ._ordereddict import OrderedDict from ._types import type_name, str_cls, bytes_to_list -from .algos import AlgorithmIdentifier, SignedDigestAlgorithm +from .algos import AlgorithmIdentifier, AnyAlgorithmIdentifier, DigestAlgorithm, SignedDigestAlgorithm from .core import ( Any, BitString, @@ -35,6 +36,7 @@ Boolean, Choice, Concat, + Enumerated, GeneralizedTime, GeneralString, IA5String, @@ -443,14 +445,47 @@ class PrivateKeyUsagePeriod(Sequence): _fields = [ - ('not_before', GeneralizedTime, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), - ('not_after', GeneralizedTime, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('not_before', GeneralizedTime, {'implicit': 0, 'optional': True}), + ('not_after', GeneralizedTime, {'implicit': 1, 'optional': True}), ] +class NotReallyTeletexString(TeletexString): + """ + OpenSSL (and probably some other libraries) puts ISO-8859-1 + into TeletexString instead of ITU T.61. We use Windows-1252 when + decoding since it is a superset of ISO-8859-1, and less likely to + cause encoding issues, but we stay strict with encoding to prevent + us from creating bad data. + """ + + _decoding_encoding = 'cp1252' + + def __unicode__(self): + """ + :return: + A unicode string + """ + + if self.contents is None: + return '' + if self._unicode is None: + self._unicode = self._merge_chunks().decode(self._decoding_encoding) + return self._unicode + + +@contextmanager +def strict_teletex(): + try: + NotReallyTeletexString._decoding_encoding = 'teletex' + yield + finally: + NotReallyTeletexString._decoding_encoding = 'cp1252' + + class DirectoryString(Choice): _alternatives = [ - ('teletex_string', TeletexString), + ('teletex_string', NotReallyTeletexString), ('printable_string', PrintableString), ('universal_string', UniversalString), ('utf8_string', UTF8String), @@ -483,6 +518,13 @@ '2.5.4.46': 'dn_qualifier', '2.5.4.65': 'pseudonym', '2.5.4.97': 'organization_identifier', + # https://www.trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf + '2.23.133.2.1': 'tpm_manufacturer', + '2.23.133.2.2': 'tpm_model', + '2.23.133.2.3': 'tpm_version', + '2.23.133.2.4': 'platform_manufacturer', + '2.23.133.2.5': 'platform_model', + '2.23.133.2.6': 'platform_version', # https://tools.ietf.org/html/rfc2985#page-26 '1.2.840.113549.1.9.1': 'email_address', # Page 10 of https://cabforum.org/wp-content/uploads/EV-V1_5_5.pdf @@ -525,6 +567,12 @@ 'domain_component', 'name_distinguisher', 'organization_identifier', + 'tpm_manufacturer', + 'tpm_model', + 'tpm_version', + 'platform_manufacturer', + 'platform_model', + 'platform_version', ] @classmethod @@ -582,6 +630,12 @@ 'domain_component': 'Domain Component', 'name_distinguisher': 'Name Distinguisher', 'organization_identifier': 'Organization Identifier', + 'tpm_manufacturer': 'TPM Manufacturer', + 'tpm_model': 'TPM Model', + 'tpm_version': 'TPM Version', + 'platform_manufacturer': 'Platform Manufacturer', + 'platform_model': 'Platform Model', + 'platform_version': 'Platform Version', }.get(self.native, self.native) @@ -622,6 +676,12 @@ 'domain_component': DNSName, 'name_distinguisher': DirectoryString, 'organization_identifier': DirectoryString, + 'tpm_manufacturer': UTF8String, + 'tpm_model': UTF8String, + 'tpm_version': UTF8String, + 'platform_manufacturer': UTF8String, + 'platform_model': UTF8String, + 'platform_version': UTF8String, } _prepped = None @@ -920,7 +980,7 @@ :param use_printable: A bool - if PrintableString should be used for encoding instead of - UTF8String. This is for backwards compatiblity with old software. + UTF8String. This is for backwards compatibility with old software. :return: An x509.Name object @@ -1096,7 +1156,7 @@ class AnotherName(Sequence): _fields = [ ('type_id', ObjectIdentifier), - ('value', Any, {'tag_type': 'explicit', 'tag': 0}), + ('value', Any, {'explicit': 0}), ] @@ -1129,19 +1189,19 @@ class PersonalName(Set): _fields = [ - ('surname', PrintableString, {'tag_type': 'implicit', 'tag': 0}), - ('given_name', PrintableString, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), - ('initials', PrintableString, {'tag_type': 'implicit', 'tag': 2, 'optional': True}), - ('generation_qualifier', PrintableString, {'tag_type': 'implicit', 'tag': 3, 'optional': True}), + ('surname', PrintableString, {'implicit': 0}), + ('given_name', PrintableString, {'implicit': 1, 'optional': True}), + ('initials', PrintableString, {'implicit': 2, 'optional': True}), + ('generation_qualifier', PrintableString, {'implicit': 3, 'optional': True}), ] class TeletexPersonalName(Set): _fields = [ - ('surname', TeletexString, {'tag_type': 'implicit', 'tag': 0}), - ('given_name', TeletexString, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), - ('initials', TeletexString, {'tag_type': 'implicit', 'tag': 2, 'optional': True}), - ('generation_qualifier', TeletexString, {'tag_type': 'implicit', 'tag': 3, 'optional': True}), + ('surname', TeletexString, {'implicit': 0}), + ('given_name', TeletexString, {'implicit': 1, 'optional': True}), + ('initials', TeletexString, {'implicit': 2, 'optional': True}), + ('generation_qualifier', TeletexString, {'implicit': 3, 'optional': True}), ] @@ -1157,13 +1217,13 @@ _fields = [ ('country_name', CountryName, {'optional': True}), ('administration_domain_name', AdministrationDomainName, {'optional': True}), - ('network_address', NumericString, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), - ('terminal_identifier', PrintableString, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), - ('private_domain_name', PrivateDomainName, {'tag_type': 'explicit', 'tag': 2, 'optional': True}), - ('organization_name', PrintableString, {'tag_type': 'implicit', 'tag': 3, 'optional': True}), - ('numeric_user_identifier', NumericString, {'tag_type': 'implicit', 'tag': 4, 'optional': True}), - ('personal_name', PersonalName, {'tag_type': 'implicit', 'tag': 5, 'optional': True}), - ('organizational_unit_names', OrganizationalUnitNames, {'tag_type': 'implicit', 'tag': 6, 'optional': True}), + ('network_address', NumericString, {'implicit': 0, 'optional': True}), + ('terminal_identifier', PrintableString, {'implicit': 1, 'optional': True}), + ('private_domain_name', PrivateDomainName, {'explicit': 2, 'optional': True}), + ('organization_name', PrintableString, {'implicit': 3, 'optional': True}), + ('numeric_user_identifier', NumericString, {'implicit': 4, 'optional': True}), + ('personal_name', PersonalName, {'implicit': 5, 'optional': True}), + ('organizational_unit_names', OrganizationalUnitNames, {'implicit': 6, 'optional': True}), ] @@ -1223,8 +1283,8 @@ class E1634Address(Sequence): _fields = [ - ('number', NumericString, {'tag_type': 'implicit', 'tag': 0}), - ('sub_address', NumericString, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('number', NumericString, {'implicit': 0}), + ('sub_address', NumericString, {'implicit': 1, 'optional': True}), ] @@ -1234,17 +1294,17 @@ class PresentationAddress(Sequence): _fields = [ - ('p_selector', OctetString, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), - ('s_selector', OctetString, {'tag_type': 'explicit', 'tag': 1, 'optional': True}), - ('t_selector', OctetString, {'tag_type': 'explicit', 'tag': 2, 'optional': True}), - ('n_addresses', NAddresses, {'tag_type': 'explicit', 'tag': 3}), + ('p_selector', OctetString, {'explicit': 0, 'optional': True}), + ('s_selector', OctetString, {'explicit': 1, 'optional': True}), + ('t_selector', OctetString, {'explicit': 2, 'optional': True}), + ('n_addresses', NAddresses, {'explicit': 3}), ] class ExtendedNetworkAddress(Choice): _alternatives = [ ('e163_4_address', E1634Address), - ('psap_address', PresentationAddress, {'tag_type': 'implicit', 'tag': 0}) + ('psap_address', PresentationAddress, {'implicit': 0}) ] @@ -1289,8 +1349,8 @@ class ExtensionAttribute(Sequence): _fields = [ - ('extension_attribute_type', ExtensionAttributeType, {'tag_type': 'implicit', 'tag': 0}), - ('extension_attribute_value', Any, {'tag_type': 'explicit', 'tag': 1}), + ('extension_attribute_type', ExtensionAttributeType, {'implicit': 0}), + ('extension_attribute_value', Any, {'explicit': 1}), ] _oid_pair = ('extension_attribute_type', 'extension_attribute_value') @@ -1335,22 +1395,22 @@ class EDIPartyName(Sequence): _fields = [ - ('name_assigner', DirectoryString, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), - ('party_name', DirectoryString, {'tag_type': 'implicit', 'tag': 1}), + ('name_assigner', DirectoryString, {'implicit': 0, 'optional': True}), + ('party_name', DirectoryString, {'implicit': 1}), ] class GeneralName(Choice): _alternatives = [ - ('other_name', AnotherName, {'tag_type': 'implicit', 'tag': 0}), - ('rfc822_name', EmailAddress, {'tag_type': 'implicit', 'tag': 1}), - ('dns_name', DNSName, {'tag_type': 'implicit', 'tag': 2}), - ('x400_address', ORAddress, {'tag_type': 'implicit', 'tag': 3}), - ('directory_name', Name, {'tag_type': 'explicit', 'tag': 4}), - ('edi_party_name', EDIPartyName, {'tag_type': 'implicit', 'tag': 5}), - ('uniform_resource_identifier', URI, {'tag_type': 'implicit', 'tag': 6}), - ('ip_address', IPAddress, {'tag_type': 'implicit', 'tag': 7}), - ('registered_id', ObjectIdentifier, {'tag_type': 'implicit', 'tag': 8}), + ('other_name', AnotherName, {'implicit': 0}), + ('rfc822_name', EmailAddress, {'implicit': 1}), + ('dns_name', DNSName, {'implicit': 2}), + ('x400_address', ORAddress, {'implicit': 3}), + ('directory_name', Name, {'explicit': 4}), + ('edi_party_name', EDIPartyName, {'implicit': 5}), + ('uniform_resource_identifier', URI, {'implicit': 6}), + ('ip_address', IPAddress, {'implicit': 7}), + ('registered_id', ObjectIdentifier, {'implicit': 8}), ] def __ne__(self, other): @@ -1417,16 +1477,16 @@ class AuthorityKeyIdentifier(Sequence): _fields = [ - ('key_identifier', OctetString, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), - ('authority_cert_issuer', GeneralNames, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), - ('authority_cert_serial_number', Integer, {'tag_type': 'implicit', 'tag': 2, 'optional': True}), + ('key_identifier', OctetString, {'implicit': 0, 'optional': True}), + ('authority_cert_issuer', GeneralNames, {'implicit': 1, 'optional': True}), + ('authority_cert_serial_number', Integer, {'implicit': 2, 'optional': True}), ] class DistributionPointName(Choice): _alternatives = [ - ('full_name', GeneralNames, {'tag_type': 'implicit', 'tag': 0}), - ('name_relative_to_crl_issuer', RelativeDistinguishedName, {'tag_type': 'implicit', 'tag': 1}), + ('full_name', GeneralNames, {'implicit': 0}), + ('name_relative_to_crl_issuer', RelativeDistinguishedName, {'implicit': 1}), ] @@ -1447,8 +1507,8 @@ class GeneralSubtree(Sequence): _fields = [ ('base', GeneralName), - ('minimum', Integer, {'tag_type': 'implicit', 'tag': 0, 'default': 0}), - ('maximum', Integer, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('minimum', Integer, {'implicit': 0, 'default': 0}), + ('maximum', Integer, {'implicit': 1, 'optional': True}), ] @@ -1458,16 +1518,16 @@ class NameConstraints(Sequence): _fields = [ - ('permitted_subtrees', GeneralSubtrees, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), - ('excluded_subtrees', GeneralSubtrees, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('permitted_subtrees', GeneralSubtrees, {'implicit': 0, 'optional': True}), + ('excluded_subtrees', GeneralSubtrees, {'implicit': 1, 'optional': True}), ] class DistributionPoint(Sequence): _fields = [ - ('distribution_point', DistributionPointName, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), - ('reasons', ReasonFlags, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), - ('crl_issuer', GeneralNames, {'tag_type': 'implicit', 'tag': 2, 'optional': True}), + ('distribution_point', DistributionPointName, {'explicit': 0, 'optional': True}), + ('reasons', ReasonFlags, {'implicit': 1, 'optional': True}), + ('crl_issuer', GeneralNames, {'implicit': 2, 'optional': True}), ] _url = False @@ -1493,7 +1553,7 @@ for general_name in name.chosen: if general_name.name == 'uniform_resource_identifier': url = general_name.native - if url[0:7] == 'http://': + if url.lower().startswith(('http://', 'https://', 'ldap://', 'ldaps://')): self._url = url break @@ -1585,8 +1645,8 @@ class PolicyConstraints(Sequence): _fields = [ - ('require_explicit_policy', Integer, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), - ('inhibit_policy_mapping', Integer, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('require_explicit_policy', Integer, {'implicit': 0, 'optional': True}), + ('inhibit_policy_mapping', Integer, {'implicit': 1, 'optional': True}), ] @@ -1650,6 +1710,8 @@ '1.3.6.1.4.1.311.10.3.12': 'microsoft_document_signing', '1.3.6.1.4.1.311.10.3.13': 'microsoft_lifetime_signing', '1.3.6.1.4.1.311.10.3.14': 'microsoft_mobile_device_software', + # https://support.microsoft.com/en-us/help/287547/object-ids-associated-with-microsoft-cryptography + '1.3.6.1.4.1.311.20.2.2': 'microsoft_smart_card_logon', # https://opensource.apple.com/source # - /Security/Security-57031.40.6/Security/libsecurity_keychain/lib/SecPolicy.cpp # - /libsecurity_cssm/libsecurity_cssm-36064/lib/oidsalg.c @@ -1685,6 +1747,16 @@ '1.2.840.113625.100.1.32': 'apple_test_smp_encryption', '1.2.840.113635.100.1.33': 'apple_server_authentication', '1.2.840.113635.100.1.34': 'apple_pcs_escrow_service', + # http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.201-2.pdf + '2.16.840.1.101.3.6.8': 'piv_card_authentication', + '2.16.840.1.101.3.6.7': 'piv_content_signing', + # https://tools.ietf.org/html/rfc4556.html + '1.3.6.1.5.2.3.4': 'pkinit_kpclientauth', + '1.3.6.1.5.2.3.5': 'pkinit_kpkdc', + # https://www.adobe.com/devnet-docs/acrobatetk/tools/DigSig/changes.html + '1.2.840.113583.1.1.5': 'adobe_authentic_documents_trust', + # https://www.idmanagement.gov/wp-content/uploads/sites/1171/uploads/fpki-pivi-cert-profiles.pdf + '2.16.840.1.101.3.8.7': 'fpki_pivi_content_signing' } @@ -1741,6 +1813,232 @@ } +class Version(Integer): + _map = { + 0: 'v1', + 1: 'v2', + 2: 'v3', + } + + +class TPMSpecification(Sequence): + _fields = [ + ('family', UTF8String), + ('level', Integer), + ('revision', Integer), + ] + + +class SetOfTPMSpecification(SetOf): + _child_spec = TPMSpecification + + +class TCGSpecificationVersion(Sequence): + _fields = [ + ('major_version', Integer), + ('minor_version', Integer), + ('revision', Integer), + ] + + +class TCGPlatformSpecification(Sequence): + _fields = [ + ('version', TCGSpecificationVersion), + ('platform_class', OctetString), + ] + + +class SetOfTCGPlatformSpecification(SetOf): + _child_spec = TCGPlatformSpecification + + +class EKGenerationType(Enumerated): + _map = { + 0: 'internal', + 1: 'injected', + 2: 'internal_revocable', + 3: 'injected_revocable', + } + + +class EKGenerationLocation(Enumerated): + _map = { + 0: 'tpm_manufacturer', + 1: 'platform_manufacturer', + 2: 'ek_cert_signer', + } + + +class EKCertificateGenerationLocation(Enumerated): + _map = { + 0: 'tpm_manufacturer', + 1: 'platform_manufacturer', + 2: 'ek_cert_signer', + } + + +class EvaluationAssuranceLevel(Enumerated): + _map = { + 1: 'level1', + 2: 'level2', + 3: 'level3', + 4: 'level4', + 5: 'level5', + 6: 'level6', + 7: 'level7', + } + + +class EvaluationStatus(Enumerated): + _map = { + 0: 'designed_to_meet', + 1: 'evaluation_in_progress', + 2: 'evaluation_completed', + } + + +class StrengthOfFunction(Enumerated): + _map = { + 0: 'basic', + 1: 'medium', + 2: 'high', + } + + +class URIReference(Sequence): + _fields = [ + ('uniform_resource_identifier', IA5String), + ('hash_algorithm', DigestAlgorithm, {'optional': True}), + ('hash_value', BitString, {'optional': True}), + ] + + +class CommonCriteriaMeasures(Sequence): + _fields = [ + ('version', IA5String), + ('assurance_level', EvaluationAssuranceLevel), + ('evaluation_status', EvaluationStatus), + ('plus', Boolean, {'default': False}), + ('strengh_of_function', StrengthOfFunction, {'implicit': 0, 'optional': True}), + ('profile_oid', ObjectIdentifier, {'implicit': 1, 'optional': True}), + ('profile_url', URIReference, {'implicit': 2, 'optional': True}), + ('target_oid', ObjectIdentifier, {'implicit': 3, 'optional': True}), + ('target_uri', URIReference, {'implicit': 4, 'optional': True}), + ] + + +class SecurityLevel(Enumerated): + _map = { + 1: 'level1', + 2: 'level2', + 3: 'level3', + 4: 'level4', + } + + +class FIPSLevel(Sequence): + _fields = [ + ('version', IA5String), + ('level', SecurityLevel), + ('plus', Boolean, {'default': False}), + ] + + +class TPMSecurityAssertions(Sequence): + _fields = [ + ('version', Version, {'default': 'v1'}), + ('field_upgradable', Boolean, {'default': False}), + ('ek_generation_type', EKGenerationType, {'implicit': 0, 'optional': True}), + ('ek_generation_location', EKGenerationLocation, {'implicit': 1, 'optional': True}), + ('ek_certificate_generation_location', EKCertificateGenerationLocation, {'implicit': 2, 'optional': True}), + ('cc_info', CommonCriteriaMeasures, {'implicit': 3, 'optional': True}), + ('fips_level', FIPSLevel, {'implicit': 4, 'optional': True}), + ('iso_9000_certified', Boolean, {'implicit': 5, 'default': False}), + ('iso_9000_uri', IA5String, {'optional': True}), + ] + + +class SetOfTPMSecurityAssertions(SetOf): + _child_spec = TPMSecurityAssertions + + +class SubjectDirectoryAttributeId(ObjectIdentifier): + _map = { + # https://tools.ietf.org/html/rfc2256#page-11 + '2.5.4.52': 'supported_algorithms', + # https://www.trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf + '2.23.133.2.16': 'tpm_specification', + '2.23.133.2.17': 'tcg_platform_specification', + '2.23.133.2.18': 'tpm_security_assertions', + # https://tools.ietf.org/html/rfc3739#page-18 + '1.3.6.1.5.5.7.9.1': 'pda_date_of_birth', + '1.3.6.1.5.5.7.9.2': 'pda_place_of_birth', + '1.3.6.1.5.5.7.9.3': 'pda_gender', + '1.3.6.1.5.5.7.9.4': 'pda_country_of_citizenship', + '1.3.6.1.5.5.7.9.5': 'pda_country_of_residence', + # https://holtstrom.com/michael/tools/asn1decoder.php + '1.2.840.113533.7.68.29': 'entrust_user_role', + } + + +class SetOfGeneralizedTime(SetOf): + _child_spec = GeneralizedTime + + +class SetOfDirectoryString(SetOf): + _child_spec = DirectoryString + + +class SetOfPrintableString(SetOf): + _child_spec = PrintableString + + +class SupportedAlgorithm(Sequence): + _fields = [ + ('algorithm_identifier', AnyAlgorithmIdentifier), + ('intended_usage', KeyUsage, {'explicit': 0, 'optional': True}), + ('intended_certificate_policies', CertificatePolicies, {'explicit': 1, 'optional': True}), + ] + + +class SetOfSupportedAlgorithm(SetOf): + _child_spec = SupportedAlgorithm + + +class SubjectDirectoryAttribute(Sequence): + _fields = [ + ('type', SubjectDirectoryAttributeId), + ('values', Any), + ] + + _oid_pair = ('type', 'values') + _oid_specs = { + 'supported_algorithms': SetOfSupportedAlgorithm, + 'tpm_specification': SetOfTPMSpecification, + 'tcg_platform_specification': SetOfTCGPlatformSpecification, + 'tpm_security_assertions': SetOfTPMSecurityAssertions, + 'pda_date_of_birth': SetOfGeneralizedTime, + 'pda_place_of_birth': SetOfDirectoryString, + 'pda_gender': SetOfPrintableString, + 'pda_country_of_citizenship': SetOfPrintableString, + 'pda_country_of_residence': SetOfPrintableString, + } + + def _values_spec(self): + type_ = self['type'].native + if type_ in self._oid_specs: + return self._oid_specs[type_] + return SetOf + + _spec_callbacks = { + 'values': _values_spec + } + + +class SubjectDirectoryAttributes(SequenceOf): + _child_spec = SubjectDirectoryAttribute + + class ExtensionId(ObjectIdentifier): _map = { '2.5.29.9': 'subject_directory_attributes', @@ -1766,6 +2064,8 @@ '1.3.6.1.5.5.7.48.1.5': 'ocsp_no_check', '1.2.840.113533.7.65.0': 'entrust_version_extension', '2.16.840.1.113730.1.1': 'netscape_certificate_type', + # https://tools.ietf.org/html/rfc6962.html#page-14 + '1.3.6.1.4.1.11129.2.4.2': 'signed_certificate_timestamp_list', } @@ -1778,7 +2078,7 @@ _oid_pair = ('extn_id', 'extn_value') _oid_specs = { - 'subject_directory_attributes': Attributes, + 'subject_directory_attributes': SubjectDirectoryAttributes, 'key_identifier': OctetString, 'key_usage': KeyUsage, 'private_key_usage_period': PrivateKeyUsagePeriod, @@ -1800,6 +2100,7 @@ 'ocsp_no_check': Null, 'entrust_version_extension': EntrustVersionInfo, 'netscape_certificate_type': NetscapeCertificateType, + 'signed_certificate_timestamp_list': OctetString, } @@ -1807,26 +2108,18 @@ _child_spec = Extension -class Version(Integer): - _map = { - 0: 'v1', - 1: 'v2', - 2: 'v3', - } - - class TbsCertificate(Sequence): _fields = [ - ('version', Version, {'tag_type': 'explicit', 'tag': 0, 'default': 'v1'}), + ('version', Version, {'explicit': 0, 'default': 'v1'}), ('serial_number', Integer), ('signature', SignedDigestAlgorithm), ('issuer', Name), ('validity', Validity), ('subject', Name), ('subject_public_key_info', PublicKeyInfo), - ('issuer_unique_id', OctetBitString, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), - ('subject_unique_id', OctetBitString, {'tag_type': 'implicit', 'tag': 2, 'optional': True}), - ('extensions', Extensions, {'tag_type': 'explicit', 'tag': 3, 'optional': True}), + ('issuer_unique_id', OctetBitString, {'implicit': 1, 'optional': True}), + ('subject_unique_id', OctetBitString, {'implicit': 2, 'optional': True}), + ('extensions', Extensions, {'explicit': 3, 'optional': True}), ] @@ -1856,6 +2149,7 @@ _extended_key_usage_value = None _authority_information_access_value = None _subject_information_access_value = None + _private_key_usage_period_value = None _tls_feature_value = None _ocsp_no_check_value = None _issuer_serial = None @@ -1902,18 +2196,32 @@ return self._critical_extensions @property + def private_key_usage_period_value(self): + """ + This extension is used to constrain the period over which the subject + private key may be used + + :return: + None or a PrivateKeyUsagePeriod object + """ + + if not self._processed_extensions: + self._set_extensions() + return self._private_key_usage_period_value + + @property def subject_directory_attributes_value(self): """ This extension is used to contain additional identification attributes about the subject. :return: - None or an Attributes object + None or a SubjectDirectoryAttributes object """ if not self._processed_extensions: self._set_extensions() - return self._key_identifier_value + return self._subject_directory_attributes @property def key_identifier_value(self): @@ -2374,7 +2682,7 @@ if location.name != 'uniform_resource_identifier': continue url = location.native - if url.lower()[0:7] == 'http://': + if url.lower().startswith(('http://', 'https://', 'ldap://', 'ldaps://')): output.append(url) return output @@ -2466,11 +2774,14 @@ def self_signed(self): """ :return: - A unicode string of "yes", "no" or "maybe". The "maybe" result will - be returned if the certificate does not contain a key identifier - extension, but is issued by the subject. In this case the - certificate signature will need to be verified using the subject - public key to determine a "yes" or "no" answer. + A unicode string of "no" or "maybe". The "maybe" result will + be returned if the certificate issuer and subject are the same. + If a key identifier and authority key identifier are present, + they will need to match otherwise "no" will be returned. + + To verify is a certificate is truly self-signed, the signature + will need to be verified. See the certvalidator package for + one possible solution. """ if self._self_signed is None: @@ -2478,9 +2789,9 @@ if self.self_issued: if self.key_identifier: if not self.authority_key_identifier: - self._self_signed = 'yes' + self._self_signed = 'maybe' elif self.authority_key_identifier == self.key_identifier: - self._self_signed = 'yes' + self._self_signed = 'maybe' else: self._self_signed = 'maybe' return self._self_signed @@ -2518,6 +2829,16 @@ self._sha256 = hashlib.sha256(self.dump()).digest() return self._sha256 + @property + def sha256_fingerprint(self): + """ + :return: + A unicode string of the SHA-256 hash, formatted using hex encoding + with a space between each pair of characters, all uppercase + """ + + return ' '.join('%02X' % c for c in bytes_to_list(self.sha256)) + def is_valid_domain_ip(self, domain_ip): """ Check if a domain name or IP address is valid according to the @@ -2670,10 +2991,10 @@ class CertificateAux(Sequence): _fields = [ ('trust', KeyPurposeIdentifiers, {'optional': True}), - ('reject', KeyPurposeIdentifiers, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), + ('reject', KeyPurposeIdentifiers, {'implicit': 0, 'optional': True}), ('alias', UTF8String, {'optional': True}), ('keyid', OctetString, {'optional': True}), - ('other', SequenceOfAlgorithmIdentifiers, {'tag_type': 'implicit', 'tag': 1, 'optional': True}), + ('other', SequenceOfAlgorithmIdentifiers, {'implicit': 1, 'optional': True}), ] diff -Nru asn1crypto-0.22.0/asn1crypto.egg-info/PKG-INFO asn1crypto-0.24.0/asn1crypto.egg-info/PKG-INFO --- asn1crypto-0.22.0/asn1crypto.egg-info/PKG-INFO 2017-03-15 13:32:49.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto.egg-info/PKG-INFO 2017-12-14 21:04:10.000000000 +0000 @@ -1,11 +1,12 @@ Metadata-Version: 1.1 Name: asn1crypto -Version: 0.22.0 +Version: 0.24.0 Summary: Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP Home-page: https://github.com/wbond/asn1crypto Author: wbond Author-email: will@wbond.net License: MIT +Description-Content-Type: UNKNOWN Description: Docs for this project are maintained at https://github.com/wbond/asn1crypto#readme. Keywords: asn1 crypto pki x509 certificate rsa dsa ec dh Platform: UNKNOWN diff -Nru asn1crypto-0.22.0/asn1crypto.egg-info/SOURCES.txt asn1crypto-0.24.0/asn1crypto.egg-info/SOURCES.txt --- asn1crypto-0.22.0/asn1crypto.egg-info/SOURCES.txt 2017-03-15 13:32:49.000000000 +0000 +++ asn1crypto-0.24.0/asn1crypto.egg-info/SOURCES.txt 2017-12-14 21:04:10.000000000 +0000 @@ -1,3 +1,7 @@ +LICENSE +MANIFEST.in +changelog.md +readme.md setup.py asn1crypto/__init__.py asn1crypto/_elliptic_curve.py @@ -29,4 +33,7 @@ asn1crypto.egg-info/dependency_links.txt asn1crypto.egg-info/top_level.txt asn1crypto/_perf/__init__.py -asn1crypto/_perf/_big_num_ctypes.py \ No newline at end of file +asn1crypto/_perf/_big_num_ctypes.py +docs/pem.md +docs/readme.md +docs/universal_types.md \ No newline at end of file diff -Nru asn1crypto-0.22.0/changelog.md asn1crypto-0.24.0/changelog.md --- asn1crypto-0.22.0/changelog.md 1970-01-01 00:00:00.000000000 +0000 +++ asn1crypto-0.24.0/changelog.md 2017-12-14 21:01:31.000000000 +0000 @@ -0,0 +1,339 @@ +# changelog + +## 0.24.0 + + - `x509.Certificate().self_signed` will no longer return `"yes"` under any + circumstances. This helps prevent confusion since the library does not + verify the signature. Instead a library like oscrypto should be used + to confirm if a certificate is self-signed. + - Added various OIDs to `x509.KeyPurposeId()` + - Added `x509.Certificate().private_key_usage_period_value` + - Added structures for parsing common subject directory attributes for + X.509 certificates, including `x509.SubjectDirectoryAttribute()` + - Added `algos.AnyAlgorithmIdentifier()` for situations where an + algorithm identifier may contain a digest, signed digest or encryption + algorithm OID + - Fixed a bug with `x509.Certificate().subject_directory_attributes_value` + not returning the correct value + - Fixed a bug where explicitly-tagged fields in a `core.Sequence()` would + not function properly when the field had a default value + - Fixed a bug with type checking in `pem.armor()` + +## 0.23.0 + + - Backwards compatibility break: the `tag_type`, `explicit_tag` and + `explicit_class` attributes on `core.Asn1Value` no longer exist and were + replaced by the `implicit` and `explicit` attributes. Field param dicts + may use the new `explicit` and `implicit` keys, or the old `tag_type` and + `tag` keys. The attribute changes will likely to have little to no impact + since they were primarily an implementation detail. + - Teletex strings used inside of X.509 certificates are now interpreted + using Windows-1252 (a superset of ISO-8859-1). This enables compatibility + with certificates generated by OpenSSL. Strict parsing of Teletex strings + can be retained by using the `x509.strict_teletex()` context manager. + - Added support for nested explicit tagging, supporting values that are + defined with explicit tagging and then added as a field of another + structure using explicit tagging. + - Fixed a `UnicodeDecodeError` when trying to find the (optional) dependency + OpenSSL on Python 2 + - Fixed `next_update` field of `crl.TbsCertList` to be optional + - Added the `x509.Certificate.sha256_fingerprint` property + - `x509.Certificate.ocsp_urls` and `x509.DistributionPoint.url` will now + return `https://`, `ldap://` and `ldaps://` URLs in addition to `http://`. + - Added CMS Attribute Protection definitions from RFC 6211 + - Added OIDs from RFC 6962 + +## 0.22.0 + + - Added `parser.peek()` + - Implemented proper support for BER-encoded indefinite length strings of + all kinds - `core.BitString`, `core.OctetString` and all of the `core` + classes that are natively represented as Python unicode strings + - Fixed a bug with encoding LDAP URLs in `x509.URI` + - Correct `x509.DNSName` to allow a leading `.`, such as when used with + `x509.NameConstraints` + - Fixed an issue with dumping the parsed contents of `core.Any` when + explicitly tagged + - Custom `setup.py clean` now accepts the short `-a` flag for compatibility + +## 0.21.1 + + - Fixed a regression where explicit tagging of a field containing a + `core.Choice` would result in an incorrect header + - Fixed a bug where an `IndexError` was being raised instead of a `ValueError` + when a value was truncated to not include enough bytes for the header + - Corrected the spec for the `value` field of `pkcs12.Attribute` + - Added support for `2.16.840.1.113894.746875.1.1` OID to + `pkcs12.AttributeType` + +## 0.21.0 + + - Added `core.load()` for loading standard, universal types without knowing + the spec beforehand + - Added a `strict` keyword arg to the various `load()` methods and functions in + `core` that checks for trailing data and raises a `ValueError` when found + - Added `asn1crypto.parser` submodule with `emit()` and `parse()` functions for + low-level integration + - Added `asn1crypto.version` for version introspection without side-effects + - Added `algos.DSASignature` + - Fixed a bug with the `_header` attribute of explicitly-tagged values only + containing the explicit tag header instead of both the explicit tag header + and the encapsulated value header + +## 0.20.0 + + - Added support for year 0 + - Added the OID for unique identifier to `x509.NameType` + - Fixed a bug creating the native representation of a `core.BitString` with + leading null bytes + - Added a `.cast()` method to allow converting between different + representations of the same data, e.g. `core.BitString` and + `core.OctetBitString` + +## 0.19.0 + + - Force `algos.DigestAlgorithm` to encoding `parameters` as `Null` when the + `algorithm` is `sha1`, `sha224`, `sha256`, `sha384` or `sha512` per RFC 4055 + - Resolved an issue where a BER-encoded indefinite-length value could not be + properly parsed when embedded inside of a `core.Sequence` or `core.Set` + - Fix `x509.Name.build()` to properly handle dotted OID type values + - `core.Choice` can now be constructed from a single-element `dict` or a + two-element `tuple` to allow for better usability when constructing values + from native Python values + - All `core` objects can now be passed to `print()` with an exception being + raised + +## 0.18.5 + + - Don't fail importing if `ctypes` or `_ctypes` is not available + +## 0.18.4 + + - `core.Sequence` will now raise an exception when an unknown field is provided + - Prevent `UnicodeDecodeError` on Python 2 when calling + `core.OctetString.debug()` + - Corrected the default value for the `hash_algorithm` field of + `tsp.ESSCertIDv2` + - Fixed a bug constructing a `cms.SignedData` object + - Ensure that specific RSA OIDs are always paired with `parameters` set to + `core.Null` + +## 0.18.3 + + - Fixed DER encoding of `core.BitString` when a `_map` is specified (i.e. a + "named bit list") to omit trailing zero bits. This fixes compliance of + various `x509` structures with RFC 5280. + - Corrected a side effect in `keys.PrivateKeyInfo.wrap()` that would cause the + original `keys.ECPrivateKey` structure to become corrupt + - `core.IntegerOctetString` now correctly encodes the integer as an unsigned + value when converting to bytes. Previously decoding was unsigned, but + encoding was signed. + - Fix `util.int_from_bytes()` on Python 2 to return `0` from an empty byte + string + +## 0.18.2 + + - Allow `_perf` submodule to be removed from source tree when embedding + +## 0.18.1 + + - Fixed DER encoding of `core.Set` and `core.SetOf` + - Fixed a bug in `x509.Name.build()` that could generate invalid DER encoding + - Improved exception messages when parsing nested structures via the `.native` + attribute + - `algos.SignedDigestAlgorithm` now ensures the `parameters` are set to + `Null` when `algorithm` is `sha224_rsa`, `sha256_rsa`, `sha384_rsa` or + `sha512_rsa`, per RFC 4055 + - Corrected the definition of `pdf.AdobeTimestamp` to mark the + `requires_auth` field as optional + - Add support for the OID `1.2.840.113549.1.9.16.2.14` to + `cms.CMSAttributeType` + - Improve attribute support for `cms.AttributeCertificateV2` + - Handle `cms.AttributeCertificateV2` when incorrectly tagged as + `cms.AttributeCertificateV1` in `cms.CertificateChoices` + +## 0.18.0 + + - Improved general parsing performance by 10-15% + - Add support for Windows XP + - Added `core.ObjectIdentifier.dotted` attribute to always return dotted + integer unicode string + - Added `core.ObjectIdentifier.map()` and `core.ObjectIdentifier.unmap()` + class methods to map dotted integer unicode strings to user-friendly unicode + strings and back + - Added various Apple OIDs to `x509.KeyPurposeId` + - Fixed a bug parsing nested indefinite-length-encoded values + - Fixed a bug with `x509.Certificate.issuer_alt_name_value` if it is the first + extension queried + - `keys.PublicKeyInfo.bit_size` and `keys.PrivateKeyInfo.bit_size` values are + now rounded up to the next closest multiple of 8 + +## 0.17.1 + + - Fix a bug in `x509.URI` parsing IRIs containing explicit port numbers on + Python 3.x + +## 0.17.0 + + - Added `x509.TrustedCertificate` for handling OpenSSL auxiliary certificate + information appended after a certificate + - Added `core.Concat` class for situations such as `x509.TrustedCertificate` + - Allow "broken" X.509 certificates to use `core.IA5String` where an + `x509.DirectoryString` should be used instead + - Added `keys.PrivateKeyInfo.public_key_info` attribute + - Added a bunch of OIDs to `x509.KeyPurposeId` + +## 0.16.0 + + - Added DH key exchange structures: `algos.KeyExchangeAlgorithm`, + `algos.KeyExchangeAlgorithmId` and `algos.DHParameters`. + - Added DH public key support to `keys.PublicKeyInfo`, + `keys.PublicKeyAlgorithm` and `keys.PublicKeyAlgorithmId`. New structures + include `keys.DomainParameters` and `keys.ValidationParms`. + +## 0.15.1 + + - Fixed `cms.CMSAttributes` to be a `core.SetOf` instead of `core.SequenceOf` + - `cms.CMSAttribute` can now parse unknown attribute contrustruct without an + exception being raised + - `x509.PolicyMapping` now uses `x509.PolicyIdentifier` for field types + - Fixed `pdf.RevocationInfoArchival` so that all fields are now of the type + `core.SequenceOf` instead of a single value + - Added support for the `name_distinguisher`, `telephone_number` and + `organization_identifier` OIDs to `x509.Name` + - Fixed `x509.Name.native` to not accidentally create nested lists when three + of more values for a single type are part of the name + - `x509.Name.human_friendly` now reverses the order of fields when the data + in an `x509.Name` was encoded in most-specific to least-specific order, which + is the opposite of the standard way of least-specific to most-specific. + - `x509.NameType.human_friendly` no longer raises an exception when an + unknown OID is encountered + - Raise a `ValueError` when parsing a `core.Set` and an unknown field is + encountered + +## 0.15.0 + + - Added support for the TLS feature extension from RFC 7633 + - `x509.Name.build()` now accepts a keyword parameter `use_printable` to force + string encoding to be `core.PrintableString` instead of `core.UTF8String` + - Added the functions `util.uri_to_iri()` and `util.iri_to_uri()` + - Changed `algos.SignedDigestAlgorithmId` to use the preferred OIDs when + mapping a unicode string name to an OID. Previously there were multiple OIDs + for some algorithms, and different OIDs would sometimes be selected due to + the fact that the `_map` `dict` is not ordered. + +## 0.14.1 + + - Fixed a bug generating `x509.Certificate.sha1_fingerprint` on Python 2 + +## 0.14.0 + + - Added the `x509.Certificate.sha1_fingerprint` attribute + +## 0.13.0 + + - Backwards compatibility break: the native representation of some + `algos.EncryptionAlgorithmId` values changed. `aes128` became `aes128_cbc`, + `aes192` became `aes192_cbc` and `aes256` became `aes256_cbc`. + - Added more OIDs to `algos.EncryptionAlgorithmId` + - Added more OIDs to `cms.KeyEncryptionAlgorithmId` + - `x509.Name.human_friendly` now properly supports multiple values per + `x509.NameTypeAndValue` object + - Added `ocsp.OCSPResponse.basic_ocsp_response` and + `ocsp.OCSPResponse.response_data` properties + - Added `algos.EncryptionAlgorithm.encryption_mode` property + - Fixed a bug with parsing times containing timezone offsets in Python 3 + - The `attributes` field of `csr.CertificationRequestInfo` is now optional, + for compatibility with other ASN.1 parsers + +## 0.12.2 + + - Correct `core.Sequence.__setitem__()` so set `core.VOID` to an optional + field when `None` is set + +## 0.12.1 + + - Fixed a `unicode`/`bytes` bug with `x509.URI.dump()` on Python 2 + +## 0.12.0 + + - Backwards Compatibility Break: `core.NoValue` was renamed to `core.Void` and + a singleton was added as `core.VOID` + - 20-30% improvement in parsing performance + - `core.Void` now implements `__nonzero__` + - `core.Asn1Value.copy()` now performs a deep copy + - All `core` value classes are now compatible with the `copy` module + - `core.SequenceOf` and `core.SetOf` now implement `__contains__` + - Added `x509.Name.__len__()` + - Fixed a bug where `core.Choice.validate()` would not properly account for + explicit tagging + - `core.Choice.load()` now properly passes itself as the spec when parsing + - `x509.Certificate.crl_distribution_points` no longer throws an exception if + the `DistributionPoint` does not have a value for the `distribution_point` + field + +## 0.11.1 + + - Corrected `core.UTCTime` to interpret year <= 49 as 20xx and >= 50 as 19xx + - `keys.PublicKeyInfo.hash_algo` can now handle DSA keys without parameters + - Added `crl.CertificateList.sha256` and `crl.CertificateList.sha1` + - Fixed `x509.Name.build()` to properly encode `country_name`, `serial_number` + and `dn_qualifier` as `core.PrintableString` as specified in RFC 5280, + instead of `core.UTF8String` + +## 0.11.0 + + - Added Python 2.6 support + - Added ability to compare primitive type objects + - Implemented proper support for internationalized domains, URLs and email + addresses in `x509.Certificate` + - Comparing `x509.Name` and `x509.GeneralName` objects adheres to RFC 5280 + - `x509.Certificate.self_signed` and `x509.Certificate.self_issued` no longer + require that certificate is for a CA + - Fixed `x509.Certificate.valid_domains` to adhere to RFC 6125 + - Added `x509.Certificate.is_valid_domain_ip()` + - Added `x509.Certificate.sha1` and `x509.Certificate.sha256` + - Exposed `util.inet_ntop()` and `util.inet_pton()` for IP address encoding + - Improved exception messages for improper types to include type's module name + +## 0.10.1 + + - Fixed bug in `core.Sequence` affecting Python 2.7 and pypy + +## 0.10.0 + + - Added PEM encoding/decoding functionality + - `core.BitString` now uses item access instead of attributes for named bit + access + - `core.BitString.native` now uses a `set` of unicode strings when `_map` is + present + - Removed `core.Asn1Value.pprint()` method + - Added `core.ParsableOctetString` class + - Added `core.ParsableOctetBitString` class + - Added `core.Asn1Value.copy()` method + - Added `core.Asn1Value.debug()` method + - Added `core.SequenceOf.append()` method + - Added `core.Sequence.spec()` and `core.SequenceOf.spec()` methods + - Added correct IP address parsing to `x509.GeneralName` + - `x509.Name` and `x509.GeneralName` are now compared according to rules in + RFC 5280 + - Added convenience attributes to: + - `algos.SignedDigestAlgorithm` + - `crl.CertificateList` + - `crl.RevokedCertificate` + - `keys.PublicKeyInfo` + - `ocsp.OCSPRequest` + - `ocsp.Request` + - `ocsp.OCSPResponse` + - `ocsp.SingleResponse` + - `x509.Certificate` + - `x509.Name` + - Added `asn1crypto.util` module with the following items: + - `int_to_bytes()` + - `int_from_bytes()` + - `timezone.utc` + - Added `setup.py clean` command + +## 0.9.0 + + - Initial release diff -Nru asn1crypto-0.22.0/debian/changelog asn1crypto-0.24.0/debian/changelog --- asn1crypto-0.22.0/debian/changelog 2017-07-04 16:51:26.000000000 +0000 +++ asn1crypto-0.24.0/debian/changelog 2017-12-23 23:26:32.000000000 +0000 @@ -1,8 +1,17 @@ -asn1crypto (0.22.0-1~cloud0) xenial-pike; urgency=medium +asn1crypto (0.24.0-1~cloud0) xenial-queens; urgency=medium - * New package for the Ubuntu Cloud Archive. + * New update for the Ubuntu Cloud Archive. - -- Openstack Ubuntu Testing Bot Tue, 04 Jul 2017 16:51:26 +0000 + -- Openstack Ubuntu Testing Bot Sat, 23 Dec 2017 23:26:32 +0000 + +asn1crypto (0.24.0-1) unstable; urgency=medium + + * Add some missing files to debian/copyright. + * New upstream release. + * Bump Standards-Version to 4.1.2 (no changes). + * Upstream now ships a changelog, drop Lintian override. + + -- Tristan Seligmann Sat, 23 Dec 2017 20:29:58 +0200 asn1crypto (0.22.0-1) unstable; urgency=low diff -Nru asn1crypto-0.22.0/debian/control asn1crypto-0.24.0/debian/control --- asn1crypto-0.22.0/debian/control 2017-06-20 18:53:15.000000000 +0000 +++ asn1crypto-0.24.0/debian/control 2017-12-23 18:29:58.000000000 +0000 @@ -11,7 +11,7 @@ python-setuptools, python3-all, python3-setuptools, -Standards-Version: 4.0.0 +Standards-Version: 4.1.2 Homepage: https://github.com/wbond/asn1crypto Vcs-Git: https://anonscm.debian.org/git/python-modules/packages/asn1crypto.git Vcs-Browser: https://anonscm.debian.org/git/python-modules/packages/asn1crypto.git diff -Nru asn1crypto-0.22.0/debian/copyright asn1crypto-0.24.0/debian/copyright --- asn1crypto-0.22.0/debian/copyright 2017-06-20 18:53:15.000000000 +0000 +++ asn1crypto-0.24.0/debian/copyright 2017-12-23 18:29:58.000000000 +0000 @@ -7,6 +7,14 @@ Copyright: 2015-2017 Will Bond License: Expat +Files: asn1crypto/_ordereddict.py +Copyright: 2009 Raymond Hettinger +License: Expat + +Files: asn1crypto/_elliptic_curve.py +Copyright: 2014 Peter Pearson +License: Expat + Files: debian/* Copyright: 2017 Tristan Seligmann License: Expat diff -Nru asn1crypto-0.22.0/debian/pypy-asn1crypto.lintian-overrides asn1crypto-0.24.0/debian/pypy-asn1crypto.lintian-overrides --- asn1crypto-0.22.0/debian/pypy-asn1crypto.lintian-overrides 2017-06-20 18:53:15.000000000 +0000 +++ asn1crypto-0.24.0/debian/pypy-asn1crypto.lintian-overrides 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -# Upstream does not ship the changelog / docs -pypy-asn1crypto: no-upstream-changelog diff -Nru asn1crypto-0.22.0/debian/python3-asn1crypto.lintian-overrides asn1crypto-0.24.0/debian/python3-asn1crypto.lintian-overrides --- asn1crypto-0.22.0/debian/python3-asn1crypto.lintian-overrides 2017-06-20 18:53:15.000000000 +0000 +++ asn1crypto-0.24.0/debian/python3-asn1crypto.lintian-overrides 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -# Upstream does not ship the changelog / docs -python3-asn1crypto: no-upstream-changelog diff -Nru asn1crypto-0.22.0/debian/python-asn1crypto.lintian-overrides asn1crypto-0.24.0/debian/python-asn1crypto.lintian-overrides --- asn1crypto-0.22.0/debian/python-asn1crypto.lintian-overrides 2017-06-20 18:53:15.000000000 +0000 +++ asn1crypto-0.24.0/debian/python-asn1crypto.lintian-overrides 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -# Upstream does not ship the changelog / docs -python-asn1crypto: no-upstream-changelog diff -Nru asn1crypto-0.22.0/docs/pem.md asn1crypto-0.24.0/docs/pem.md --- asn1crypto-0.22.0/docs/pem.md 1970-01-01 00:00:00.000000000 +0000 +++ asn1crypto-0.24.0/docs/pem.md 2015-07-15 14:52:41.000000000 +0000 @@ -0,0 +1,79 @@ +# PEM Decoder and Encoder + +Often times DER-encoded data is wrapped in PEM encoding. This allows the binary +DER data to be identified and reliably sent over various communication channels. + +The `asn1crypto.pem` module includes three functions: + + - `detect(byte_string)` + - `unarmor(pem_bytes, multiple=False)` + - `armor(type_name, der_bytes, headers=None)` + +## detect() + +The `detect()` function accepts a byte string and looks for a `BEGIN` block +line. This is useful to determine in a byte string needs to be PEM-decoded +before parsing. + +```python +from asn1crypto import pem, x509 + +with open('/path/to/cert', 'rb') as f: + der_bytes = f.read() + if pem.detect(der_bytes): + _, _, der_bytes = pem.unarmor(der_bytes) +``` + +## unarmor() + +The `unarmor()` function accepts a byte string and the flag to indicates if +more than one PEM block may be contained in the byte string. The result is +a three-element tuple. + + - The first element is a unicode string of the type of PEM block. Examples + include: `CERTIFICATE`, `PRIVATE KEY`, `PUBLIC KEY`. + - The second element is a `dict` of PEM block headers. Headers are typically + only used by encrypted OpenSSL private keys, and are in the format + `Name: Value`. + - The third element is a byte string of the decoded block contents. + +```python +from asn1crypto import pem, x509 + +with open('/path/to/cert', 'rb') as f: + der_bytes = f.read() + if pem.detect(der_bytes): + type_name, headers, der_bytes = pem.unarmor(der_bytes) + +cert = x509.Certificate.load(der_bytes) +``` + +If the `multiple` keyword argument is set to `True`, a generator will be +returned. + +```python +from asn1crypto import pem, x509 + +certs = [] +with open('/path/to/ca_certs', 'rb') as f: + for type_name, headers, der_bytes in pem.unarmor(f.read(), multiple=True): + certs.append(x509.Certificate.load(der_bytes)) +``` + +## armor() + +The `armor()` function accepts three parameters: a unicode string of the block +type name, a byte string to encode and an optional keyword argument `headers`, +that should be a `dict` of headers to add after the `BEGIN` line. Headers are +typically only used by encrypted OpenSSL private keys. + +```python +from asn1crypto import pem, x509 + +# cert is an instance of x509.Certificate + +with open('/path/to/cert', 'wb') as f: + der_bytes = cert.dump() + pem_bytes = pem.armor('CERTIFICATE', der_bytes) + f.write(pem_bytes) +``` diff -Nru asn1crypto-0.22.0/docs/readme.md asn1crypto-0.24.0/docs/readme.md --- asn1crypto-0.22.0/docs/readme.md 1970-01-01 00:00:00.000000000 +0000 +++ asn1crypto-0.24.0/docs/readme.md 2015-08-06 16:39:32.000000000 +0000 @@ -0,0 +1,23 @@ +# asn1crypto Documentation + +The documentation for *asn1crypto* is composed of tutorials on basic usage and +links to the source for the various pre-defined type classes. + +## Tutorials + + - [Universal Types with BER/DER Decoder and DER Encoder](universal_types.md) + - [PEM Decoder and Encoder](pem.md) + +## Reference + + - [Universal types](../asn1crypto/core.py), `asn1crypto.core` + - [Digest, HMAC, signed digest and encryption algorithms](../asn1crypto/algos.py), `asn1crypto.algos` + - [Private and public keys](../asn1crypto/keys.py), `asn1crypto.keys` + - [X.509 certificates](../asn1crypto/x509.py), `asn1crypto.x509` + - [Certificate revocation lists (CRLs)](../asn1crypto/crl.py), `asn1crypto.crl` + - [Online certificate status protocol (OCSP)](../asn1crypto/ocsp.py), `asn1crypto.ocsp` + - [Certificate signing requests (CSRs)](../asn1crypto/csr.py), `asn1crypto.csr` + - [Private key/certificate containers (PKCS#12)](../asn1crypto/pkcs12.py), `asn1crypto.pkcs12` + - [Cryptographic message syntax (CMS, PKCS#7)](../asn1crypto/cms.py), `asn1crypto.cms` + - [Time stamp protocol (TSP)](../asn1crypto/tsp.py), `asn1crypto.tsp` + - [PDF signatures](../asn1crypto/pdf.py), `asn1crypto.pdf` diff -Nru asn1crypto-0.22.0/docs/universal_types.md asn1crypto-0.24.0/docs/universal_types.md --- asn1crypto-0.22.0/docs/universal_types.md 1970-01-01 00:00:00.000000000 +0000 +++ asn1crypto-0.24.0/docs/universal_types.md 2017-09-15 10:54:40.000000000 +0000 @@ -0,0 +1,675 @@ +# Universal Types with BER/DER Decoder and DER Encoder + +The *asn1crypto* library is a combination of universal type classes that +implement BER/DER decoding and DER encoding, a PEM encoder and decoder, and a +number of pre-built cryptographic type classes. This document covers the +universal type classes. + +For a general overview of ASN.1 as used in cryptography, please see +[A Layman's Guide to a Subset of ASN.1, BER, and DER](http://luca.ntop.org/Teaching/Appunti/asn1.html). + +This page contains the following sections: + + - [Universal Types](#universal-types) + - [Basic Usage](#basic-usage) + - [Sequence](#sequence) + - [Set](#set) + - [SequenceOf](#sequenceof) + - [SetOf](#setof) + - [Integer](#integer) + - [Enumerated](#enumerated) + - [ObjectIdentifier](#objectidentifier) + - [BitString](#bitstring) + - [Strings](#strings) + - [UTCTime](#utctime) + - [GeneralizedTime](#generalizedtime) + - [Choice](#choice) + - [Any](#any) + - [Specification via OID](#specification-via-oid) + - [Explicit and Implicit Tagging](#explicit-and-implicit-tagging) + +## Universal Types + +For general purpose ASN.1 parsing, the `asn1crypto.core` module is used. It +contains the following classes, that parse, represent and serialize all of the +ASN.1 universal types: + +| Class | Native Type | Implementation Notes | +| ------------------ | -------------------------------------- | ------------------------------------ | +| `Boolean` | `bool` | | +| `Integer` | `int` | may be `long` on Python 2 | +| `BitString` | `tuple` of `int` or `set` of `unicode` | `set` used if `_map` present | +| `OctetString` | `bytes` (`str`) | | +| `Null` | `None` | | +| `ObjectIdentifier` | `str` (`unicode`) | string is dotted integer format | +| `ObjectDescriptor` | | no native conversion | +| `InstanceOf` | | no native conversion | +| `Real` | | no native conversion | +| `Enumerated` | `str` (`unicode`) | `_map` must be set | +| `UTF8String` | `str` (`unicode`) | | +| `RelativeOid` | `str` (`unicode`) | string is dotted integer format | +| `Sequence` | `OrderedDict` | | +| `SequenceOf` | `list` | | +| `Set` | `OrderedDict` | | +| `SetOf` | `list` | | +| `EmbeddedPdv` | `OrderedDict` | no named field parsing | +| `NumericString` | `str` (`unicode`) | no charset limitations | +| `PrintableString` | `str` (`unicode`) | no charset limitations | +| `TeletexString` | `str` (`unicode`) | | +| `VideotexString` | `bytes` (`str`) | no unicode conversion | +| `IA5String` | `str` (`unicode`) | | +| `UTCTime` | `datetime.datetime` | | +| `GeneralizedTime` | `datetime.datetime` | treated as UTC when no timezone | +| `GraphicString` | `str` (`unicode`) | unicode conversion as latin1 | +| `VisibleString` | `str` (`unicode`) | no charset limitations | +| `GeneralString` | `str` (`unicode`) | unicode conversion as latin1 | +| `UniversalString` | `str` (`unicode`) | | +| `CharacterString` | `str` (`unicode`) | unicode conversion as latin1 | +| `BMPString` | `str` (`unicode`) | | + +For *Native Type*, the Python 3 type is listed first, with the Python 2 type +in parentheses. + +As mentioned next to some of the types, value parsing may not be implemented +for types not currently used in cryptography (such as `ObjectDescriptor`, +`InstanceOf` and `Real`). Additionally some of the string classes don't +enforce character set limitations, and for some string types that accept all +different encodings, the default encoding is set to latin1. + +In addition, there are a few overridden types where various specifications use +a `BitString` or `OctetString` type to represent a different type. These +include: + +| Class | Native Type | Implementation Notes | +| -------------------- | ------------------- | ------------------------------- | +| `OctetBitString` | `bytes` (`str`) | | +| `IntegerBitString` | `int` | may be `long` on Python 2 | +| `IntegerOctetString` | `int` | may be `long` on Python 2 | + +For situations where the DER encoded bytes from one type is embedded in another, +the `ParsableOctetString` and `ParsableOctetBitString` classes exist. These +function the same as `OctetString` and `OctetBitString`, however they also +have an attribute `.parsed` and a method `.parse()` that allows for +parsing the content as ASN.1 structures. + +All of these overrides can be used with the `cast()` method to convert between +them. The only requirement is that the class being casted to has the same tag +as the original class. No re-encoding is done, rather the contents are simply +re-interpreted. + +```python +from asn1crypto.core import BitString, OctetBitString, IntegerBitString + +bit = BitString({ + 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 1, 0, +}) + +# Will print (0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0) +print(bit.native) + +octet = bit.cast(OctetBitString) + +# Will print b'\x01\x02' +print(octet.native) + +i = bit.cast(IntegerBitString) + +# Will print 258 +print(i.native) +``` + +## Basic Usage + +All of the universal types implement four methods, a class method `.load()` and +the instance methods `.dump()`, `.copy()` and `.debug()`. + +`.load()` accepts a byte string of DER or BER encoded data and returns an +object of the class it was called on. `.dump()` returns the serialization of +an object into DER encoding. + +```python +from asn1crypto.core import Sequence + +parsed = Sequence.load(der_byte_string) +serialized = parsed.dump() +``` + +By default, *asn1crypto* tries to be efficient and caches serialized data for +better performance. If the input data is possibly BER encoded, but the output +must be DER encoded, the `force` parameter may be used with `.dump()`. + +```python +from asn1crypto.core import Sequence + +parsed = Sequence.load(der_byte_string) +der_serialized = parsed.dump(force=True) +``` + +The `.copy()` method creates a deep copy of an object, allowing child fields to +be modified without affecting the original. + +```python +from asn1crypto.core import Sequence + +seq1 = Sequence.load(der_byte_string) +seq2 = seq1.copy() +seq2[0] = seq1[0] + 1 +if seq1[0] != seq2[0]: + print('Copies have distinct contents') +``` + +The `.debug()` method is available to help in situations where interaction with +another ASN.1 serializer or parsing is not functioning as expected. Calling +this method will print a tree structure with information about the header bytes, +class, method, tag, special tagging, content bytes, native Python value, child +fields and any sub-parsed values. + +```python +from asn1crypto.core import Sequence + +parsed = Sequence.load(der_byte_string) +parsed.debug() +``` + +In addition to the available methods, every instance has a `.native` property +that converts the data into a native Python data type. + +```python +import pprint +from asn1crypto.core import Sequence + +parsed = Sequence.load(der_byte_string) +pprint(parsed.native) +``` + +## Sequence + +One of the core structures when dealing with ASN.1 is the Sequence type. The +`Sequence` class can handle field with universal data types, however in most +situations the `_fields` property will need to be set with the expected +definition of each field in the Sequence. + +### Configuration + +The `_fields` property must be set to a `list` of 2-3 element `tuple`s. The +first element in the tuple must be a unicode string of the field name. The +second must be a type class - either a universal type, or a custom type. The +third, and optional, element is a `dict` with parameters to pass to the type +class for things like default values, marking the field as optional, or +implicit/explicit tagging. + +```python +from asn1crypto.core import Sequence, Integer, OctetString, IA5String + +class MySequence(Sequence): + _fields = [ + ('field_one', Integer), + ('field_two', OctetString), + ('field_three', IA5String, {'optional': True}), + ] +``` + +Implicit and explicit tagging will be covered in more detail later, however +the following are options that can be set for each field type class: + + - `{'default: 1}` sets the field's default value to `1`, allowing it to be + omitted from the serialized form + - `{'optional': True}` set the field to be optional, allowing it to be + omitted + +### Usage + +To access values of the sequence, use dict-like access via `[]` and use the +name of the field: + +```python +seq = MySequence.load(der_byte_string) +print(seq['field_two'].native) +``` + +The values of fields can be set by assigning via `[]`. If the value assigned is +of the correct type class, it will be used as-is. If the value is not of the +correct type class, a new instance of that type class will be created and the +value will be passed to the constructor. + +```python +seq = MySequence.load(der_byte_string) +# These statements will result in the same state +seq['field_one'] = Integer(5) +seq['field_one'] = 5 +``` + +When fields are complex types such as `Sequence` or `SequenceOf`, there is no +way to construct the value out of a native Python data type. + +### Optional Fields + +When a field is configured via the `optional` parameter, not present in the +`Sequence`, but accessed, the `VOID` object will be returned. This is an object +that is serialized to an empty byte string and returns `None` when `.native` is +accessed. + +## Set + +The `Set` class is configured in the same was as `Sequence`, however it allows +serialized fields to be in any order, per the ASN.1 standard. + +```python +from asn1crypto.core import Set, Integer, OctetString, IA5String + +class MySet(Set): + _fields = [ + ('field_one', Integer), + ('field_two', OctetString), + ('field_three', IA5String, {'optional': True}), + ] +``` + +## SequenceOf + +The `SequenceOf` class is used to allow for zero or more instances of a type. +The class uses the `_child_spec` property to define the instance class type. + +```python +from asn1crypto.core import SequenceOf, Integer + +class Integers(SequenceOf): + _child_spec = Integer +``` + +Values in the `SequenceOf` can be accessed via `[]` with an integer key. The +length of the `SequenceOf` is determined via `len()`. + +```python +values = Integers.load(der_byte_string) +for i in range(0, len(values)): + print(values[i].native) +``` + +## SetOf + +The `SetOf` class is an exact duplicate of `SequenceOf`. According to the ASN.1 +standard, the difference is that a `SequenceOf` is explicitly ordered, however +`SetOf` may be in any order. This is an equivalent comparison of a Python `list` +and `set`. + +```python +from asn1crypto.core import SetOf, Integer + +class Integers(SetOf): + _child_spec = Integer +``` + +## Integer + +The `Integer` class allows values to be *named*. An `Integer` with named values +may contain any integer, however special values with named will be represented +as those names when `.native` is called. + +Named values are configured via the `_map` property, which must be a `dict` +with the keys being integers and the values being unicode strings. + +```python +from asn1crypto.core import Integer + +class Version(Integer): + _map = { + 1: 'v1', + 2: 'v2', + } + +# Will print: "v1" +print(Version(1).native) + +# Will print: 4 +print(Version(4).native) +``` + +## Enumerated + +The `Enumerated` class is almost identical to `Integer`, however only values in +the `_map` property are valid. + +```python +from asn1crypto.core import Enumerated + +class Version(Enumerated): + _map = { + 1: 'v1', + 2: 'v2', + } + +# Will print: "v1" +print(Version(1).native) + +# Will raise a ValueError exception +print(Version(4).native) +``` + +## ObjectIdentifier + +The `ObjectIdentifier` class represents values of the ASN.1 type of the same +name. `ObjectIdentifier` instances are converted to a unicode string in a +dotted-integer format when `.native` is accessed. + +While this standard conversion is a reasonable baseline, in most situations +it will be more maintainable to map the OID strings to a unicode string +containing a description of what the OID repesents. + +The mapping of OID strings to name strings is configured via the `_map` +property, which is a `dict` object with keys being unicode OID string and the +values being a unicode string. + +The `.dotted` attribute will always return a unicode string of the dotted +integer form of the OID. + +The class methods `.map()` and `.unmap()` will convert a dotted integer unicode +string to the user-friendly name, and vice-versa. + +```python +from asn1crypto.core import ObjectIdentifier + +class MyType(ObjectIdentifier): + _map = { + '1.8.2.1.23': 'value_name', + '1.8.2.1.24': 'other_value', + } + +# Will print: "value_name" +print(MyType('1.8.2.1.23').native) + +# Will print: "1.8.2.1.23" +print(MyType('1.8.2.1.23').dotted) + +# Will print: "1.8.2.1.25" +print(MyType('1.8.2.1.25').native) + +# Will print "value_name" +print(MyType.map('1.8.2.1.23')) + +# Will print "1.8.2.1.23" +print(MyType.unmap('value_name')) +``` + +## BitString + +When no `_map` is set for a `BitString` class, the native representation is a +`tuple` of `int`s (being either `1` or `0`). + +```python +from asn1crypto.core import BitString + +b1 = BitString((1, 0, 1)) +``` + +Additionally, it is possible to set the `_map` property to a dict where the +keys are bit indexes and the values are unicode string names. This allows +checking the value of a given bit by item access, and the native representation +becomes a `set` of unicode strings. + +```python +from asn1crypto.core import BitString + +class MyFlags(BitString): + _map = { + 0: 'edit', + 1: 'delete', + 2: 'manage_users', + } + +permissions = MyFlags({'edit', 'delete'}) + +# This will be printed +if permissions['edit'] and permissions['delete']: + print('Can edit and delete') + +# This will not +if 'manage_users' in permissions.native: + print('Is admin') +``` + +## Strings + +ASN.1 contains quite a number of string types: + +| Type | Standard Encoding | Implementation Encoding | Notes | +| ----------------- | --------------------------------- | ----------------------- | ------------------------------------------------------------------------- | +| `UTF8String` | UTF-8 | UTF-8 | | +| `NumericString` | ASCII `[0-9 ]` | ISO 8859-1 | The implementation is a superset of supported characters | +| `PrintableString` | ASCII `[a-zA-Z0-9 '()+,\\-./:=?]` | ISO 8859-1 | The implementation is a superset of supported characters | +| `TeletexString` | ITU T.61 | Custom | The implementation is based off of https://en.wikipedia.org/wiki/ITU_T.61 | +| `VideotexString` | *?* | *None* | This has no set encoding, and it not used in cryptography | +| `IA5String` | ITU T.50 (very similar to ASCII) | ISO 8859-1 | The implementation is a superset of supported characters | +| `GraphicString` | * | ISO 8859-1 | This has not set encoding, but seems to often contain ISO 8859-1 | +| `VisibleString` | ASCII (printable) | ISO 8859-1 | The implementation is a superset of supported characters | +| `GeneralString` | * | ISO 8859-1 | This has not set encoding, but seems to often contain ISO 8859-1 | +| `UniversalString` | UTF-32 | UTF-32 | | +| `CharacterString` | * | ISO 8859-1 | This has not set encoding, but seems to often contain ISO 8859-1 | +| `BMPString` | UTF-16 | UTF-16 | | + +As noted in the table above, many of the implementations are supersets of the +supported characters. This simplifies parsing, but puts the onus of using valid +characters on the developer. However, in general `UTF8String`, `BMPString` or +`UniversalString` should be preferred when a choice is given. + +All string types other than `VideotexString` are created from unicode strings. + +```python +from asn1crypto.core import IA5String + +print(IA5String('Testing!').native) +``` + +## UTCTime + +The class `UTCTime` accepts a unicode string in one of the formats: + + - `%y%m%d%H%MZ` + - `%y%m%d%H%M%SZ` + - `%y%m%d%H%M%z` + - `%y%m%d%H%M%S%z` + +or a `datetime.datetime` instance. See the +[Python datetime strptime() reference](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior) +for details of the formats. + +When `.native` is accessed, it returns a `datetime.datetime` object with a +`tzinfo` of `asn1crypto.util.timezone.utc`. + +## GeneralizedTime + +The class `GeneralizedTime` accepts a unicode string in one of the formats: + + - `%Y%m%d%H` + - `%Y%m%d%H%M` + - `%Y%m%d%H%M%S` + - `%Y%m%d%H%M%S.%f` + - `%Y%m%d%HZ` + - `%Y%m%d%H%MZ` + - `%Y%m%d%H%M%SZ` + - `%Y%m%d%H%M%S.%fZ` + - `%Y%m%d%H%z` + - `%Y%m%d%H%M%z` + - `%Y%m%d%H%M%S%z` + - `%Y%m%d%H%M%S.%f%z` + +or a `datetime.datetime` instance. See the +[Python datetime strptime() reference](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior) +for details of the formats. + +When `.native` is accessed, it returns a `datetime.datetime` object with a +`tzinfo` of `asn1crypto.util.timezone.utc`. For formats where the time has a +timezone offset is specified (`[+-]\d{4}`), the time is converted to UTC. For +times without a timezone, the time is assumed to be in UTC. + +## Choice + +The `Choice` class allows handling ASN.1 Choice structures. The `_alternatives` +property must be set to a `list` containing 2-3 element `tuple`s. The first +element in the tuple is the alternative name. The second element is the type +class for the alternative. The, optional, third element is a `dict` of +parameters to pass to the type class constructor. This is used primarily for +implicit and explicit tagging. + +```python +from asn1crypto.core import Choice, Integer, OctetString, IA5String + +class MyChoice(Choice): + _alternatives = [ + ('option_one', Integer), + ('option_two', OctetString), + ('option_three', IA5String), + ] +``` + +`Choice` objects has two extra properties, `.name` and `.chosen`. The `.name` +property contains the name of the chosen alternative. The `.chosen` property +contains the instance of the chosen type class. + +```python +parsed = MyChoice.load(der_bytes) +print(parsed.name) +print(type(parsed.chosen)) +``` + +The `.native` property and `.dump()` method work as with the universal type +classes. Under the hood they just proxy the calls to the `.chosen` object. + +## Any + +The `Any` class implements the ASN.1 Any type, which allows any data type. By +default objects of this class do not perform any parsing. However, the +`.parse()` instance method allows parsing the contents of the `Any` object, +either into a universal type, or to a specification pass in via the `spec` +parameter. + +This type is not used as a top-level structure, but instead allows `Sequence` +and `Set` objects to accept varying contents, usually based on some sort of +`ObjectIdentifier`. + +```python +from asn1crypto.core import Sequence, ObjectIdentifier, Any, Integer, OctetString + +class MySequence(Sequence): + _fields = [ + ('type', ObjectIdentifier), + ('value', Any), + ] +``` + +## Specification via OID + +Throughout the usage of ASN.1 in cryptography, a pattern is present where an +`ObjectIdenfitier` is used to determine what specification should be used to +interpret another field in a `Sequence`. Usually the other field is an instance +of `Any`, however occasionally it is an `OctetString` or `OctetBitString`. + +*asn1crypto* provides the `_oid_pair` and `_oid_specs` properties of the +`Sequence` class to allow handling these situations. + +The `_oid_pair` is a tuple with two unicode string elements. The first is the +name of the field that is an `ObjectIdentifier` and the second if the name of +the field that has a variable specification based on the first field. *In +situations where the value field should be an `OctetString` or `OctetBitString`, +`ParsableOctetString` and `ParsableOctetBitString` will need to be used instead +to allow for the sub-parsing of the contents.* + +The `_oid_specs` property is a `dict` object with `ObjectIdentifier` values as +the keys (either dotted or mapped notation) and a type class as the value. When +the first field in `_oid_pair` has a value equal to one of the keys in +`_oid_specs`, then the corresponding type class will be used as the +specification for the second field of `_oid_pair`. + +```python +from asn1crypto.core import Sequence, ObjectIdentifier, Any, OctetString, Integer + +class MyId(ObjectIdentifier): + _map = { + '1.2.3.4': 'initialization_vector', + '1.2.3.5': 'iterations', + } + +class MySequence(Sequence): + _fields = [ + ('type', MyId), + ('value', Any), + ] + + _oid_pair = ('type', 'value') + _oid_specs = { + 'initialization_vector': OctetString, + 'iterations': Integer, + } +``` + +## Explicit and Implicit Tagging + +When working with `Sequence`, `Set` and `Choice` it is often necessary to +disambiguate between fields because of a number of factors: + + - In `Sequence` the presence of an optional field must be determined by tag number + - In `Set`, each field must have a different tag number since they can be in any order + - In `Choice`, each alternative must have a different tag number to determine which is present + +The universal types all have unique tag numbers. However, if a `Sequence`, `Set` +or `Choice` has more than one field with the same universal type, tagging allows +a way to keep the semantics of the original type, but with a different tag +number. + +Implicit tagging simply changes the tag number of a type to a different value. +However, Explicit tagging wraps the existing type in another tag with the +specified tag number. + +In general, most situations allow for implicit tagging, with the notable +exception than a field that is a `Choice` type must always be explicitly tagged. +Otherwise, using implicit tagging would modify the tag of the chosen +alternative, breaking the mechanism by which `Choice` works. + +Here is an example of implicit and explicit tagging where explicit tagging on +the `Sequence` allows a `Choice` type field to be optional, and where implicit +tagging in the `Choice` structure allows disambiguating between two string of +the same type. + +```python +from asn1crypto.core import Sequence, Choice, IA5String, UTCTime, ObjectIdentifier + +class Person(Choice): + _alternatives = [ + ('name', IA5String), + ('email', IA5String, {'implicit': 0}), + ] + +class Record(Sequence): + _fields = [ + ('id', ObjectIdentifier), + ('created', UTCTime), + ('creator', Person, {'explicit': 0, 'optional': True}), + ] +``` + +As is shown above, the keys `implicit` and `explicit` are used for tagging, +and are passed to a type class constructor via the optional third element of +a field or alternative tuple. Both parameters may be an integer tag number, or +a 2-element tuple of string class name and integer tag. + +If a tagging value needs its tagging changed, the `.untag()` method can be used +to create a copy of the object without explicit/implicit tagging. The `.retag()` +method can be used to change the tagging. This method accepts one parameter, a +dict with either or both of the keys `implicit` and `explicit`. + +```python +person = Person(name='email', value='will@wbond.net') + +# Will display True +print(person.implicit) + +# Will display False +print(person.untag().implicit) + +# Will display 0 +print(person.tag) + +# Will display 1 +print(person.retag({'implicit': 1}).tag) +``` diff -Nru asn1crypto-0.22.0/LICENSE asn1crypto-0.24.0/LICENSE --- asn1crypto-0.22.0/LICENSE 1970-01-01 00:00:00.000000000 +0000 +++ asn1crypto-0.24.0/LICENSE 2017-04-11 16:02:23.000000000 +0000 @@ -0,0 +1,19 @@ +Copyright (c) 2015-2017 Will Bond + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff -Nru asn1crypto-0.22.0/MANIFEST.in asn1crypto-0.24.0/MANIFEST.in --- asn1crypto-0.22.0/MANIFEST.in 1970-01-01 00:00:00.000000000 +0000 +++ asn1crypto-0.24.0/MANIFEST.in 2017-09-14 17:48:59.000000000 +0000 @@ -0,0 +1,3 @@ +include LICENSE +include readme.md changelog.md +recursive-include docs *.md diff -Nru asn1crypto-0.22.0/PKG-INFO asn1crypto-0.24.0/PKG-INFO --- asn1crypto-0.22.0/PKG-INFO 2017-03-15 13:32:50.000000000 +0000 +++ asn1crypto-0.24.0/PKG-INFO 2017-12-14 21:04:10.000000000 +0000 @@ -1,11 +1,12 @@ Metadata-Version: 1.1 Name: asn1crypto -Version: 0.22.0 +Version: 0.24.0 Summary: Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP Home-page: https://github.com/wbond/asn1crypto Author: wbond Author-email: will@wbond.net License: MIT +Description-Content-Type: UNKNOWN Description: Docs for this project are maintained at https://github.com/wbond/asn1crypto#readme. Keywords: asn1 crypto pki x509 certificate rsa dsa ec dh Platform: UNKNOWN diff -Nru asn1crypto-0.22.0/readme.md asn1crypto-0.24.0/readme.md --- asn1crypto-0.22.0/readme.md 1970-01-01 00:00:00.000000000 +0000 +++ asn1crypto-0.24.0/readme.md 2017-12-14 21:01:31.000000000 +0000 @@ -0,0 +1,232 @@ +# asn1crypto + +A fast, pure Python library for parsing and serializing ASN.1 structures. + + - [Features](#features) + - [Why Another Python ASN.1 Library?](#why-another-python-asn1-library) + - [Related Crypto Libraries](#related-crypto-libraries) + - [Current Release](#current-release) + - [Dependencies](#dependencies) + - [Installation](#installation) + - [License](#license) + - [Documentation](#documentation) + - [Continuous Integration](#continuous-integration) + - [Testing](#testing) + - [Development](#development) + +[![Travis CI](https://api.travis-ci.org/wbond/asn1crypto.svg?branch=master)](https://travis-ci.org/wbond/asn1crypto) +[![AppVeyor](https://ci.appveyor.com/api/projects/status/github/wbond/asn1crypto?branch=master&svg=true)](https://ci.appveyor.com/project/wbond/asn1crypto) +[![CircleCI](https://circleci.com/gh/wbond/asn1crypto.svg?style=shield)](https://circleci.com/gh/wbond/asn1crypto) +[![Codecov](https://codecov.io/gh/wbond/asn1crypto/branch/master/graph/badge.svg)](https://codecov.io/gh/wbond/asn1crypto) +[![PyPI](https://img.shields.io/pypi/v/asn1crypto.svg)](https://pypi.python.org/pypi/asn1crypto) + +## Features + +In addition to an ASN.1 BER/DER decoder and DER serializer, the project includes +a bunch of ASN.1 structures for use with various common cryptography standards: + +| Standard | Module | Source | +| ---------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| X.509 | [`asn1crypto.x509`](asn1crypto/x509.py) | [RFC 5280](https://tools.ietf.org/html/rfc5280) | +| CRL | [`asn1crypto.crl`](asn1crypto/crl.py) | [RFC 5280](https://tools.ietf.org/html/rfc5280) | +| CSR | [`asn1crypto.csr`](asn1crypto/csr.py) | [RFC 2986](https://tools.ietf.org/html/rfc2986), [RFC 2985](https://tools.ietf.org/html/rfc2985) | +| OCSP | [`asn1crypto.ocsp`](asn1crypto/ocsp.py) | [RFC 6960](https://tools.ietf.org/html/rfc6960) | +| PKCS#12 | [`asn1crypto.pkcs12`](asn1crypto/pkcs12.py) | [RFC 7292](https://tools.ietf.org/html/rfc7292) | +| PKCS#8 | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 5208](https://tools.ietf.org/html/rfc5208) | +| PKCS#1 v2.1 (RSA keys) | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 3447](https://tools.ietf.org/html/rfc3447) | +| DSA keys | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 3279](https://tools.ietf.org/html/rfc3279) | +| Elliptic curve keys | [`asn1crypto.keys`](asn1crypto/keys.py) | [SECG SEC1 V2](http://www.secg.org/sec1-v2.pdf) | +| PKCS#3 v1.4 | [`asn1crypto.algos`](asn1crypto/algos.py) | [PKCS#3 v1.4](ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-3.asc) | +| PKCS#5 v2.1 | [`asn1crypto.algos`](asn1crypto/algos.py) | [PKCS#5 v2.1](http://www.emc.com/collateral/white-papers/h11302-pkcs5v2-1-password-based-cryptography-standard-wp.pdf) | +| CMS (and PKCS#7) | [`asn1crypto.cms`](asn1crypto/cms.py) | [RFC 5652](https://tools.ietf.org/html/rfc5652), [RFC 2315](https://tools.ietf.org/html/rfc2315) | +| TSP | [`asn1crypto.tsp`](asn1crypto/tsp.py) | [RFC 3161](https://tools.ietf.org/html/rfc3161) | +| PDF signatures | [`asn1crypto.pdf`](asn1crypto/pdf.py) | [PDF 1.7](http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf) | + +## Why Another Python ASN.1 Library? + +Python has long had the [pyasn1](https://pypi.python.org/pypi/pyasn1) and +[pyasn1_modules](https://pypi.python.org/pypi/pyasn1-modules) available for +parsing and serializing ASN.1 structures. While the project does include a +comprehensive set of tools for parsing and serializing, the performance of the +library can be very poor, especially when dealing with bit fields and parsing +large structures such as CRLs. + +After spending extensive time using *pyasn1*, the following issues were +identified: + + 1. Poor performance + 2. Verbose, non-pythonic API + 3. Out-dated and incomplete definitions in *pyasn1-modules* + 4. No simple way to map data to native Python data structures + 5. No mechanism for overridden universal ASN.1 types + +The *pyasn1* API is largely method driven, and uses extensive configuration +objects and lowerCamelCase names. There were no consistent options for +converting types of native Python data structures. Since the project supports +out-dated versions of Python, many newer language features are unavailable +for use. + +Time was spent trying to profile issues with the performance, however the +architecture made it hard to pin down the primary source of the poor +performance. Attempts were made to improve performance by utilizing unreleased +patches and delaying parsing using the `Any` type. Even with such changes, the +performance was still unacceptably slow. + +Finally, a number of structures in the cryptographic space use universal data +types such as `BitString` and `OctetString`, but interpret the data as other +types. For instance, signatures are really byte strings, but are encoded as +`BitString`. Elliptic curve keys use both `BitString` and `OctetString` to +represent integers. Parsing these structures as the base universal types and +then re-interpreting them wastes computation. + +*asn1crypto* uses the following techniques to improve performance, especially +when extracting one or two fields from large, complex structures: + + - Delayed parsing of byte string values + - Persistence of original ASN.1 encoded data until a value is changed + - Lazy loading of child fields + - Utilization of high-level Python stdlib modules + +While there is no extensive performance test suite, the +`CRLTests.test_parse_crl` test case was used to parse a 21MB CRL file on a +late 2013 rMBP. *asn1crypto* parsed the certificate serial numbers in just +under 8 seconds. With *pyasn1*, using definitions from *pyasn1-modules*, the +same parsing took over 4,100 seconds. + +For smaller structures the performance difference can range from a few times +faster to an order of magnitude of more. + +## Related Crypto Libraries + +*asn1crypto* is part of the modularcrypto family of Python packages: + + - [asn1crypto](https://github.com/wbond/asn1crypto) + - [oscrypto](https://github.com/wbond/oscrypto) + - [csrbuilder](https://github.com/wbond/csrbuilder) + - [certbuilder](https://github.com/wbond/certbuilder) + - [crlbuilder](https://github.com/wbond/crlbuilder) + - [ocspbuilder](https://github.com/wbond/ocspbuilder) + - [certvalidator](https://github.com/wbond/certvalidator) + +## Current Release + +0.24.0 - [changelog](changelog.md) + +## Dependencies + +Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6 or pypy. *No third-party packages +required.* + +## Installation + +```bash +pip install asn1crypto +``` + +## License + +*asn1crypto* is licensed under the terms of the MIT license. See the +[LICENSE](LICENSE) file for the exact license text. + +## Documentation + +The documentation for *asn1crypto* is composed of tutorials on basic usage and +links to the source for the various pre-defined type classes. + +### Tutorials + + - [Universal Types with BER/DER Decoder and DER Encoder](docs/universal_types.md) + - [PEM Encoder and Decoder](docs/pem.md) + +### Reference + + - [Universal types](asn1crypto/core.py), `asn1crypto.core` + - [Digest, HMAC, signed digest and encryption algorithms](asn1crypto/algos.py), `asn1crypto.algos` + - [Private and public keys](asn1crypto/keys.py), `asn1crypto.keys` + - [X509 certificates](asn1crypto/x509.py), `asn1crypto.x509` + - [Certificate revocation lists (CRLs)](asn1crypto/crl.py), `asn1crypto.crl` + - [Online certificate status protocol (OCSP)](asn1crypto/ocsp.py), `asn1crypto.ocsp` + - [Certificate signing requests (CSRs)](asn1crypto/csr.py), `asn1crypto.csr` + - [Private key/certificate containers (PKCS#12)](asn1crypto/pkcs12.py), `asn1crypto.pkcs12` + - [Cryptographic message syntax (CMS, PKCS#7)](asn1crypto/cms.py), `asn1crypto.cms` + - [Time stamp protocol (TSP)](asn1crypto/tsp.py), `asn1crypto.tsp` + - [PDF signatures](asn1crypto/pdf.py), `asn1crypto.pdf` + +## Continuous Integration + + - [Windows](https://ci.appveyor.com/project/wbond/asn1crypto/history) via AppVeyor + - [OS X](https://circleci.com/gh/wbond/asn1crypto) via CircleCI + - [Linux](https://travis-ci.org/wbond/asn1crypto/builds) via Travis CI + - [Test Coverage](https://codecov.io/gh/wbond/asn1crypto/commits) via Codecov + +## Testing + +Tests are written using `unittest` and require no third-party packages: + +```bash +python run.py tests +``` + +To run only some tests, pass a regular expression as a parameter to `tests`. + +```bash +python run.py tests ocsp +``` + +## Development + +To install the package used for linting, execute: + +```bash +pip install --user -r requires/lint +``` + +The following command will run the linter: + +```bash +python run.py lint +``` + +Support for code coverage can be installed via: + +```bash +pip install --user -r requires/coverage +``` + +Coverage is measured by running: + +```bash +python run.py coverage +``` + +To install the necessary packages for releasing a new version on PyPI, run: + +```bash +pip install --user -r requires/release +``` + +Releases are created by: + + - Making a git tag in [semver](http://semver.org/) format + - Running the command: + + ```bash + python run.py release + ``` + +Existing releases can be found at https://pypi.python.org/pypi/asn1crypto. + +## CI Tasks + +A task named `deps` exists to ensure a modern version of `pip` is installed, +along with all necessary testing dependencies. + +The `ci` task runs `lint` (if flake8 is available for the version of Python) and +`coverage` (or `tests` if coverage is not available for the version of Python). +If the current directory is a clean git working copy, the coverage data is +submitted to codecov.io. + +```bash +python run.py deps +python run.py ci +``` diff -Nru asn1crypto-0.22.0/setup.cfg asn1crypto-0.24.0/setup.cfg --- asn1crypto-0.22.0/setup.cfg 2017-03-15 13:32:50.000000000 +0000 +++ asn1crypto-0.24.0/setup.cfg 2017-12-14 21:04:10.000000000 +0000 @@ -1,5 +1,4 @@ [egg_info] tag_build = tag_date = 0 -tag_svn_revision = 0