From 044686b56427dab5eb89a91b9d73221cc6aecd89 Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Wed, 4 Jul 2018 20:14:52 -0400 Subject: [PATCH] pyasn1 0.4.3, pyasn1_modules 0.2.2 --- src/pyasn1/__init__.py | 4 +- src/pyasn1/codec/ber/decoder.py | 297 ++++-- src/pyasn1/codec/ber/encoder.py | 301 ++++-- src/pyasn1/codec/ber/eoo.py | 9 +- src/pyasn1/codec/cer/decoder.py | 42 +- src/pyasn1/codec/cer/encoder.py | 235 +++-- src/pyasn1/codec/der/decoder.py | 36 +- src/pyasn1/codec/der/encoder.py | 83 +- src/pyasn1/codec/native/decoder.py | 30 +- src/pyasn1/codec/native/encoder.py | 29 +- src/pyasn1/compat/binary.py | 4 +- src/pyasn1/compat/calling.py | 4 +- src/pyasn1/compat/dateandtime.py | 8 +- src/pyasn1/compat/integer.py | 6 +- src/pyasn1/compat/octets.py | 4 +- src/pyasn1/compat/string.py | 4 +- src/pyasn1/debug.py | 10 +- src/pyasn1/error.py | 21 +- src/pyasn1/type/base.py | 354 ++++---- src/pyasn1/type/char.py | 105 +-- src/pyasn1/type/constraint.py | 358 +++++++- src/pyasn1/type/error.py | 4 +- src/pyasn1/type/namedtype.py | 86 +- src/pyasn1/type/namedval.py | 46 +- src/pyasn1/type/opentype.py | 75 ++ src/pyasn1/type/tag.py | 47 +- src/pyasn1/type/tagmap.py | 26 +- src/pyasn1/type/univ.py | 1360 ++++++++++++++++------------ src/pyasn1/type/useful.py | 26 +- src/pyasn1_modules/__init__.py | 2 +- src/pyasn1_modules/pem.py | 4 +- src/pyasn1_modules/rfc1155.py | 9 +- src/pyasn1_modules/rfc1157.py | 10 +- src/pyasn1_modules/rfc1901.py | 8 +- src/pyasn1_modules/rfc1902.py | 9 +- src/pyasn1_modules/rfc1905.py | 11 +- src/pyasn1_modules/rfc2251.py | 10 +- src/pyasn1_modules/rfc2314.py | 4 +- src/pyasn1_modules/rfc2315.py | 36 +- src/pyasn1_modules/rfc2437.py | 9 +- src/pyasn1_modules/rfc2459.py | 554 +++++------ src/pyasn1_modules/rfc2511.py | 6 +- src/pyasn1_modules/rfc2560.py | 15 +- src/pyasn1_modules/rfc2986.py | 124 +++ src/pyasn1_modules/rfc3279.py | 6 +- src/pyasn1_modules/rfc3280.py | 12 +- src/pyasn1_modules/rfc3281.py | 8 +- src/pyasn1_modules/rfc3412.py | 9 +- src/pyasn1_modules/rfc3414.py | 8 +- src/pyasn1_modules/rfc3447.py | 8 +- src/pyasn1_modules/rfc3852.py | 11 +- src/pyasn1_modules/rfc4210.py | 17 +- src/pyasn1_modules/rfc4211.py | 11 +- src/pyasn1_modules/rfc5208.py | 6 +- src/pyasn1_modules/rfc5280.py | 122 +-- src/pyasn1_modules/rfc5652.py | 4 +- src/pyasn1_modules/rfc6402.py | 12 +- 57 files changed, 3047 insertions(+), 1612 deletions(-) create mode 100644 src/pyasn1/type/opentype.py create mode 100644 src/pyasn1_modules/rfc2986.py diff --git a/src/pyasn1/__init__.py b/src/pyasn1/__init__.py index c9218a04..71bb22fe 100644 --- a/src/pyasn1/__init__.py +++ b/src/pyasn1/__init__.py @@ -1,7 +1,7 @@ import sys -# http://www.python.org/dev/peps/pep-0396/ -__version__ = '0.3.7' +# https://www.python.org/dev/peps/pep-0396/ +__version__ = '0.4.3' if sys.version_info[:2] < (2, 4): raise RuntimeError('PyASN1 requires Python 2.4 or later') diff --git a/src/pyasn1/codec/ber/decoder.py b/src/pyasn1/codec/ber/decoder.py index ee3064f3..a27b3e0e 100644 --- a/src/pyasn1/codec/ber/decoder.py +++ b/src/pyasn1/codec/ber/decoder.py @@ -1,14 +1,20 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # -from pyasn1.type import base, tag, univ, char, useful, tagmap +from pyasn1 import debug +from pyasn1 import error from pyasn1.codec.ber import eoo -from pyasn1.compat.octets import oct2int, octs2ints, ints2octs, null from pyasn1.compat.integer import from_bytes -from pyasn1 import debug, error +from pyasn1.compat.octets import oct2int, octs2ints, ints2octs, null +from pyasn1.type import base +from pyasn1.type import char +from pyasn1.type import tag +from pyasn1.type import tagmap +from pyasn1.type import univ +from pyasn1.type import useful __all__ = ['decode'] @@ -36,8 +42,10 @@ class AbstractSimpleDecoder(AbstractDecoder): def substrateCollector(asn1Object, substrate, length): return substrate[:length], substrate[length:] - def _createComponent(self, asn1Spec, tagSet, value=noValue): - if asn1Spec is None: + def _createComponent(self, asn1Spec, tagSet, value, **options): + if options.get('native'): + return value + elif asn1Spec is None: return self.protoComponent.clone(value, tagSet=tagSet) elif value is noValue: return asn1Spec @@ -54,7 +62,7 @@ class ExplicitTagDecoder(AbstractSimpleDecoder): **options): if substrateFun: return substrateFun( - self._createComponent(asn1Spec, tagSet, ''), + self._createComponent(asn1Spec, tagSet, '', **options), substrate, length ) @@ -70,7 +78,7 @@ class ExplicitTagDecoder(AbstractSimpleDecoder): **options): if substrateFun: return substrateFun( - self._createComponent(asn1Spec, tagSet, ''), + self._createComponent(asn1Spec, tagSet, '', **options), substrate, length ) @@ -101,18 +109,18 @@ class IntegerDecoder(AbstractSimpleDecoder): head, tail = substrate[:length], substrate[length:] if not head: - return self._createComponent(asn1Spec, tagSet, 0), tail + return self._createComponent(asn1Spec, tagSet, 0, **options), tail value = from_bytes(head, signed=True) - return self._createComponent(asn1Spec, tagSet, value), tail + return self._createComponent(asn1Spec, tagSet, value, **options), tail class BooleanDecoder(IntegerDecoder): protoComponent = univ.Boolean(0) - def _createComponent(self, asn1Spec, tagSet, value=noValue): - return IntegerDecoder._createComponent(self, asn1Spec, tagSet, value and 1 or 0) + def _createComponent(self, asn1Spec, tagSet, value, **options): + return IntegerDecoder._createComponent(self, asn1Spec, tagSet, value and 1 or 0, **options) class BitStringDecoder(AbstractSimpleDecoder): @@ -124,53 +132,86 @@ class BitStringDecoder(AbstractSimpleDecoder): decodeFun=None, substrateFun=None, **options): head, tail = substrate[:length], substrate[length:] + + if substrateFun: + return substrateFun(self._createComponent(asn1Spec, tagSet, noValue, **options), + substrate, length) + + if not head: + raise error.PyAsn1Error('Empty BIT STRING substrate') + if tagSet[0].tagFormat == tag.tagFormatSimple: # XXX what tag to check? - if not head: - raise error.PyAsn1Error('Empty substrate') + trailingBits = oct2int(head[0]) if trailingBits > 7: raise error.PyAsn1Error( 'Trailing bits overflow %s' % trailingBits ) - head = head[1:] - value = self.protoComponent.fromOctetString(head, trailingBits) - return self._createComponent(asn1Spec, tagSet, value), tail + + value = self.protoComponent.fromOctetString(head[1:], internalFormat=True, padding=trailingBits) + + return self._createComponent(asn1Spec, tagSet, value, **options), tail if not self.supportConstructedForm: raise error.PyAsn1Error('Constructed encoding form prohibited at %s' % self.__class__.__name__) - bitString = self._createComponent(asn1Spec, tagSet) + # All inner fragments are of the same type, treat them as octet string + substrateFun = self.substrateCollector - if substrateFun: - return substrateFun(bitString, substrate, length) + bitString = self.protoComponent.fromOctetString(null, internalFormat=True) while head: - component, head = decodeFun(head, self.protoComponent, **options) - bitString += component + component, head = decodeFun(head, self.protoComponent, + substrateFun=substrateFun, **options) - return bitString, tail + trailingBits = oct2int(component[0]) + if trailingBits > 7: + raise error.PyAsn1Error( + 'Trailing bits overflow %s' % trailingBits + ) + + bitString = self.protoComponent.fromOctetString( + component[1:], internalFormat=True, + prepend=bitString, padding=trailingBits + ) + + return self._createComponent(asn1Spec, tagSet, bitString, **options), tail def indefLenValueDecoder(self, substrate, asn1Spec, tagSet=None, length=None, state=None, decodeFun=None, substrateFun=None, **options): - bitString = self._createComponent(asn1Spec, tagSet) if substrateFun: - return substrateFun(bitString, substrate, length) + return substrateFun(self._createComponent(asn1Spec, tagSet, noValue, **options), substrate, length) + + # All inner fragments are of the same type, treat them as octet string + substrateFun = self.substrateCollector + + bitString = self.protoComponent.fromOctetString(null, internalFormat=True) while substrate: component, substrate = decodeFun(substrate, self.protoComponent, + substrateFun=substrateFun, allowEoo=True, **options) if component is eoo.endOfOctets: break - bitString += component + trailingBits = oct2int(component[0]) + if trailingBits > 7: + raise error.PyAsn1Error( + 'Trailing bits overflow %s' % trailingBits + ) + + bitString = self.protoComponent.fromOctetString( + component[1:], internalFormat=True, + prepend=bitString, padding=trailingBits + ) else: raise error.SubstrateUnderrunError('No EOO seen before substrate ends') - return bitString, substrate + return self._createComponent(asn1Spec, tagSet, bitString, **options), substrate class OctetStringDecoder(AbstractSimpleDecoder): @@ -184,11 +225,11 @@ class OctetStringDecoder(AbstractSimpleDecoder): head, tail = substrate[:length], substrate[length:] if substrateFun: - return substrateFun(self._createComponent(asn1Spec, tagSet), + return substrateFun(self._createComponent(asn1Spec, tagSet, noValue, **options), substrate, length) if tagSet[0].tagFormat == tag.tagFormatSimple: # XXX what tag to check? - return self._createComponent(asn1Spec, tagSet, head), tail + return self._createComponent(asn1Spec, tagSet, head, **options), tail if not self.supportConstructedForm: raise error.PyAsn1Error('Constructed encoding form prohibited at %s' % self.__class__.__name__) @@ -204,14 +245,14 @@ class OctetStringDecoder(AbstractSimpleDecoder): **options) header += component - return self._createComponent(asn1Spec, tagSet, header), tail + return self._createComponent(asn1Spec, tagSet, header, **options), tail def indefLenValueDecoder(self, substrate, asn1Spec, tagSet=None, length=None, state=None, decodeFun=None, substrateFun=None, **options): if substrateFun and substrateFun is not self.substrateCollector: - asn1Object = self._createComponent(asn1Spec, tagSet) + asn1Object = self._createComponent(asn1Spec, tagSet, noValue, **options) return substrateFun(asn1Object, substrate, length) # All inner fragments are of the same type, treat them as octet string @@ -232,7 +273,7 @@ class OctetStringDecoder(AbstractSimpleDecoder): 'No EOO seen before substrate ends' ) - return self._createComponent(asn1Spec, tagSet, header), substrate + return self._createComponent(asn1Spec, tagSet, header, **options), substrate class NullDecoder(AbstractSimpleDecoder): @@ -248,7 +289,7 @@ class NullDecoder(AbstractSimpleDecoder): head, tail = substrate[:length], substrate[length:] - component = self._createComponent(asn1Spec, tagSet) + component = self._createComponent(asn1Spec, tagSet, '', **options) if head: raise error.PyAsn1Error('Unexpected %d-octet substrate for Null' % length) @@ -296,7 +337,7 @@ class ObjectIdentifierDecoder(AbstractSimpleDecoder): elif subId == 128: # ASN.1 spec forbids leading zeros (0x80) in OID # encoding, tolerating it opens a vulnerability. See - # http://www.cosic.esat.kuleuven.be/publications/article-1432.pdf + # https://www.esat.kuleuven.be/cosic/publications/article-1432.pdf # page 7 raise error.PyAsn1Error('Invalid octet 0x80 in OID encoding') @@ -310,7 +351,7 @@ class ObjectIdentifierDecoder(AbstractSimpleDecoder): else: raise error.PyAsn1Error('Malformed first OID octet: %s' % head[0]) - return self._createComponent(asn1Spec, tagSet, oid), tail + return self._createComponent(asn1Spec, tagSet, oid, **options), tail class RealDecoder(AbstractSimpleDecoder): @@ -326,7 +367,7 @@ class RealDecoder(AbstractSimpleDecoder): head, tail = substrate[:length], substrate[length:] if not head: - return self._createComponent(asn1Spec, tagSet, 0.0), tail + return self._createComponent(asn1Spec, tagSet, 0.0, **options), tail fo = oct2int(head[0]) head = head[1:] @@ -386,7 +427,7 @@ class RealDecoder(AbstractSimpleDecoder): raise error.SubstrateUnderrunError( 'Unknown encoding (tag %s)' % fo ) - return self._createComponent(asn1Spec, tagSet, value), tail + return self._createComponent(asn1Spec, tagSet, value, **options), tail class AbstractConstructedDecoder(AbstractDecoder): @@ -415,10 +456,9 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): # Now we have to guess is it SEQUENCE/SET or SEQUENCE OF/SET OF # The heuristics is: - # * 0-1 component -> likely SEQUENCE OF/SET OF - # * 1+ components of the same type -> likely SEQUENCE OF/SET OF - # * otherwise -> likely SEQUENCE/SET - if len(components) > 1 or len(componentTypes) > 1: + # * 1+ components of different types -> likely SEQUENCE/SET + # * otherwise -> likely SEQUENCE OF/SET OF + if len(componentTypes) > 1: protoComponent = self.protoRecordComponent else: protoComponent = self.protoSequenceComponent @@ -467,34 +507,34 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): asn1Object = asn1Spec.clone() - if asn1Object.typeId in (univ.Sequence.typeId, univ.Set.typeId): + if asn1Spec.typeId in (univ.Sequence.typeId, univ.Set.typeId): - namedTypes = asn1Object.componentType + namedTypes = asn1Spec.componentType - isSetType = asn1Object.typeId == univ.Set.typeId + isSetType = asn1Spec.typeId == univ.Set.typeId isDeterministic = not isSetType and not namedTypes.hasOptionalOrDefault seenIndices = set() idx = 0 while head: if not namedTypes: - asn1Spec = None + componentType = None elif isSetType: - asn1Spec = namedTypes.tagMapUnique + componentType = namedTypes.tagMapUnique else: try: if isDeterministic: - asn1Spec = namedTypes[idx].asn1Object + componentType = namedTypes[idx].asn1Object elif namedTypes[idx].isOptional or namedTypes[idx].isDefaulted: - asn1Spec = namedTypes.getTagMapNearPosition(idx) + componentType = namedTypes.getTagMapNearPosition(idx) else: - asn1Spec = namedTypes[idx].asn1Object + componentType = namedTypes[idx].asn1Object except IndexError: raise error.PyAsn1Error( - 'Excessive components decoded at %r' % (asn1Object,) + 'Excessive components decoded at %r' % (asn1Spec,) ) - component, head = decodeFun(head, asn1Spec, **options) + component, head = decodeFun(head, componentType, **options) if not isDeterministic and namedTypes: if isSetType: @@ -514,14 +554,54 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): if namedTypes: if not namedTypes.requiredComponents.issubset(seenIndices): raise error.PyAsn1Error('ASN.1 object %s has uninitialized components' % asn1Object.__class__.__name__) + + if namedTypes.hasOpenTypes: + + openTypes = options.get('openTypes', {}) + + if openTypes or options.get('decodeOpenTypes', False): + + for idx, namedType in enumerate(namedTypes.namedTypes): + if not namedType.openType: + continue + + if namedType.isOptional and not asn1Object.getComponentByPosition(idx).isValue: + continue + + governingValue = asn1Object.getComponentByName( + namedType.openType.name + ) + + try: + openType = openTypes[governingValue] + + except KeyError: + + try: + openType = namedType.openType[governingValue] + + except KeyError: + continue + + component, rest = decodeFun( + asn1Object.getComponentByPosition(idx).asOctets(), + asn1Spec=openType + ) + + asn1Object.setComponentByPosition(idx, component) + else: asn1Object.verifySizeSpec() else: - asn1Spec = asn1Object.componentType + asn1Object = asn1Spec.clone() + + componentType = asn1Spec.componentType + idx = 0 + while head: - component, head = decodeFun(head, asn1Spec, **options) + component, head = decodeFun(head, componentType, **options) asn1Object.setComponentByPosition( idx, component, verifyConstraints=False, @@ -529,8 +609,6 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): ) idx += 1 - asn1Object.verifySizeSpec() - return asn1Object, tail def indefLenValueDecoder(self, substrate, asn1Spec, @@ -557,7 +635,7 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): asn1Object = asn1Spec.clone() - if asn1Object.typeId in (univ.Sequence.typeId, univ.Set.typeId): + if asn1Spec.typeId in (univ.Sequence.typeId, univ.Set.typeId): namedTypes = asn1Object.componentType @@ -611,16 +689,59 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): if namedTypes: if not namedTypes.requiredComponents.issubset(seenIndices): raise error.PyAsn1Error('ASN.1 object %s has uninitialized components' % asn1Object.__class__.__name__) - else: - asn1Object.verifySizeSpec() + + if namedTypes.hasOpenTypes: + + openTypes = options.get('openTypes', None) + + if openTypes or options.get('decodeOpenTypes', False): + + for idx, namedType in enumerate(namedTypes.namedTypes): + if not namedType.openType: + continue + + if namedType.isOptional and not asn1Object.getComponentByPosition(idx).isValue: + continue + + governingValue = asn1Object.getComponentByName( + namedType.openType.name + ) + + try: + openType = openTypes[governingValue] + + except KeyError: + + try: + openType = namedType.openType[governingValue] + + except KeyError: + continue + + component, rest = decodeFun( + asn1Object.getComponentByPosition(idx).asOctets(), + asn1Spec=openType, allowEoo=True + ) + + if component is not eoo.endOfOctets: + asn1Object.setComponentByPosition(idx, component) + + else: + asn1Object.verifySizeSpec() else: - asn1Spec = asn1Object.componentType + asn1Object = asn1Spec.clone() + + componentType = asn1Spec.componentType + idx = 0 + while substrate: - component, substrate = decodeFun(substrate, asn1Spec, allowEoo=True, **options) + component, substrate = decodeFun(substrate, componentType, allowEoo=True, **options) + if component is eoo.endOfOctets: break + asn1Object.setComponentByPosition( idx, component, verifyConstraints=False, @@ -631,7 +752,6 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): raise error.SubstrateUnderrunError( 'No EOO seen before substrate ends' ) - asn1Object.verifySizeSpec() return asn1Object, substrate @@ -671,28 +791,35 @@ class ChoiceDecoder(AbstractConstructedDecoder): decodeFun=None, substrateFun=None, **options): head, tail = substrate[:length], substrate[length:] + if asn1Spec is None: asn1Object = self.protoComponent.clone(tagSet=tagSet) else: asn1Object = asn1Spec.clone() + if substrateFun: return substrateFun(asn1Object, substrate, length) + if asn1Object.tagSet == tagSet: # explicitly tagged Choice component, head = decodeFun( head, asn1Object.componentTagMap, **options ) + else: component, head = decodeFun( head, asn1Object.componentTagMap, tagSet, length, state, **options ) + effectiveTagSet = component.effectiveTagSet + asn1Object.setComponentByType( effectiveTagSet, component, verifyConstraints=False, matchTags=False, matchConstraints=False, innerFlag=False ) + return asn1Object, tail def indefLenValueDecoder(self, substrate, asn1Spec, @@ -703,8 +830,10 @@ class ChoiceDecoder(AbstractConstructedDecoder): asn1Object = self.protoComponent.clone(tagSet=tagSet) else: asn1Object = asn1Spec.clone() + if substrateFun: return substrateFun(asn1Object, substrate, length) + if asn1Object.tagSet == tagSet: # explicitly tagged Choice component, substrate = decodeFun( substrate, asn1Object.componentType.tagMapUnique, **options @@ -715,18 +844,22 @@ class ChoiceDecoder(AbstractConstructedDecoder): ) if eooMarker is not eoo.endOfOctets: raise error.PyAsn1Error('No EOO seen before substrate ends') + else: component, substrate = decodeFun( substrate, asn1Object.componentType.tagMapUnique, tagSet, length, state, **options ) + effectiveTagSet = component.effectiveTagSet + asn1Object.setComponentByType( effectiveTagSet, component, verifyConstraints=False, matchTags=False, matchConstraints=False, innerFlag=False ) + return asn1Object, substrate @@ -745,12 +878,12 @@ class AnyDecoder(AbstractSimpleDecoder): substrate = fullSubstrate if substrateFun: - return substrateFun(self._createComponent(asn1Spec, tagSet), + return substrateFun(self._createComponent(asn1Spec, tagSet, noValue, **options), substrate, length) head, tail = substrate[:length], substrate[length:] - return self._createComponent(asn1Spec, tagSet, value=head), tail + return self._createComponent(asn1Spec, tagSet, head, **options), tail def indefLenValueDecoder(self, substrate, asn1Spec, tagSet=None, length=None, state=None, @@ -769,7 +902,7 @@ class AnyDecoder(AbstractSimpleDecoder): asn1Spec = self.protoComponent if substrateFun and substrateFun is not self.substrateCollector: - asn1Object = self._createComponent(asn1Spec, tagSet) + asn1Object = self._createComponent(asn1Spec, tagSet, noValue, **options) return substrateFun(asn1Object, header + substrate, length + len(header)) # All inner fragments are of the same type, treat them as octet string @@ -789,7 +922,7 @@ class AnyDecoder(AbstractSimpleDecoder): if substrateFun: return header, substrate else: - return self._createComponent(asn1Spec, tagSet, header), substrate + return self._createComponent(asn1Spec, tagSet, header, **options), substrate # character string types @@ -1159,8 +1292,10 @@ class Decoder(object): self, substrateFun, **options ) + if logger: logger('codec %s yields type %s, value:\n%s\n...remaining substrate is: %s' % (concreteDecoder.__class__.__name__, value.__class__.__name__, isinstance(value, base.Asn1Item) and value.prettyPrint() or value, substrate and debug.hexdump(substrate) or '')) + state = stStop break if state is stTryAsExplicitTag: @@ -1190,15 +1325,17 @@ class Decoder(object): #: Turns BER octet stream into an ASN.1 object. #: -#: Takes BER octetstream and decode it into an ASN.1 object +#: Takes BER octet-stream and decode it into an ASN.1 object #: (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which #: may be a scalar or an arbitrary nested structure. #: #: Parameters #: ---------- #: substrate: :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) -#: BER octetstream +#: BER octet-stream #: +#: Keyword Args +#: ------------ #: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative #: A pyasn1 type object to act as a template guiding the decoder. Depending on the ASN.1 structure #: being decoded, *asn1Spec* may or may not be required. Most common reason for @@ -1212,8 +1349,30 @@ class Decoder(object): #: #: Raises #: ------ -#: : :py:class:`pyasn1.error.PyAsn1Error` +#: :py:class:`~pyasn1.error.PyAsn1Error` #: On decoding errors +#: +#: Examples +#: -------- +#: Decode BER serialisation without ASN.1 schema +#: +#: .. code-block:: pycon +#: +#: >>> s, _ = decode(b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03') +#: >>> str(s) +#: SequenceOf: +#: 1 2 3 +#: +#: Decode BER serialisation with ASN.1 schema +#: +#: .. code-block:: pycon +#: +#: >>> seq = SequenceOf(componentType=Integer()) +#: >>> s, _ = decode(b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03', asn1Spec=seq) +#: >>> str(s) +#: SequenceOf: +#: 1 2 3 +#: decode = Decoder(tagMap, typeMap) # XXX diff --git a/src/pyasn1/codec/ber/encoder.py b/src/pyasn1/codec/ber/encoder.py index 4ed4c3b7..0094b224 100644 --- a/src/pyasn1/codec/ber/encoder.py +++ b/src/pyasn1/codec/ber/encoder.py @@ -1,20 +1,25 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # -from pyasn1.type import tag, univ, char, useful +from pyasn1 import debug +from pyasn1 import error from pyasn1.codec.ber import eoo -from pyasn1.compat.octets import int2oct, oct2int, ints2octs, null, str2octs from pyasn1.compat.integer import to_bytes -from pyasn1 import debug, error +from pyasn1.compat.octets import (int2oct, oct2int, ints2octs, null, + str2octs, isOctetsType) +from pyasn1.type import char +from pyasn1.type import tag +from pyasn1.type import univ +from pyasn1.type import useful __all__ = ['encode'] class AbstractItemEncoder(object): - supportIndefLenMode = 1 + supportIndefLenMode = True # An outcome of otherwise legit call `encodeFun(eoo.endOfOctets)` eooIntegerSubstrate = (0, 0) @@ -51,17 +56,20 @@ class AbstractItemEncoder(object): raise error.PyAsn1Error('Length octets overflow (%d)' % substrateLen) return (0x80 | substrateLen,) + substrate - def encodeValue(self, value, encodeFun, **options): + def encodeValue(self, value, asn1Spec, encodeFun, **options): raise error.PyAsn1Error('Not implemented') - def encode(self, value, encodeFun, **options): + def encode(self, value, asn1Spec=None, encodeFun=None, **options): - tagSet = value.tagSet + if asn1Spec is None: + tagSet = value.tagSet + else: + tagSet = asn1Spec.tagSet # untagged item? if not tagSet: substrate, isConstructed, isOctets = self.encodeValue( - value, encodeFun, **options + value, asn1Spec, encodeFun, **options ) return substrate @@ -74,10 +82,10 @@ class AbstractItemEncoder(object): # base tag? if not idx: substrate, isConstructed, isOctets = self.encodeValue( - value, encodeFun, **options + value, asn1Spec, encodeFun, **options ) - if options.get('ifNotEmpty', False) and not substrate: + if not substrate and isConstructed and options.get('ifNotEmpty', False): return substrate # primitive form implies definite mode @@ -106,14 +114,14 @@ class AbstractItemEncoder(object): class EndOfOctetsEncoder(AbstractItemEncoder): - def encodeValue(self, value, encodeFun, **options): + def encodeValue(self, value, asn1Spec, encodeFun, **options): return null, False, True class BooleanEncoder(AbstractItemEncoder): supportIndefLenMode = False - def encodeValue(self, value, encodeFun, **options): + def encodeValue(self, value, asn1Spec, encodeFun, **options): return value and (1,) or (0,), False, False @@ -121,7 +129,7 @@ class IntegerEncoder(AbstractItemEncoder): supportIndefLenMode = False supportCompactZero = False - def encodeValue(self, value, encodeFun, **options): + def encodeValue(self, value, asn1Spec, encodeFun, **options): if value == 0: # de-facto way to encode zero if self.supportCompactZero: @@ -133,7 +141,11 @@ class IntegerEncoder(AbstractItemEncoder): class BitStringEncoder(AbstractItemEncoder): - def encodeValue(self, value, encodeFun, **options): + def encodeValue(self, value, asn1Spec, encodeFun, **options): + if asn1Spec is not None: + # TODO: try to avoid ASN.1 schema instantiation + value = asn1Spec.clone(value) + valueLength = len(value) if valueLength % 8: alignedValue = value << (8 - valueLength % 8) @@ -145,39 +157,79 @@ class BitStringEncoder(AbstractItemEncoder): substrate = alignedValue.asOctets() return int2oct(len(substrate) * 8 - valueLength) + substrate, False, True + baseTag = value.tagSet.baseTag + # strip off explicit tags - alignedValue = alignedValue.clone( - tagSet=tag.TagSet(value.tagSet.baseTag, value.tagSet.baseTag) - ) + if baseTag: + tagSet = tag.TagSet(baseTag, baseTag) + else: + tagSet = tag.TagSet() + + alignedValue = alignedValue.clone(tagSet=tagSet) stop = 0 substrate = null while stop < valueLength: start = stop stop = min(start + maxChunkSize * 8, valueLength) - substrate += encodeFun(alignedValue[start:stop], **options) + substrate += encodeFun(alignedValue[start:stop], asn1Spec, **options) return substrate, True, True class OctetStringEncoder(AbstractItemEncoder): - def encodeValue(self, value, encodeFun, **options): - maxChunkSize = options.get('maxChunkSize', 0) - if not maxChunkSize or len(value) <= maxChunkSize: - return value.asOctets(), False, True + + def encodeValue(self, value, asn1Spec, encodeFun, **options): + + if asn1Spec is None: + substrate = value.asOctets() + + elif not isOctetsType(value): + substrate = asn1Spec.clone(value).asOctets() else: - # will strip off explicit tags - baseTagSet = tag.TagSet(value.tagSet.baseTag, value.tagSet.baseTag) + substrate = value + + maxChunkSize = options.get('maxChunkSize', 0) + + if not maxChunkSize or len(substrate) <= maxChunkSize: + return substrate, False, True + + else: + + # strip off explicit tags for inner chunks + + if asn1Spec is None: + baseTag = value.tagSet.baseTag + + # strip off explicit tags + if baseTag: + tagSet = tag.TagSet(baseTag, baseTag) + else: + tagSet = tag.TagSet() + + asn1Spec = value.clone(tagSet=tagSet) + + elif not isOctetsType(value): + baseTag = asn1Spec.tagSet.baseTag + + # strip off explicit tags + if baseTag: + tagSet = tag.TagSet(baseTag, baseTag) + else: + tagSet = tag.TagSet() + + asn1Spec = asn1Spec.clone(tagSet=tagSet) pos = 0 substrate = null + while True: - chunk = value.clone(value[pos:pos + maxChunkSize], - tagSet=baseTagSet) + chunk = value[pos:pos + maxChunkSize] if not chunk: break - substrate += encodeFun(chunk, **options) + + substrate += encodeFun(chunk, asn1Spec, **options) pos += maxChunkSize return substrate, True, True @@ -186,14 +238,17 @@ class OctetStringEncoder(AbstractItemEncoder): class NullEncoder(AbstractItemEncoder): supportIndefLenMode = False - def encodeValue(self, value, encodeFun, **options): + def encodeValue(self, value, asn1Spec, encodeFun, **options): return null, False, True class ObjectIdentifierEncoder(AbstractItemEncoder): supportIndefLenMode = False - def encodeValue(self, value, encodeFun, **options): + def encodeValue(self, value, asn1Spec, encodeFun, **options): + if asn1Spec is not None: + value = asn1Spec.clone(value) + oid = value.asTuple() # Build the first pair @@ -291,7 +346,10 @@ class RealEncoder(AbstractItemEncoder): encbase = encBase[i] return sign, m, encbase, e - def encodeValue(self, value, encodeFun, **options): + def encodeValue(self, value, asn1Spec, encodeFun, **options): + if asn1Spec is not None: + value = asn1Spec.clone(value) + if value.isPlusInf: return (0x40,), False, False if value.isMinusInf: @@ -362,44 +420,116 @@ class RealEncoder(AbstractItemEncoder): class SequenceEncoder(AbstractItemEncoder): - def encodeValue(self, value, encodeFun, **options): - value.verifySizeSpec() + omitEmptyOptionals = False + + # TODO: handling three flavors of input is too much -- split over codecs + + def encodeValue(self, value, asn1Spec, encodeFun, **options): - namedTypes = value.componentType substrate = null - idx = len(value) - while idx > 0: - idx -= 1 - if namedTypes: - if namedTypes[idx].isOptional and not value[idx].isValue: + if asn1Spec is None: + # instance of ASN.1 schema + value.verifySizeSpec() + + namedTypes = value.componentType + + for idx, component in enumerate(value.values()): + if namedTypes: + namedType = namedTypes[idx] + + if namedType.isOptional and not component.isValue: + continue + + if namedType.isDefaulted and component == namedType.asn1Object: + continue + + if self.omitEmptyOptionals: + options.update(ifNotEmpty=namedType.isOptional) + + chunk = encodeFun(component, asn1Spec, **options) + + # wrap open type blob if needed + if namedTypes and namedType.openType: + wrapType = namedType.asn1Object + if wrapType.tagSet and not wrapType.isSameTypeWith(component): + chunk = encodeFun(chunk, wrapType, **options) + + substrate += chunk + + else: + # bare Python value + ASN.1 schema + for idx, namedType in enumerate(asn1Spec.componentType.namedTypes): + + try: + component = value[namedType.name] + + except KeyError: + raise error.PyAsn1Error('Component name "%s" not found in %r' % (namedType.name, value)) + + if namedType.isOptional and namedType.name not in value: continue - if namedTypes[idx].isDefaulted and value[idx] == namedTypes[idx].asn1Object: + + if namedType.isDefaulted and component == namedType.asn1Object: continue - substrate = encodeFun(value[idx], **options) + substrate + + if self.omitEmptyOptionals: + options.update(ifNotEmpty=namedType.isOptional) + + chunk = encodeFun(component, asn1Spec[idx], **options) + + # wrap open type blob if needed + if namedType.openType: + wrapType = namedType.asn1Object + if wrapType.tagSet and not wrapType.isSameTypeWith(component): + chunk = encodeFun(chunk, wrapType, **options) + + substrate += chunk return substrate, True, True class SequenceOfEncoder(AbstractItemEncoder): - def encodeValue(self, value, encodeFun, **options): - value.verifySizeSpec() + def encodeValue(self, value, asn1Spec, encodeFun, **options): + if asn1Spec is None: + value.verifySizeSpec() + else: + asn1Spec = asn1Spec.componentType + substrate = null - idx = len(value) - while idx > 0: - idx -= 1 - substrate = encodeFun(value[idx], **options) + substrate + + for idx, component in enumerate(value): + substrate += encodeFun(value[idx], asn1Spec, **options) + return substrate, True, True class ChoiceEncoder(AbstractItemEncoder): - def encodeValue(self, value, encodeFun, **options): - return encodeFun(value.getComponent(), **options), True, True + def encodeValue(self, value, asn1Spec, encodeFun, **options): + if asn1Spec is None: + component = value.getComponent() + else: + names = [namedType.name for namedType in asn1Spec.componentType.namedTypes + if namedType.name in value] + if len(names) != 1: + raise error.PyAsn1Error('%s components for Choice at %r' % (len(names) and 'Multiple ' or 'None ', value)) + + name = names[0] + + component = value[name] + asn1Spec = asn1Spec[name] + + return encodeFun(component, asn1Spec, **options), True, True class AnyEncoder(OctetStringEncoder): - def encodeValue(self, value, encodeFun, **options): - return value.asOctets(), not options.get('defMode', True), True + def encodeValue(self, value, asn1Spec, encodeFun, **options): + if asn1Spec is None: + value = value.asOctets() + elif not isOctetsType(value): + value = asn1Spec.clone(value).asOctets() + + return value, not options.get('defMode', True), True tagMap = { @@ -479,7 +609,16 @@ class Encoder(object): self.__tagMap = tagMap self.__typeMap = typeMap - def __call__(self, value, **options): + def __call__(self, value, asn1Spec=None, **options): + try: + if asn1Spec is None: + typeId = value.typeId + else: + typeId = asn1Spec.typeId + + except AttributeError: + raise error.PyAsn1Error('Value %r is not ASN.1 type instance ' + 'and "asn1Spec" not given' % (value,)) if debug.logger & debug.flagEncoder: logger = debug.logger @@ -487,7 +626,8 @@ class Encoder(object): logger = None if logger: - logger('encoder called in %sdef mode, chunk size %s for type %s, value:\n%s' % (not options.get('defMode', True) and 'in' or '', options.get('maxChunkSize', 0), value.prettyPrintType(), value.prettyPrint())) + logger('encoder called in %sdef mode, chunk size %s for ' + 'type %s, value:\n%s' % (not options.get('defMode', True) and 'in' or '', options.get('maxChunkSize', 0), asn1Spec is None and value.prettyPrintType() or asn1Spec.prettyPrintType(), value)) if self.fixedDefLengthMode is not None: options.update(defMode=self.fixedDefLengthMode) @@ -495,25 +635,32 @@ class Encoder(object): if self.fixedChunkSize is not None: options.update(maxChunkSize=self.fixedChunkSize) - tagSet = value.tagSet try: - concreteEncoder = self.__typeMap[value.typeId] + concreteEncoder = self.__typeMap[typeId] + + if logger: + logger('using value codec %s chosen by type ID %s' % (concreteEncoder.__class__.__name__, typeId)) except KeyError: + if asn1Spec is None: + tagSet = value.tagSet + else: + tagSet = asn1Spec.tagSet + # use base type for codec lookup to recover untagged types - baseTagSet = tag.TagSet(value.tagSet.baseTag, value.tagSet.baseTag) + baseTagSet = tag.TagSet(tagSet.baseTag, tagSet.baseTag) try: concreteEncoder = self.__tagMap[baseTagSet] except KeyError: - raise error.PyAsn1Error('No encoder for %s' % (value,)) + raise error.PyAsn1Error('No encoder for %r (%s)' % (value, tagSet)) - if logger: - logger('using value codec %s chosen by %s' % (concreteEncoder.__class__.__name__, tagSet)) + if logger: + logger('using value codec %s chosen by tagSet %s' % (concreteEncoder.__class__.__name__, tagSet)) - substrate = concreteEncoder.encode(value, self, **options) + substrate = concreteEncoder.encode(value, asn1Spec, self, **options) if logger: logger('codec %s built %s octets of substrate: %s\nencoder completed' % (concreteEncoder, len(substrate), debug.hexdump(substrate))) @@ -527,8 +674,14 @@ class Encoder(object): #: #: Parameters #: ---------- -# value: any pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) -#: A pyasn1 object to encode +#: value: either a Python or pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: A Python or pyasn1 object to encode. If Python object is given, `asnSpec` +#: parameter is required to guide the encoding process. +#: +#: Keyword Args +#: ------------ +#: asn1Spec: +#: Optional ASN.1 schema or value object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative #: #: defMode: :py:class:`bool` #: If `False`, produces indefinite length encoding @@ -543,6 +696,26 @@ class Encoder(object): #: #: Raises #: ------ -#: : :py:class:`pyasn1.error.PyAsn1Error` +#: :py:class:`~pyasn1.error.PyAsn1Error` #: On encoding errors +#: +#: Examples +#: -------- +#: Encode Python value into BER with ASN.1 schema +#: +#: .. code-block:: pycon +#: +#: >>> seq = SequenceOf(componentType=Integer()) +#: >>> encode([1, 2, 3], asn1Spec=seq) +#: b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03' +#: +#: Encode ASN.1 value object into BER +#: +#: .. code-block:: pycon +#: +#: >>> seq = SequenceOf(componentType=Integer()) +#: >>> seq.extend([1, 2, 3]) +#: >>> encode(seq) +#: b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03' +#: encode = Encoder(tagMap, typeMap) diff --git a/src/pyasn1/codec/ber/eoo.py b/src/pyasn1/codec/ber/eoo.py index 28e33c5a..d4cd827a 100644 --- a/src/pyasn1/codec/ber/eoo.py +++ b/src/pyasn1/codec/ber/eoo.py @@ -1,10 +1,13 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # -from pyasn1.type import base, tag +from pyasn1.type import base +from pyasn1.type import tag + +__all__ = ['endOfOctets'] class EndOfOctets(base.AbstractSimpleAsn1Item): diff --git a/src/pyasn1/codec/cer/decoder.py b/src/pyasn1/codec/cer/decoder.py index 5e3e8bf2..66572ecb 100644 --- a/src/pyasn1/codec/cer/decoder.py +++ b/src/pyasn1/codec/cer/decoder.py @@ -1,13 +1,13 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # -from pyasn1.type import univ +from pyasn1 import error from pyasn1.codec.ber import decoder from pyasn1.compat.octets import oct2int -from pyasn1 import error +from pyasn1.type import univ __all__ = ['decode'] @@ -25,14 +25,14 @@ class BooleanDecoder(decoder.AbstractSimpleDecoder): byte = oct2int(head[0]) # CER/DER specifies encoding of TRUE as 0xFF and FALSE as 0x0, while # BER allows any non-zero value as TRUE; cf. sections 8.2.2. and 11.1 - # in http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf + # in https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf if byte == 0xff: value = 1 elif byte == 0x00: value = 0 else: raise error.PyAsn1Error('Unexpected Boolean payload: %s' % byte) - return self._createComponent(asn1Spec, tagSet, value), tail + return self._createComponent(asn1Spec, tagSet, value, **options), tail # TODO: prohibit non-canonical encoding BitStringDecoder = decoder.BitStringDecoder @@ -63,15 +63,17 @@ class Decoder(decoder.Decoder): #: Turns CER octet stream into an ASN.1 object. #: -#: Takes CER octetstream and decode it into an ASN.1 object +#: Takes CER octet-stream and decode it into an ASN.1 object #: (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which #: may be a scalar or an arbitrary nested structure. #: #: Parameters #: ---------- #: substrate: :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) -#: CER octetstream +#: CER octet-stream #: +#: Keyword Args +#: ------------ #: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative #: A pyasn1 type object to act as a template guiding the decoder. Depending on the ASN.1 structure #: being decoded, *asn1Spec* may or may not be required. Most common reason for @@ -85,6 +87,28 @@ class Decoder(decoder.Decoder): #: #: Raises #: ------ -#: : :py:class:`pyasn1.error.PyAsn1Error` +#: :py:class:`~pyasn1.error.PyAsn1Error` #: On decoding errors +#: +#: Examples +#: -------- +#: Decode CER serialisation without ASN.1 schema +#: +#: .. code-block:: pycon +#: +#: >>> s, _ = decode(b'0\x80\x02\x01\x01\x02\x01\x02\x02\x01\x03\x00\x00') +#: >>> str(s) +#: SequenceOf: +#: 1 2 3 +#: +#: Decode CER serialisation with ASN.1 schema +#: +#: .. code-block:: pycon +#: +#: >>> seq = SequenceOf(componentType=Integer()) +#: >>> s, _ = decode(b'0\x80\x02\x01\x01\x02\x01\x02\x02\x01\x03\x00\x00', asn1Spec=seq) +#: >>> str(s) +#: SequenceOf: +#: 1 2 3 +#: decode = Decoder(tagMap, decoder.typeMap) diff --git a/src/pyasn1/codec/cer/encoder.py b/src/pyasn1/codec/cer/encoder.py index 4700de0a..768d3c11 100644 --- a/src/pyasn1/codec/cer/encoder.py +++ b/src/pyasn1/codec/cer/encoder.py @@ -1,20 +1,20 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # -from pyasn1.type import univ -from pyasn1.type import useful +from pyasn1 import error from pyasn1.codec.ber import encoder from pyasn1.compat.octets import str2octs, null -from pyasn1 import error +from pyasn1.type import univ +from pyasn1.type import useful __all__ = ['encode'] class BooleanEncoder(encoder.IntegerEncoder): - def encodeValue(self, value, encodeFun, **options): + def encodeValue(self, value, asn1Spec, encodeFun, **options): if value == 0: substrate = (0,) else: @@ -38,7 +38,7 @@ class TimeEncoderMixIn(object): minLength = 12 maxLength = 19 - def encodeValue(self, value, encodeFun, **options): + def encodeValue(self, value, asn1Spec, encodeFun, **options): # Encoding constraints: # - minutes are mandatory, seconds are optional # - subseconds must NOT be zero @@ -46,6 +46,9 @@ class TimeEncoderMixIn(object): # - time in UTC (Z) # - only dot is allowed for fractions + if asn1Spec is not None: + value = asn1Spec.clone(value) + octets = value.asOctets() if not self.minLength < len(octets) < self.maxLength: @@ -63,7 +66,7 @@ class TimeEncoderMixIn(object): options.update(maxChunkSize=1000) return encoder.OctetStringEncoder.encodeValue( - self, value, encodeFun, **options + self, value, asn1Spec, encodeFun, **options ) @@ -77,88 +80,140 @@ class UTCTimeEncoder(TimeEncoderMixIn, encoder.OctetStringEncoder): maxLength = 14 -class SetOfEncoder(encoder.SequenceOfEncoder): +class SetEncoder(encoder.SequenceEncoder): @staticmethod - def _sortComponents(components): - # sort by tags regardless of the Choice value (static sort) - return sorted(components, key=lambda x: isinstance(x, univ.Choice) and x.minTagSet or x.tagSet) + def _componentSortKey(componentAndType): + """Sort SET components by tag - def encodeValue(self, value, encodeFun, **options): - value.verifySizeSpec() - substrate = null - idx = len(value) - if value.typeId == univ.Set.typeId: - namedTypes = value.componentType - comps = [] - compsMap = {} - while idx > 0: - idx -= 1 - if namedTypes: - if namedTypes[idx].isOptional and not value[idx].isValue: - continue - if namedTypes[idx].isDefaulted and value[idx] == namedTypes[idx].asn1Object: - continue + Sort regardless of the Choice value (static sort) + """ + component, asn1Spec = componentAndType - comps.append(value[idx]) - compsMap[id(value[idx])] = namedTypes and namedTypes[idx].isOptional + if asn1Spec is None: + asn1Spec = component - for comp in self._sortComponents(comps): - options.update(ifNotEmpty=compsMap[id(comp)]) - substrate += encodeFun(comp, **options) + if asn1Spec.typeId == univ.Choice.typeId and not asn1Spec.tagSet: + if asn1Spec.tagSet: + return asn1Spec.tagSet + else: + return asn1Spec.componentType.minTagSet else: - components = [encodeFun(x, **options) for x in value] + return asn1Spec.tagSet - # sort by serialized and padded components - if len(components) > 1: - zero = str2octs('\x00') - maxLen = max(map(len, components)) - paddedComponents = [ - (x.ljust(maxLen, zero), x) for x in components + def encodeValue(self, value, asn1Spec, encodeFun, **options): + + substrate = null + + comps = [] + compsMap = {} + + if asn1Spec is None: + # instance of ASN.1 schema + value.verifySizeSpec() + + namedTypes = value.componentType + + for idx, component in enumerate(value.values()): + if namedTypes: + namedType = namedTypes[idx] + + if namedType.isOptional and not component.isValue: + continue + + if namedType.isDefaulted and component == namedType.asn1Object: + continue + + compsMap[id(component)] = namedType + + else: + compsMap[id(component)] = None + + comps.append((component, asn1Spec)) + + else: + # bare Python value + ASN.1 schema + for idx, namedType in enumerate(asn1Spec.componentType.namedTypes): + + try: + component = value[namedType.name] + + except KeyError: + raise error.PyAsn1Error('Component name "%s" not found in %r' % (namedType.name, value)) + + if namedType.isOptional and namedType.name not in value: + continue + + if namedType.isDefaulted and component == namedType.asn1Object: + continue + + compsMap[id(component)] = namedType + comps.append((component, asn1Spec[idx])) + + for comp, compType in sorted(comps, key=self._componentSortKey): + namedType = compsMap[id(comp)] + + if namedType: + options.update(ifNotEmpty=namedType.isOptional) + + chunk = encodeFun(comp, compType, **options) + + # wrap open type blob if needed + if namedType and namedType.openType: + wrapType = namedType.asn1Object + if wrapType.tagSet and not wrapType.isSameTypeWith(comp): + chunk = encodeFun(chunk, wrapType, **options) + + substrate += chunk + + return substrate, True, True + + +class SetOfEncoder(encoder.SequenceOfEncoder): + def encodeValue(self, value, asn1Spec, encodeFun, **options): + if asn1Spec is None: + value.verifySizeSpec() + else: + asn1Spec = asn1Spec.componentType + + components = [encodeFun(x, asn1Spec, **options) + for x in value] + + # sort by serialised and padded components + if len(components) > 1: + zero = str2octs('\x00') + maxLen = max(map(len, components)) + paddedComponents = [ + (x.ljust(maxLen, zero), x) for x in components ] - paddedComponents.sort(key=lambda x: x[0]) + paddedComponents.sort(key=lambda x: x[0]) - components = [x[1] for x in paddedComponents] + components = [x[1] for x in paddedComponents] - substrate = null.join(components) + substrate = null.join(components) return substrate, True, True class SequenceEncoder(encoder.SequenceEncoder): - def encodeValue(self, value, encodeFun, **options): - value.verifySizeSpec() - - namedTypes = value.componentType - substrate = null - - idx = len(value) - while idx > 0: - idx -= 1 - if namedTypes: - if namedTypes[idx].isOptional and not value[idx].isValue: - continue - if namedTypes[idx].isDefaulted and value[idx] == namedTypes[idx].asn1Object: - continue - - options.update(ifNotEmpty=namedTypes and namedTypes[idx].isOptional) - - substrate = encodeFun(value[idx], **options) + substrate - - return substrate, True, True + omitEmptyOptionals = True class SequenceOfEncoder(encoder.SequenceOfEncoder): - def encodeValue(self, value, encodeFun, **options): + def encodeValue(self, value, asn1Spec, encodeFun, **options): + + if options.get('ifNotEmpty', False) and not len(value): + return null, True, True + + if asn1Spec is None: + value.verifySizeSpec() + else: + asn1Spec = asn1Spec.componentType + substrate = null - idx = len(value) - if options.get('ifNotEmpty', False) and not idx: - return substrate, True, True + for idx, component in enumerate(value): + substrate += encodeFun(value[idx], asn1Spec, **options) - value.verifySizeSpec() - while idx > 0: - idx -= 1 - substrate = encodeFun(value[idx], **options) + substrate return substrate, True, True @@ -180,7 +235,7 @@ typeMap.update({ useful.GeneralizedTime.typeId: GeneralizedTimeEncoder(), useful.UTCTime.typeId: UTCTimeEncoder(), # Sequence & Set have same tags as SequenceOf & SetOf - univ.Set.typeId: SetOfEncoder(), + univ.Set.typeId: SetEncoder(), univ.SetOf.typeId: SetOfEncoder(), univ.Sequence.typeId: SequenceEncoder(), univ.SequenceOf.typeId: SequenceOfEncoder() @@ -198,24 +253,44 @@ class Encoder(encoder.Encoder): #: #: Parameters #: ---------- -# value: any pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) -#: A pyasn1 object to encode +#: value: either a Python or pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: A Python or pyasn1 object to encode. If Python object is given, `asnSpec` +#: parameter is required to guide the encoding process. #: -#: defMode: :py:class:`bool` -#: If `False`, produces indefinite length encoding -#: -#: maxChunkSize: :py:class:`int` -#: Maximum chunk size in chunked encoding mode (0 denotes unlimited chunk size) +#: Keyword Args +#: ------------ +#: asn1Spec: +#: Optional ASN.1 schema or value object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative #: #: Returns #: ------- #: : :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) -#: Given ASN.1 object encoded into BER octetstream +#: Given ASN.1 object encoded into BER octet-stream #: #: Raises #: ------ -#: : :py:class:`pyasn1.error.PyAsn1Error` +#: :py:class:`~pyasn1.error.PyAsn1Error` #: On encoding errors +#: +#: Examples +#: -------- +#: Encode Python value into CER with ASN.1 schema +#: +#: .. code-block:: pycon +#: +#: >>> seq = SequenceOf(componentType=Integer()) +#: >>> encode([1, 2, 3], asn1Spec=seq) +#: b'0\x80\x02\x01\x01\x02\x01\x02\x02\x01\x03\x00\x00' +#: +#: Encode ASN.1 value object into CER +#: +#: .. code-block:: pycon +#: +#: >>> seq = SequenceOf(componentType=Integer()) +#: >>> seq.extend([1, 2, 3]) +#: >>> encode(seq) +#: b'0\x80\x02\x01\x01\x02\x01\x02\x02\x01\x03\x00\x00' +#: encode = Encoder(tagMap, typeMap) # EncoderFactory queries class instance and builds a map of tags -> encoders diff --git a/src/pyasn1/codec/der/decoder.py b/src/pyasn1/codec/der/decoder.py index 77b1cec5..f67d0250 100644 --- a/src/pyasn1/codec/der/decoder.py +++ b/src/pyasn1/codec/der/decoder.py @@ -1,11 +1,11 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # -from pyasn1.type import univ from pyasn1.codec.cer import decoder +from pyasn1.type import univ __all__ = ['decode'] @@ -43,15 +43,17 @@ class Decoder(decoder.Decoder): #: Turns DER octet stream into an ASN.1 object. #: -#: Takes DER octetstream and decode it into an ASN.1 object +#: Takes DER octet-stream and decode it into an ASN.1 object #: (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which #: may be a scalar or an arbitrary nested structure. #: #: Parameters #: ---------- #: substrate: :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) -#: DER octetstream +#: DER octet-stream #: +#: Keyword Args +#: ------------ #: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative #: A pyasn1 type object to act as a template guiding the decoder. Depending on the ASN.1 structure #: being decoded, *asn1Spec* may or may not be required. Most common reason for @@ -65,6 +67,28 @@ class Decoder(decoder.Decoder): #: #: Raises #: ------ -#: : :py:class:`pyasn1.error.PyAsn1Error` +#: :py:class:`~pyasn1.error.PyAsn1Error` #: On decoding errors +#: +#: Examples +#: -------- +#: Decode DER serialisation without ASN.1 schema +#: +#: .. code-block:: pycon +#: +#: >>> s, _ = decode(b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03') +#: >>> str(s) +#: SequenceOf: +#: 1 2 3 +#: +#: Decode DER serialisation with ASN.1 schema +#: +#: .. code-block:: pycon +#: +#: >>> seq = SequenceOf(componentType=Integer()) +#: >>> s, _ = decode(b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03', asn1Spec=seq) +#: >>> str(s) +#: SequenceOf: +#: 1 2 3 +#: decode = Decoder(tagMap, typeMap) diff --git a/src/pyasn1/codec/der/encoder.py b/src/pyasn1/codec/der/encoder.py index d2992a96..756d9fe9 100644 --- a/src/pyasn1/codec/der/encoder.py +++ b/src/pyasn1/codec/der/encoder.py @@ -1,32 +1,57 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # -from pyasn1.type import univ +from pyasn1 import error from pyasn1.codec.cer import encoder +from pyasn1.type import univ __all__ = ['encode'] -class SetOfEncoder(encoder.SetOfEncoder): +class SetEncoder(encoder.SetEncoder): @staticmethod - def _sortComponents(components): - # sort by tags depending on the actual Choice value (dynamic sort) - return sorted(components, key=lambda x: isinstance(x, univ.Choice) and x.getComponent().tagSet or x.tagSet) + def _componentSortKey(componentAndType): + """Sort SET components by tag + + Sort depending on the actual Choice value (dynamic sort) + """ + component, asn1Spec = componentAndType + + if asn1Spec is None: + compType = component + else: + compType = asn1Spec + + if compType.typeId == univ.Choice.typeId and not compType.tagSet: + if asn1Spec is None: + return component.getComponent().tagSet + else: + # TODO: move out of sorting key function + names = [namedType.name for namedType in asn1Spec.componentType.namedTypes + if namedType.name in component] + if len(names) != 1: + raise error.PyAsn1Error( + '%s components for Choice at %r' % (len(names) and 'Multiple ' or 'None ', component)) + + # TODO: support nested CHOICE ordering + return asn1Spec[names[0]].tagSet + + else: + return compType.tagSet tagMap = encoder.tagMap.copy() tagMap.update({ # Set & SetOf have same tags - univ.SetOf.tagSet: SetOfEncoder() + univ.Set.tagSet: SetEncoder() }) typeMap = encoder.typeMap.copy() typeMap.update({ # Set & SetOf have same tags - univ.Set.typeId: SetOfEncoder(), - univ.SetOf.typeId: SetOfEncoder() + univ.Set.typeId: SetEncoder() }) @@ -41,22 +66,42 @@ class Encoder(encoder.Encoder): #: #: Parameters #: ---------- -# value: any pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) -#: A pyasn1 object to encode +#: value: either a Python or pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: A Python or pyasn1 object to encode. If Python object is given, `asnSpec` +#: parameter is required to guide the encoding process. #: -#: defMode: :py:class:`bool` -#: If `False`, produces indefinite length encoding -#: -#: maxChunkSize: :py:class:`int` -#: Maximum chunk size in chunked encoding mode (0 denotes unlimited chunk size) +#: Keyword Args +#: ------------ +#: asn1Spec: +#: Optional ASN.1 schema or value object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative #: #: Returns #: ------- #: : :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) -#: Given ASN.1 object encoded into BER octetstream +#: Given ASN.1 object encoded into BER octet-stream #: #: Raises #: ------ -#: : :py:class:`pyasn1.error.PyAsn1Error` +#: :py:class:`~pyasn1.error.PyAsn1Error` #: On encoding errors +#: +#: Examples +#: -------- +#: Encode Python value into DER with ASN.1 schema +#: +#: .. code-block:: pycon +#: +#: >>> seq = SequenceOf(componentType=Integer()) +#: >>> encode([1, 2, 3], asn1Spec=seq) +#: b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03' +#: +#: Encode ASN.1 value object into DER +#: +#: .. code-block:: pycon +#: +#: >>> seq = SequenceOf(componentType=Integer()) +#: >>> seq.extend([1, 2, 3]) +#: >>> encode(seq) +#: b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03' +#: encode = Encoder(tagMap, typeMap) diff --git a/src/pyasn1/codec/native/decoder.py b/src/pyasn1/codec/native/decoder.py index 70b22a85..78fcda68 100644 --- a/src/pyasn1/codec/native/decoder.py +++ b/src/pyasn1/codec/native/decoder.py @@ -1,11 +1,16 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # -from pyasn1.type import base, univ, char, useful, tag -from pyasn1 import debug, error +from pyasn1 import debug +from pyasn1 import error +from pyasn1.type import base +from pyasn1.type import char +from pyasn1.type import tag +from pyasn1.type import univ +from pyasn1.type import useful __all__ = ['decode'] @@ -177,6 +182,8 @@ class Decoder(object): #: pyObject: :py:class:`object` #: A scalar or nested Python objects #: +#: Keyword Args +#: ------------ #: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative #: A pyasn1 type object to act as a template guiding the decoder. It is required #: for successful interpretation of Python objects mapping into their ASN.1 @@ -189,6 +196,19 @@ class Decoder(object): #: #: Raises #: ------ -#: : :py:class:`pyasn1.error.PyAsn1Error` +#: :py:class:`~pyasn1.error.PyAsn1Error` #: On decoding errors +#: +#: Examples +#: -------- +#: Decode native Python object into ASN.1 objects with ASN.1 schema +#: +#: .. code-block:: pycon +#: +#: >>> seq = SequenceOf(componentType=Integer()) +#: >>> s, _ = decode([1, 2, 3], asn1Spec=seq) +#: >>> str(s) +#: SequenceOf: +#: 1 2 3 +#: decode = Decoder(tagMap, typeMap) diff --git a/src/pyasn1/codec/native/encoder.py b/src/pyasn1/codec/native/encoder.py index 3d23d606..87e50f2b 100644 --- a/src/pyasn1/codec/native/encoder.py +++ b/src/pyasn1/codec/native/encoder.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # try: from collections import OrderedDict @@ -10,8 +10,13 @@ try: except ImportError: OrderedDict = dict -from pyasn1.type import base, univ, tag, char, useful -from pyasn1 import debug, error +from pyasn1 import debug +from pyasn1 import error +from pyasn1.type import base +from pyasn1.type import char +from pyasn1.type import tag +from pyasn1.type import univ +from pyasn1.type import useful __all__ = ['encode'] @@ -43,7 +48,7 @@ class OctetStringEncoder(AbstractItemEncoder): class TextStringEncoder(AbstractItemEncoder): def encode(self, value, encodeFun, **options): - return value.prettyPrint() + return str(value) class NullEncoder(AbstractItemEncoder): @@ -207,6 +212,18 @@ class Encoder(object): #: #: Raises #: ------ -#: : :py:class:`pyasn1.error.PyAsn1Error` +#: :py:class:`~pyasn1.error.PyAsn1Error` #: On encoding errors +#: +#: Examples +#: -------- +#: Encode ASN.1 value object into native Python types +#: +#: .. code-block:: pycon +#: +#: >>> seq = SequenceOf(componentType=Integer()) +#: >>> seq.extend([1, 2, 3]) +#: >>> encode(seq) +#: [1, 2, 3] +#: encode = Encoder(tagMap, typeMap) diff --git a/src/pyasn1/compat/binary.py b/src/pyasn1/compat/binary.py index 86f6e5d0..c38a6508 100644 --- a/src/pyasn1/compat/binary.py +++ b/src/pyasn1/compat/binary.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # from sys import version_info diff --git a/src/pyasn1/compat/calling.py b/src/pyasn1/compat/calling.py index fde25d80..c60b50d8 100644 --- a/src/pyasn1/compat/calling.py +++ b/src/pyasn1/compat/calling.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # from sys import version_info diff --git a/src/pyasn1/compat/dateandtime.py b/src/pyasn1/compat/dateandtime.py index 646b9e85..27526ade 100644 --- a/src/pyasn1/compat/dateandtime.py +++ b/src/pyasn1/compat/dateandtime.py @@ -1,12 +1,12 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # -from sys import version_info -from datetime import datetime import time +from datetime import datetime +from sys import version_info __all__ = ['strptime'] diff --git a/src/pyasn1/compat/integer.py b/src/pyasn1/compat/integer.py index 0c426a2e..bb3d099e 100644 --- a/src/pyasn1/compat/integer.py +++ b/src/pyasn1/compat/integer.py @@ -1,12 +1,14 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # import sys + try: import platform + implementation = platform.python_implementation() except (ImportError, AttributeError): diff --git a/src/pyasn1/compat/octets.py b/src/pyasn1/compat/octets.py index f8d47089..a06db5dd 100644 --- a/src/pyasn1/compat/octets.py +++ b/src/pyasn1/compat/octets.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # from sys import version_info diff --git a/src/pyasn1/compat/string.py b/src/pyasn1/compat/string.py index 24e64b68..4d8a045a 100644 --- a/src/pyasn1/compat/string.py +++ b/src/pyasn1/compat/string.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # from sys import version_info diff --git a/src/pyasn1/debug.py b/src/pyasn1/debug.py index 24ac5ce0..ab72fa84 100644 --- a/src/pyasn1/debug.py +++ b/src/pyasn1/debug.py @@ -1,13 +1,14 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # import logging -from pyasn1.compat.octets import octs2ints -from pyasn1 import error + from pyasn1 import __version__ +from pyasn1 import error +from pyasn1.compat.octets import octs2ints __all__ = ['Debug', 'setLogger', 'hexdump'] @@ -17,6 +18,7 @@ flagDecoder = 0x0002 flagAll = 0xffff flagMap = { + 'none': flagNone, 'encoder': flagEncoder, 'decoder': flagDecoder, 'all': flagAll diff --git a/src/pyasn1/error.py b/src/pyasn1/error.py index 85308557..c05e65c6 100644 --- a/src/pyasn1/error.py +++ b/src/pyasn1/error.py @@ -1,18 +1,29 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # class PyAsn1Error(Exception): - pass + """Create pyasn1 exception object + + The `PyAsn1Error` exception represents generic, usually fatal, error. + """ class ValueConstraintError(PyAsn1Error): - pass + """Create pyasn1 exception object + + The `ValueConstraintError` exception indicates an ASN.1 value + constraint violation. + """ class SubstrateUnderrunError(PyAsn1Error): - pass + """Create pyasn1 exception object + + The `SubstrateUnderrunError` exception indicates insufficient serialised + data on input of a deserialisation routine. + """ diff --git a/src/pyasn1/type/base.py b/src/pyasn1/type/base.py index 1b54d8d2..adaab228 100644 --- a/src/pyasn1/type/base.py +++ b/src/pyasn1/type/base.py @@ -1,13 +1,16 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # import sys -from pyasn1.type import constraint, tagmap, tag -from pyasn1.compat import calling + from pyasn1 import error +from pyasn1.compat import calling +from pyasn1.type import constraint +from pyasn1.type import tag +from pyasn1.type import tagmap __all__ = ['Asn1Item', 'Asn1ItemBase', 'AbstractSimpleAsn1Item', 'AbstractConstructedAsn1Item'] @@ -52,6 +55,9 @@ class Asn1ItemBase(Asn1Item): self.__dict__[name] = value + def __str__(self): + return self.prettyPrint() + @property def readOnly(self): return self._readOnly @@ -75,7 +81,7 @@ class Asn1ItemBase(Asn1Item): (:py:mod:`~pyasn1.type.constraint`) are examined when carrying out ASN.1 types comparison. - No Python inheritance relationship between PyASN1 objects is considered. + Python class inheritance relationship is NOT considered. Parameters ---------- @@ -94,18 +100,17 @@ class Asn1ItemBase(Asn1Item): def isSuperTypeOf(self, other, matchTags=True, matchConstraints=True): """Examine |ASN.1| type for subtype relationship with other ASN.1 type. - + ASN.1 tags (:py:mod:`~pyasn1.type.tag`) and constraints (:py:mod:`~pyasn1.type.constraint`) are examined when carrying out ASN.1 types comparison. - No Python inheritance relationship between PyASN1 objects is considered. - + Python class inheritance relationship is NOT considered. Parameters ---------- other: a pyasn1 type object - Class instance representing ASN.1 type. + Class instance representing ASN.1 type. Returns ------- @@ -120,10 +125,13 @@ class Asn1ItemBase(Asn1Item): @staticmethod def isNoValue(*values): for value in values: - if value is not None and value is not noValue: + if value is not noValue: return False return True + def prettyPrint(self, scope=0): + raise NotImplementedError() + # backward compatibility def getTagSet(self): @@ -145,15 +153,42 @@ class Asn1ItemBase(Asn1Item): class NoValue(object): """Create a singleton instance of NoValue class. - NoValue object can be used as an initializer on PyASN1 type class - instantiation to represent ASN.1 type rather than ASN.1 data value. + The *NoValue* sentinel object represents an instance of ASN.1 schema + object as opposed to ASN.1 value object. - No operations other than type comparison can be performed on - a PyASN1 type object. + Only ASN.1 schema-related operations can be performed on ASN.1 + schema objects. + + Warning + ------- + Any operation attempted on the *noValue* object will raise the + *PyAsn1Error* exception. """ - skipMethods = ('__getattribute__', '__getattr__', '__setattr__', '__delattr__', - '__class__', '__init__', '__del__', '__new__', '__repr__', - '__qualname__', '__objclass__', 'im_class', '__sizeof__') + skipMethods = set( + ('__slots__', + # attributes + '__getattribute__', + '__getattr__', + '__setattr__', + '__delattr__', + # class instance + '__class__', + '__init__', + '__del__', + '__new__', + '__repr__', + '__qualname__', + '__objclass__', + 'im_class', + '__sizeof__', + # pickle protocol + '__reduce__', + '__reduce_ex__', + '__getnewargs__', + '__getinitargs__', + '__getstate__', + '__setstate__') + ) _instance = None @@ -161,7 +196,7 @@ class NoValue(object): if cls._instance is None: def getPlug(name): def plug(self, *args, **kw): - raise error.PyAsn1Error('Uninitialized ASN.1 value ("%s" attribute looked up)' % name) + raise error.PyAsn1Error('Attempted "%s" operation on ASN.1 schema object' % name) return plug op_names = [name @@ -181,11 +216,13 @@ class NoValue(object): def __getattr__(self, attr): if attr in self.skipMethods: - raise AttributeError('attribute %s not present' % attr) - raise error.PyAsn1Error('No value for "%s"' % attr) + raise AttributeError('Attribute %s not present' % attr) + + raise error.PyAsn1Error('Attempted "%s" operation on ASN.1 schema object' % attr) def __repr__(self): - return '%s()' % self.__class__.__name__ + return '<%s object at 0x%x>' % (self.__class__.__name__, id(self)) + noValue = NoValue() @@ -197,7 +234,7 @@ class AbstractSimpleAsn1Item(Asn1ItemBase): def __init__(self, value=noValue, **kwargs): Asn1ItemBase.__init__(self, **kwargs) - if value is noValue or value is None: + if value is noValue: value = self.defaultValue else: value = self.prettyIn(value) @@ -211,17 +248,21 @@ class AbstractSimpleAsn1Item(Asn1ItemBase): self._value = value def __repr__(self): - representation = [] - if self._value is not self.defaultValue: - representation.append(self.prettyOut(self._value)) - if self.tagSet is not self.__class__.tagSet: - representation.append('tagSet=%r' % (self.tagSet,)) - if self.subtypeSpec is not self.__class__.subtypeSpec: - representation.append('subtypeSpec=%r' % (self.subtypeSpec,)) - return '%s(%s)' % (self.__class__.__name__, ', '.join(representation)) + representation = '%s %s object at 0x%x' % ( + self.__class__.__name__, self.isValue and 'value' or 'schema', id(self) + ) - def __str__(self): - return str(self._value) + for attr, value in self.readOnly.items(): + if value: + representation += ' %s %s' % (attr, value) + + if self.isValue: + value = self.prettyPrint() + if len(value) > 32: + value = value[:16] + '...' + value[-16:] + representation += ' payload [%s]' % value + + return '<%s>' % representation def __eq__(self, other): return self is other and True or self._value == other @@ -253,53 +294,50 @@ class AbstractSimpleAsn1Item(Asn1ItemBase): @property def isValue(self): - """Indicate if |ASN.1| object represents ASN.1 type or ASN.1 value. + """Indicate that |ASN.1| object represents ASN.1 value. - In other words, if *isValue* is `True`, then the ASN.1 object is - initialized. + If *isValue* is `False` then this object represents just ASN.1 schema. + + If *isValue* is `True` then, in addition to its ASN.1 schema features, + this object can also be used like a Python built-in object (e.g. `int`, + `str`, `dict` etc.). Returns ------- : :class:`bool` - :class:`True` if object represents ASN.1 value and type, - :class:`False` if object represents just ASN.1 type. + :class:`False` if object represents just ASN.1 schema. + :class:`True` if object represents ASN.1 schema and can be used as a normal value. Note ---- - There is an important distinction between PyASN1 type and value objects. - The PyASN1 type objects can only participate in ASN.1 type - operations (subtyping, comparison etc) and serve as a - blueprint for serialization codecs to resolve ambiguous types. + There is an important distinction between PyASN1 schema and value objects. + The PyASN1 schema objects can only participate in ASN.1 schema-related + operations (e.g. defining or testing the structure of the data). Most + obvious uses of ASN.1 schema is to guide serialisation codecs whilst + encoding/decoding serialised ASN.1 contents. - The PyASN1 value objects can additionally participate in most - of built-in Python operations. + The PyASN1 value objects can **additionally** participate in many operations + involving regular Python objects (e.g. arithmetic, comprehension etc). """ return self._value is not noValue def clone(self, value=noValue, **kwargs): - """Create a copy of a |ASN.1| type or object. + """Create a modified version of |ASN.1| schema or value object. - Any parameters to the *clone()* method will replace corresponding - properties of the |ASN.1| object. + The `clone()` method accepts the same set arguments as |ASN.1| + class takes on instantiation except that all arguments + of the `clone()` method are optional. - Parameters - ---------- - value: :class:`tuple`, :class:`str` or |ASN.1| object - Initialization value to pass to new ASN.1 object instead of - inheriting one from the caller. + Whatever arguments are supplied, they are used to create a copy + of `self` taking precedence over the ones used to instantiate `self`. - tagSet: :py:class:`~pyasn1.type.tag.TagSet` - Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller - - subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller - - Returns - ------- - : - new instance of |ASN.1| type/value + Note + ---- + Due to the immutable nature of the |ASN.1| object, if no arguments + are supplied, no new |ASN.1| object will be created and `self` will + be returned instead. """ - if value is noValue or value is None: + if value is noValue: if not kwargs: return self @@ -311,37 +349,53 @@ class AbstractSimpleAsn1Item(Asn1ItemBase): return self.__class__(value, **initilaizers) def subtype(self, value=noValue, **kwargs): - """Create a copy of a |ASN.1| type or object. + """Create a specialization of |ASN.1| schema or value object. - Any parameters to the *subtype()* method will be added to the corresponding - properties of the |ASN.1| object. + The subtype relationship between ASN.1 types has no correlation with + subtype relationship between Python types. ASN.1 type is mainly identified + by its tag(s) (:py:class:`~pyasn1.type.tag.TagSet`) and value range + constraints (:py:class:`~pyasn1.type.constraint.ConstraintsIntersection`). + These ASN.1 type properties are implemented as |ASN.1| attributes. - Parameters - ---------- - value: :class:`tuple`, :class:`str` or |ASN.1| object - Initialization value to pass to new ASN.1 object instead of - inheriting one from the caller. + The `subtype()` method accepts the same set arguments as |ASN.1| + class takes on instantiation except that all parameters + of the `subtype()` method are optional. - implicitTag: :py:class:`~pyasn1.type.tag.Tag` - Implicitly apply given ASN.1 tag object to caller's - :py:class:`~pyasn1.type.tag.TagSet`, then use the result as - new object's ASN.1 tag(s). + With the exception of the arguments described below, the rest of + supplied arguments they are used to create a copy of `self` taking + precedence over the ones used to instantiate `self`. - explicitTag: :py:class:`~pyasn1.type.tag.Tag` - Explicitly apply given ASN.1 tag object to caller's - :py:class:`~pyasn1.type.tag.TagSet`, then use the result as - new object's ASN.1 tag(s). + The following arguments to `subtype()` create a ASN.1 subtype out of + |ASN.1| type: - subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Add ASN.1 constraints object to one of the caller, then - use the result as new object's ASN.1 constraints. + Other Parameters + ---------------- + implicitTag: :py:class:`~pyasn1.type.tag.Tag` + Implicitly apply given ASN.1 tag object to `self`'s + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). - Returns - ------- - : - new instance of |ASN.1| type/value + explicitTag: :py:class:`~pyasn1.type.tag.Tag` + Explicitly apply given ASN.1 tag object to `self`'s + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Add ASN.1 constraints object to one of the `self`'s, then + use the result as new object's ASN.1 constraints. + + Returns + ------- + : + new instance of |ASN.1| schema or value object + + Note + ---- + Due to the immutable nature of the |ASN.1| object, if no arguments + are supplied, no new |ASN.1| object will be created and `self` will + be returned instead. """ - if value is noValue or value is None: + if value is noValue: if not kwargs: return self @@ -369,21 +423,7 @@ class AbstractSimpleAsn1Item(Asn1ItemBase): return str(value) def prettyPrint(self, scope=0): - """Provide human-friendly printable object representation. - - Returns - ------- - : :class:`str` - human-friendly type and/or value representation. - """ - if self.isValue: - return self.prettyOut(self._value) - else: - return '' - - # XXX Compatibility stub - def prettyPrinter(self, scope=0): - return self.prettyPrint(scope) + return self.prettyOut(self._value) # noinspection PyUnusedLocal def prettyPrintType(self, scope=0): @@ -408,22 +448,6 @@ class AbstractSimpleAsn1Item(Asn1ItemBase): # of types for Sequence/Set/Choice. # -def setupComponent(): - """Returns a sentinel value. - - Indicates to a constructed type to set up its inner component so that it - can be referred to. This is useful in situation when you want to populate - descendants of a constructed type what requires being able to refer to - their parent types along the way. - - Example - ------- - - >>> constructed['record'] = setupComponent() - >>> constructed['record']['scalar'] = 42 - """ - return noValue - class AbstractConstructedAsn1Item(Asn1ItemBase): @@ -446,20 +470,18 @@ class AbstractConstructedAsn1Item(Asn1ItemBase): self._componentValues = [] def __repr__(self): - representation = [] - if self.componentType is not self.__class__.componentType: - representation.append('componentType=%r' % (self.componentType,)) - if self.tagSet is not self.__class__.tagSet: - representation.append('tagSet=%r' % (self.tagSet,)) - if self.subtypeSpec is not self.__class__.subtypeSpec: - representation.append('subtypeSpec=%r' % (self.subtypeSpec,)) - representation = '%s(%s)' % (self.__class__.__name__, ', '.join(representation)) - if self._componentValues: - for idx, component in enumerate(self._componentValues): - if component is None or component is noValue: - continue - representation += '.setComponentByPosition(%d, %s)' % (idx, repr(component)) - return representation + representation = '%s %s object at 0x%x' % ( + self.__class__.__name__, self.isValue and 'value' or 'schema', id(self) + ) + + for attr, value in self.readOnly.items(): + if value is not noValue: + representation += ' %s=%r' % (attr, value) + + if self.isValue and self._componentValues: + representation += ' payload [%s]' % ', '.join([repr(x) for x in self._componentValues]) + + return '<%s>' % representation def __eq__(self, other): return self is other and True or self._componentValues == other @@ -486,31 +508,35 @@ class AbstractConstructedAsn1Item(Asn1ItemBase): def __bool__(self): return self._componentValues and True or False + def __len__(self): + return len(self._componentValues) + def _cloneComponentValues(self, myClone, cloneValueFlag): pass def clone(self, **kwargs): - """Create a copy of a |ASN.1| type or object. + """Create a modified version of |ASN.1| schema object. - Any parameters to the *clone()* method will replace corresponding - properties of the |ASN.1| object. + The `clone()` method accepts the same set arguments as |ASN.1| + class takes on instantiation except that all arguments + of the `clone()` method are optional. - Parameters - ---------- - tagSet: :py:class:`~pyasn1.type.tag.TagSet` - Object representing non-default ASN.1 tag(s) + Whatever arguments are supplied, they are used to create a copy + of `self` taking precedence over the ones used to instantiate `self`. - subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 subtype constraint(s) - - sizeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 size constraint(s) + Possible values of `self` are never copied over thus `clone()` can + only create a new schema object. Returns ------- : new instance of |ASN.1| type/value + Note + ---- + Due to the mutable nature of the |ASN.1| object, even if no arguments + are supplied, new |ASN.1| object will always be created as a shallow + copy of `self`. """ cloneValueFlag = kwargs.pop('cloneValueFlag', False) @@ -525,27 +551,46 @@ class AbstractConstructedAsn1Item(Asn1ItemBase): return clone def subtype(self, **kwargs): - """Create a copy of a |ASN.1| type or object. + """Create a specialization of |ASN.1| schema object. - Any parameters to the *subtype()* method will be added to the corresponding - properties of the |ASN.1| object. + The `subtype()` method accepts the same set arguments as |ASN.1| + class takes on instantiation except that all parameters + of the `subtype()` method are optional. - Parameters - ---------- - tagSet: :py:class:`~pyasn1.type.tag.TagSet` - Object representing non-default ASN.1 tag(s) + With the exception of the arguments described below, the rest of + supplied arguments they are used to create a copy of `self` taking + precedence over the ones used to instantiate `self`. + + The following arguments to `subtype()` create a ASN.1 subtype out of + |ASN.1| type. + + Other Parameters + ---------------- + implicitTag: :py:class:`~pyasn1.type.tag.Tag` + Implicitly apply given ASN.1 tag object to `self`'s + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + explicitTag: :py:class:`~pyasn1.type.tag.Tag` + Explicitly apply given ASN.1 tag object to `self`'s + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 subtype constraint(s) + Add ASN.1 constraints object to one of the `self`'s, then + use the result as new object's ASN.1 constraints. - sizeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 size constraint(s) Returns ------- : new instance of |ASN.1| type/value + Note + ---- + Due to the immutable nature of the |ASN.1| object, if no arguments + are supplied, no new |ASN.1| object will be created and `self` will + be returned instead. """ initializers = self.readOnly.copy() @@ -586,9 +631,6 @@ class AbstractConstructedAsn1Item(Asn1ItemBase): self[k] = kwargs[k] return self - def __len__(self): - return len(self._componentValues) - def clear(self): self._componentValues = [] diff --git a/src/pyasn1/type/char.py b/src/pyasn1/type/char.py index 60f9d978..493badb9 100644 --- a/src/pyasn1/type/char.py +++ b/src/pyasn1/type/char.py @@ -1,13 +1,14 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # import sys -from pyasn1.type import univ, tag -from pyasn1 import error +from pyasn1 import error +from pyasn1.type import tag +from pyasn1.type import univ __all__ = ['NumericString', 'PrintableString', 'TeletexString', 'T61String', 'VideotexString', 'IA5String', 'GraphicString', 'VisibleString', 'ISO646String', @@ -18,16 +19,16 @@ noValue = univ.noValue class AbstractCharacterString(univ.OctetString): - """Creates |ASN.1| type or object. + """Creates |ASN.1| schema or value object. |ASN.1| objects are immutable and duck-type Python 2 :class:`unicode` or Python 3 :class:`str`. When used in octet-stream context, |ASN.1| type assumes "|encoding|" encoding. - Parameters - ---------- + Keyword Args + ------------ value: :class:`unicode`, :class:`str`, :class:`bytes` or |ASN.1| object unicode object (Python 2) or string (Python 3), alternatively string - (Python 2) or bytes (Python 3) representing octet-stream of serialized + (Python 2) or bytes (Python 3) representing octet-stream of serialised unicode string (note `encoding` parameter) or |ASN.1| class instance. tagSet: :py:class:`~pyasn1.type.tag.TagSet` @@ -43,14 +44,16 @@ class AbstractCharacterString(univ.OctetString): Raises ------ - : :py:class:`pyasn1.error.PyAsn1Error` + :py:class:`~pyasn1.error.PyAsn1Error` On constraint violation or bad initializer. """ if sys.version_info[0] <= 2: def __str__(self): try: + # `str` is Py2 text representation return self._value.encode(self.encoding) + except UnicodeEncodeError: raise error.PyAsn1Error( "Can't encode string '%s' with codec %s" % (self._value, self.encoding) @@ -85,6 +88,7 @@ class AbstractCharacterString(univ.OctetString): else: def __str__(self): + # `unicode` is Py3 text representation return str(self._value) def __bytes__(self): @@ -119,82 +123,25 @@ class AbstractCharacterString(univ.OctetString): def asNumbers(self, padding=True): return tuple(bytes(self)) + # + # See OctetString.prettyPrint() for the explanation + # + def prettyOut(self, value): return value + def prettyPrint(self, scope=0): + # first see if subclass has its own .prettyOut() + value = self.prettyOut(self._value) + + if value is not self._value: + return value + + return AbstractCharacterString.__str__(self) + def __reversed__(self): return reversed(self._value) - def clone(self, value=noValue, **kwargs): - """Creates a copy of a |ASN.1| type or object. - - Any parameters to the *clone()* method will replace corresponding - properties of the |ASN.1| object. - - Parameters - ---------- - value: :class:`unicode`, :class:`str`, :class:`bytes` or |ASN.1| object - unicode object (Python 2) or string (Python 3), alternatively string - (Python 2) or bytes (Python 3) representing octet-stream of serialized - unicode string (note `encoding` parameter) or |ASN.1| class instance. - - tagSet: :py:class:`~pyasn1.type.tag.TagSet` - Object representing non-default ASN.1 tag(s) - - subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 subtype constraint(s) - - encoding: :py:class:`str` - Unicode codec ID to encode/decode :py:class:`unicode` (Python 2) or - :py:class:`str` (Python 3) the payload when |ASN.1| object is used - in octet-stream context. - - Returns - ------- - : - new instance of |ASN.1| type/value - - """ - return univ.OctetString.clone(self, value, **kwargs) - - def subtype(self, value=noValue, **kwargs): - """Creates a copy of a |ASN.1| type or object. - - Any parameters to the *subtype()* method will be added to the corresponding - properties of the |ASN.1| object. - - Parameters - ---------- - value: :class:`unicode`, :class:`str`, :class:`bytes` or |ASN.1| object - unicode object (Python 2) or string (Python 3), alternatively string - (Python 2) or bytes (Python 3) representing octet-stream of serialized - unicode string (note `encoding` parameter) or |ASN.1| class instance. - - implicitTag: :py:class:`~pyasn1.type.tag.Tag` - Implicitly apply given ASN.1 tag object to caller's - :py:class:`~pyasn1.type.tag.TagSet`, then use the result as - new object's ASN.1 tag(s). - - explicitTag: :py:class:`~pyasn1.type.tag.Tag` - Explicitly apply given ASN.1 tag object to caller's - :py:class:`~pyasn1.type.tag.TagSet`, then use the result as - new object's ASN.1 tag(s). - - subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 subtype constraint(s) - - encoding: :py:class:`str` - Unicode codec ID to encode/decode :py:class:`unicode` (Python 2) or - :py:class:`str` (Python 3) the payload when |ASN.1| object is used - in octet-stream context. - - Returns - ------- - : - new instance of |ASN.1| type/value - - """ - return univ.OctetString.subtype(self, value, **kwargs) class NumericString(AbstractCharacterString): __doc__ = AbstractCharacterString.__doc__ diff --git a/src/pyasn1/type/constraint.py b/src/pyasn1/type/constraint.py index 35bb0e24..a7043310 100644 --- a/src/pyasn1/type/constraint.py +++ b/src/pyasn1/type/constraint.py @@ -1,26 +1,23 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # Original concept and code by Mike C. Fletcher. # import sys + from pyasn1.type import error -__all__ = ['SingleValueConstraint', 'ContainedSubtypeConstraint', 'ValueRangeConstraint', - 'ValueSizeConstraint', 'PermittedAlphabetConstraint', 'InnerTypeConstraint', - 'ConstraintsExclusion', 'ConstraintsIntersection', 'ConstraintsUnion'] +__all__ = ['SingleValueConstraint', 'ContainedSubtypeConstraint', + 'ValueRangeConstraint', 'ValueSizeConstraint', + 'PermittedAlphabetConstraint', 'InnerTypeConstraint', + 'ConstraintsExclusion', 'ConstraintsIntersection', + 'ConstraintsUnion'] class AbstractConstraint(object): - """Abstract base-class for constraint objects - - Constraints should be stored in a simple sequence in the - namespace of their client Asn1Item sub-classes in cases - when ASN.1 constraint is define. - """ def __init__(self, *values): self._valueMap = set() @@ -40,10 +37,12 @@ class AbstractConstraint(object): ) def __repr__(self): - return '%s(%s)' % ( - self.__class__.__name__, - ', '.join([repr(x) for x in self._values]) - ) + representation = '%s object at 0x%x' % (self.__class__.__name__, id(self)) + + if self._values: + representation += ' consts %s' % ', '.join([repr(x) for x in self._values]) + + return '<%s>' % representation def __eq__(self, other): return self is other and True or self._values == other @@ -96,9 +95,39 @@ class AbstractConstraint(object): otherConstraint == self or otherConstraint in self._valueMap) -class SingleValueConstraint(AbstractConstraint): - """Value must be part of defined values constraint""" +class SingleValueConstraint(AbstractConstraint): + """Create a SingleValueConstraint object. + + The SingleValueConstraint satisfies any value that + is present in the set of permitted values. + + The SingleValueConstraint object can be applied to + any ASN.1 type. + + Parameters + ---------- + \*values: :class:`int` + Full set of values permitted by this constraint object. + + Examples + -------- + .. code-block:: python + + class DivisorOfSix(Integer): + ''' + ASN.1 specification: + + Divisor-Of-6 ::= INTEGER (1 | 2 | 3 | 6) + ''' + subtypeSpec = SingleValueConstraint(1, 2, 3, 6) + + # this will succeed + divisor_of_six = DivisorOfSix(1) + + # this will raise ValueConstraintError + divisor_of_six = DivisorOfSix(7) + """ def _setValues(self, values): self._values = values self._set = set(values) @@ -109,16 +138,85 @@ class SingleValueConstraint(AbstractConstraint): class ContainedSubtypeConstraint(AbstractConstraint): - """Value must satisfy all of defined set of constraints""" + """Create a ContainedSubtypeConstraint object. + The ContainedSubtypeConstraint satisfies any value that + is present in the set of permitted values and also + satisfies included constraints. + + The ContainedSubtypeConstraint object can be applied to + any ASN.1 type. + + Parameters + ---------- + \*values: + Full set of values and constraint objects permitted + by this constraint object. + + Examples + -------- + .. code-block:: python + + class DivisorOfEighteen(Integer): + ''' + ASN.1 specification: + + Divisors-of-18 ::= INTEGER (INCLUDES Divisors-of-6 | 9 | 18) + ''' + subtypeSpec = ContainedSubtypeConstraint( + SingleValueConstraint(1, 2, 3, 6), 9, 18 + ) + + # this will succeed + divisor_of_eighteen = DivisorOfEighteen(9) + + # this will raise ValueConstraintError + divisor_of_eighteen = DivisorOfEighteen(10) + """ def _testValue(self, value, idx): - for c in self._values: - c(value, idx) + for constraint in self._values: + if isinstance(constraint, AbstractConstraint): + constraint(value, idx) + elif value not in self._set: + raise error.ValueConstraintError(value) class ValueRangeConstraint(AbstractConstraint): - """Value must be within start and stop values (inclusive)""" + """Create a ValueRangeConstraint object. + The ValueRangeConstraint satisfies any value that + falls in the range of permitted values. + + The ValueRangeConstraint object can only be applied + to :class:`~pyasn1.type.univ.Integer` and + :class:`~pyasn1.type.univ.Real` types. + + Parameters + ---------- + start: :class:`int` + Minimum permitted value in the range (inclusive) + + end: :class:`int` + Maximum permitted value in the range (inclusive) + + Examples + -------- + .. code-block:: python + + class TeenAgeYears(Integer): + ''' + ASN.1 specification: + + TeenAgeYears ::= INTEGER (13 .. 19) + ''' + subtypeSpec = ValueRangeConstraint(13, 19) + + # this will succeed + teen_year = TeenAgeYears(18) + + # this will raise ValueConstraintError + teen_year = TeenAgeYears(20) + """ def _testValue(self, value, idx): if value < self.start or value > self.stop: raise error.ValueConstraintError(value) @@ -140,8 +238,59 @@ class ValueRangeConstraint(AbstractConstraint): class ValueSizeConstraint(ValueRangeConstraint): - """len(value) must be within start and stop values (inclusive)""" + """Create a ValueSizeConstraint object. + The ValueSizeConstraint satisfies any value for + as long as its size falls within the range of + permitted sizes. + + The ValueSizeConstraint object can be applied + to :class:`~pyasn1.type.univ.BitString`, + :class:`~pyasn1.type.univ.OctetString` (including + all :ref:`character ASN.1 types `), + :class:`~pyasn1.type.univ.SequenceOf` + and :class:`~pyasn1.type.univ.SetOf` types. + + Parameters + ---------- + minimum: :class:`int` + Minimum permitted size of the value (inclusive) + + maximum: :class:`int` + Maximum permitted size of the value (inclusive) + + Examples + -------- + .. code-block:: python + + class BaseballTeamRoster(SetOf): + ''' + ASN.1 specification: + + BaseballTeamRoster ::= SET SIZE (1..25) OF PlayerNames + ''' + componentType = PlayerNames() + subtypeSpec = ValueSizeConstraint(1, 25) + + # this will succeed + team = BaseballTeamRoster() + team.extend(['Jan', 'Matej']) + encode(team) + + # this will raise ValueConstraintError + team = BaseballTeamRoster() + team.extend(['Jan'] * 26) + encode(team) + + Note + ---- + Whenever ValueSizeConstraint is applied to mutable types + (e.g. :class:`~pyasn1.type.univ.SequenceOf`, + :class:`~pyasn1.type.univ.SetOf`), constraint + validation only happens at the serialisation phase rather + than schema instantiation phase (as it is with immutable + types). + """ def _testValue(self, value, idx): valueSize = len(value) if valueSize < self.start or valueSize > self.stop: @@ -149,6 +298,40 @@ class ValueSizeConstraint(ValueRangeConstraint): class PermittedAlphabetConstraint(SingleValueConstraint): + """Create a PermittedAlphabetConstraint object. + + The PermittedAlphabetConstraint satisfies any character + string for as long as all its characters are present in + the set of permitted characters. + + The PermittedAlphabetConstraint object can only be applied + to the :ref:`character ASN.1 types ` such as + :class:`~pyasn1.type.char.IA5String`. + + Parameters + ---------- + \*alphabet: :class:`str` + Full set of characters permitted by this constraint object. + + Examples + -------- + .. code-block:: python + + class BooleanValue(IA5String): + ''' + ASN.1 specification: + + BooleanValue ::= IA5String (FROM ('T' | 'F')) + ''' + subtypeSpec = PermittedAlphabetConstraint('T', 'F') + + # this will succeed + truth = BooleanValue('T') + truth = BooleanValue('TF') + + # this will raise ValueConstraintError + garbage = BooleanValue('TAF') + """ def _setValues(self, values): self._values = values self._set = set(values) @@ -160,7 +343,7 @@ class PermittedAlphabetConstraint(SingleValueConstraint): # This is a bit kludgy, meaning two op modes within a single constraint class InnerTypeConstraint(AbstractConstraint): - """Value must satisfy type and presense constraints""" + """Value must satisfy the type and presence constraints""" def _testValue(self, value, idx): if self.__singleTypeConstraint: @@ -184,11 +367,50 @@ class InnerTypeConstraint(AbstractConstraint): AbstractConstraint._setValues(self, values) -# Boolean ops on constraints +# Logic operations on constraints class ConstraintsExclusion(AbstractConstraint): - """Value must not fit the single constraint""" + """Create a ConstraintsExclusion logic operator object. + The ConstraintsExclusion logic operator succeeds when the + value does *not* satisfy the operand constraint. + + The ConstraintsExclusion object can be applied to + any constraint and logic operator object. + + Parameters + ---------- + constraint: + Constraint or logic operator object. + + Examples + -------- + .. code-block:: python + + class Lipogramme(IA5STRING): + ''' + ASN.1 specification: + + Lipogramme ::= + IA5String (FROM (ALL EXCEPT ("e"|"E"))) + ''' + subtypeSpec = ConstraintsExclusion( + PermittedAlphabetConstraint('e', 'E') + ) + + # this will succeed + lipogramme = Lipogramme('A work of fiction?') + + # this will raise ValueConstraintError + lipogramme = Lipogramme('Eel') + + Warning + ------- + The above example involving PermittedAlphabetConstraint might + not work due to the way how PermittedAlphabetConstraint works. + The other constraints might work with ConstraintsExclusion + though. + """ def _testValue(self, value, idx): try: self._values[0](value, idx) @@ -200,11 +422,11 @@ class ConstraintsExclusion(AbstractConstraint): def _setValues(self, values): if len(values) != 1: raise error.PyAsn1Error('Single constraint expected') + AbstractConstraint._setValues(self, values) class AbstractConstraintSet(AbstractConstraint): - """Value must not satisfy the single constraint""" def __getitem__(self, idx): return self._values[idx] @@ -232,16 +454,88 @@ class AbstractConstraintSet(AbstractConstraint): class ConstraintsIntersection(AbstractConstraintSet): - """Value must satisfy all constraints""" + """Create a ConstraintsIntersection logic operator object. + The ConstraintsIntersection logic operator only succeeds + if *all* its operands succeed. + + The ConstraintsIntersection object can be applied to + any constraint and logic operator objects. + + The ConstraintsIntersection object duck-types the immutable + container object like Python :py:class:`tuple`. + + Parameters + ---------- + \*constraints: + Constraint or logic operator objects. + + Examples + -------- + .. code-block:: python + + class CapitalAndSmall(IA5String): + ''' + ASN.1 specification: + + CapitalAndSmall ::= + IA5String (FROM ("A".."Z"|"a".."z")) + ''' + subtypeSpec = ConstraintsIntersection( + PermittedAlphabetConstraint('A', 'Z'), + PermittedAlphabetConstraint('a', 'z') + ) + + # this will succeed + capital_and_small = CapitalAndSmall('Hello') + + # this will raise ValueConstraintError + capital_and_small = CapitalAndSmall('hello') + """ def _testValue(self, value, idx): for constraint in self._values: constraint(value, idx) class ConstraintsUnion(AbstractConstraintSet): - """Value must satisfy at least one constraint""" + """Create a ConstraintsUnion logic operator object. + The ConstraintsUnion logic operator only succeeds if + *at least a single* operand succeeds. + + The ConstraintsUnion object can be applied to + any constraint and logic operator objects. + + The ConstraintsUnion object duck-types the immutable + container object like Python :py:class:`tuple`. + + Parameters + ---------- + \*constraints: + Constraint or logic operator objects. + + Examples + -------- + .. code-block:: python + + class CapitalOrSmall(IA5String): + ''' + ASN.1 specification: + + CapitalOrSmall ::= + IA5String (FROM ("A".."Z") | FROM ("a".."z")) + ''' + subtypeSpec = ConstraintsIntersection( + PermittedAlphabetConstraint('A', 'Z'), + PermittedAlphabetConstraint('a', 'z') + ) + + # this will succeed + capital_or_small = CapitalAndSmall('Hello') + + # this will raise ValueConstraintError + capital_or_small = CapitalOrSmall('hello!') + """ def _testValue(self, value, idx): for constraint in self._values: try: @@ -250,9 +544,13 @@ class ConstraintsUnion(AbstractConstraintSet): pass else: return + raise error.ValueConstraintError( - 'all of %s failed for \"%s\"' % (self._values, value) + 'all of %s failed for "%s"' % (self._values, value) ) -# XXX +# TODO: +# refactor InnerTypeConstraint # add tests for type check +# implement other constraint types +# make constraint validation easy to skip diff --git a/src/pyasn1/type/error.py b/src/pyasn1/type/error.py index cbfa276a..b2056bd6 100644 --- a/src/pyasn1/type/error.py +++ b/src/pyasn1/type/error.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # from pyasn1.error import PyAsn1Error diff --git a/src/pyasn1/type/namedtype.py b/src/pyasn1/type/namedtype.py index 7a51f186..f162d194 100644 --- a/src/pyasn1/type/namedtype.py +++ b/src/pyasn1/type/namedtype.py @@ -1,14 +1,23 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # import sys -from pyasn1.type import tag, tagmap -from pyasn1 import error -__all__ = ['NamedType', 'OptionalNamedType', 'DefaultedNamedType', 'NamedTypes'] +from pyasn1 import error +from pyasn1.type import tag +from pyasn1.type import tagmap + +__all__ = ['NamedType', 'OptionalNamedType', 'DefaultedNamedType', + 'NamedTypes'] + +try: + any + +except NameError: + any = lambda x: bool(filter(bool, x)) class NamedType(object): @@ -30,13 +39,19 @@ class NamedType(object): isOptional = False isDefaulted = False - def __init__(self, name, asn1Object): + def __init__(self, name, asn1Object, openType=None): self.__name = name self.__type = asn1Object self.__nameAndType = name, asn1Object + self.__openType = openType def __repr__(self): - return '%s(%r, %r)' % (self.__class__.__name__, self.__name, self.__type) + representation = '%s=%r' % (self.name, self.asn1Object) + + if self.openType: + representation += ' openType: %r' % self.openType + + return '<%s object at 0x%x type %s>' % (self.__class__.__name__, id(self), representation) def __eq__(self, other): return self.__nameAndType == other @@ -68,11 +83,15 @@ class NamedType(object): @property def name(self): return self.__name - + @property def asn1Object(self): return self.__type + @property + def openType(self): + return self.__openType + # Backward compatibility def getName(self): @@ -105,6 +124,31 @@ class NamedTypes(object): Parameters ---------- *namedTypes: :class:`~pyasn1.type.namedtype.NamedType` + + Examples + -------- + + .. code-block:: python + + class Description(Sequence): + ''' + ASN.1 specification: + + Description ::= SEQUENCE { + surname IA5String, + first-name IA5String OPTIONAL, + age INTEGER DEFAULT 40 + } + ''' + componentType = NamedTypes( + NamedType('surname', IA5String()), + OptionalNamedType('first-name', IA5String()), + DefaultedNamedType('age', Integer(40)) + ) + + descr = Description() + descr['surname'] = 'Smith' + descr['first-name'] = 'John' """ def __init__(self, *namedTypes, **kwargs): self.__namedTypes = namedTypes @@ -115,8 +159,11 @@ class NamedTypes(object): self.__ambiguousTypes = 'terminal' not in kwargs and self.__computeAmbiguousTypes() or {} self.__uniqueTagMap = self.__computeTagMaps(unique=True) self.__nonUniqueTagMap = self.__computeTagMaps(unique=False) - self.__hasOptionalOrDefault = bool([True for namedType in self.__namedTypes - if namedType.isDefaulted or namedType.isOptional]) + self.__hasOptionalOrDefault = any([True for namedType in self.__namedTypes + if namedType.isDefaulted or namedType.isOptional]) + self.__hasOpenTypes = any([True for namedType in self.__namedTypes + if namedType.openType]) + self.__requiredComponents = frozenset( [idx for idx, nt in enumerate(self.__namedTypes) if not nt.isOptional and not nt.isDefaulted] ) @@ -125,9 +172,8 @@ class NamedTypes(object): self.__items = tuple([(namedType.name, namedType.asn1Object) for namedType in self.__namedTypes]) def __repr__(self): - return '%s(%s)' % ( - self.__class__.__name__, ', '.join([repr(x) for x in self.__namedTypes]) - ) + representation = ', '.join(['%r' % x for x in self.__namedTypes]) + return '<%s object at 0x%x types %s>' % (self.__class__.__name__, id(self), representation) def __eq__(self, other): return self.__namedTypes == other @@ -331,7 +377,7 @@ class NamedTypes(object): def getTagMapNearPosition(self, idx): """Return ASN.1 types that are allowed at or past given field position. - Some ASN.1 serialization allow for skipping optional and defaulted fields. + Some ASN.1 serialisation allow for skipping optional and defaulted fields. Some constructed ASN.1 types allow reordering of the fields. When recovering such objects it may be important to know which types can possibly be present at any given position in the field sets. @@ -360,7 +406,7 @@ class NamedTypes(object): def getPositionNearType(self, tagSet, idx): """Return the closest field position where given ASN.1 type is allowed. - Some ASN.1 serialization allow for skipping optional and defaulted fields. + Some ASN.1 serialisation allow for skipping optional and defaulted fields. Some constructed ASN.1 types allow reordering of the fields. When recovering such objects it may be important to know at which field position, in field set, given *tagSet* is allowed at or past *idx* position. @@ -410,7 +456,7 @@ class NamedTypes(object): def minTagSet(self): """Return the minimal TagSet among ASN.1 type in callee *NamedTypes*. - Some ASN.1 types/serialization protocols require ASN.1 types to be + Some ASN.1 types/serialisation protocols require ASN.1 types to be arranged based on their numerical tag value. The *minTagSet* property returns that. @@ -452,7 +498,6 @@ class NamedTypes(object): Example ------- - .. code-block:: python OuterType ::= CHOICE { @@ -477,7 +522,6 @@ class NamedTypes(object): Example ------- - .. code-block:: python OuterType ::= CHOICE { @@ -502,9 +546,13 @@ class NamedTypes(object): def hasOptionalOrDefault(self): return self.__hasOptionalOrDefault + @property + def hasOpenTypes(self): + return self.__hasOpenTypes + @property def namedTypes(self): - return iter(self.__namedTypes) + return tuple(self.__namedTypes) @property def requiredComponents(self): diff --git a/src/pyasn1/type/namedval.py b/src/pyasn1/type/namedval.py index 008ddc04..59257e48 100644 --- a/src/pyasn1/type/namedval.py +++ b/src/pyasn1/type/namedval.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # ASN.1 named integers # @@ -23,26 +23,34 @@ class NamedValues(object): Parameters ---------- + \*args: variable number of two-element :py:class:`tuple` - \*args: variable number of two-element :py:class:`tuple` - \*\*kwargs: keyword parameters of: - name: :py:class:`str` - Value name - + Value label + value: :py:class:`int` - A numerical value + Numeric value + + Keyword Args + ------------ + name: :py:class:`str` + Value label + + value: :py:class:`int` + Numeric value Examples -------- - >>> nv = namedval.NamedValues('a', 'b', ('c', 0), d=1) - >>> nv - >>> {'c': 0, 'd': 1, 'a': 2, 'b': 3} - >>> nv[0] - 'c' - >>> nv['a'] - 2 + .. code-block:: pycon + + >>> nv = NamedValues('a', 'b', ('c', 0), d=1) + >>> nv + >>> {'c': 0, 'd': 1, 'a': 2, 'b': 3} + >>> nv[0] + 'c' + >>> nv['a'] + 2 """ def __init__(self, *args, **kwargs): self.__names = {} @@ -96,10 +104,12 @@ class NamedValues(object): number += 1 def __repr__(self): - return '%s(%r)' % (self.__class__.__name__, tuple(self.items())) + representation = ', '.join(['%s=%d' % x for x in self.items()]) - def __str__(self): - return str(self.items()) + if len(representation) > 64: + representation = representation[:32] + '...' + representation[-32:] + + return '<%s object 0x%x enums %s>' % (self.__class__.__name__, id(self), representation) def __eq__(self, other): return dict(self) == other diff --git a/src/pyasn1/type/opentype.py b/src/pyasn1/type/opentype.py new file mode 100644 index 00000000..d14ab340 --- /dev/null +++ b/src/pyasn1/type/opentype.py @@ -0,0 +1,75 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html +# + +__all__ = ['OpenType'] + + +class OpenType(object): + """Create ASN.1 type map indexed by a value + + The *DefinedBy* object models the ASN.1 *DEFINED BY* clause which maps + values to ASN.1 types in the context of the ASN.1 SEQUENCE/SET type. + + OpenType objects are duck-type a read-only Python :class:`dict` objects, + however the passed `typeMap` is stored by reference. + + Parameters + ---------- + name: :py:class:`str` + Field name + + typeMap: :py:class:`dict` + A map of value->ASN.1 type. It's stored by reference and can be + mutated later to register new mappings. + + Examples + -------- + .. code-block:: python + + openType = OpenType( + 'id', + {1: Integer(), + 2: OctetString()} + ) + Sequence( + componentType=NamedTypes( + NamedType('id', Integer()), + NamedType('blob', Any(), openType=openType) + ) + ) + """ + + def __init__(self, name, typeMap=None): + self.__name = name + if typeMap is None: + self.__typeMap = {} + else: + self.__typeMap = typeMap + + @property + def name(self): + return self.__name + + # Python dict protocol + + def values(self): + return self.__typeMap.values() + + def keys(self): + return self.__typeMap.keys() + + def items(self): + return self.__typeMap.items() + + def __contains__(self, key): + return key in self.__typeMap + + def __getitem__(self, key): + return self.__typeMap[key] + + def __iter__(self): + return iter(self.__typeMap) diff --git a/src/pyasn1/type/tag.py b/src/pyasn1/type/tag.py index 6c0e2c17..95c226f6 100644 --- a/src/pyasn1/type/tag.py +++ b/src/pyasn1/type/tag.py @@ -1,15 +1,15 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # from pyasn1 import error __all__ = ['tagClassUniversal', 'tagClassApplication', 'tagClassContext', 'tagClassPrivate', 'tagFormatSimple', 'tagFormatConstructed', - 'tagCategoryImplicit', 'tagCategoryExplicit', 'tagCategoryUntagged', - 'Tag', 'TagSet'] + 'tagCategoryImplicit', 'tagCategoryExplicit', + 'tagCategoryUntagged', 'Tag', 'TagSet'] #: Identifier for ASN.1 class UNIVERSAL tagClassUniversal = 0x00 @@ -63,13 +63,9 @@ class Tag(object): self.__tagClassId = tagClass, tagId self.__hash = hash(self.__tagClassId) - def __str__(self): - return '[%s:%s:%s]' % (self.__tagClass, self.__tagFormat, self.__tagId) - def __repr__(self): - return '%s(tagClass=%s, tagFormat=%s, tagId=%s)' % ( - (self.__class__.__name__, self.__tagClass, self.__tagFormat, self.__tagId) - ) + representation = '[%s:%s:%s]' % (self.__tagClass, self.__tagFormat, self.__tagId) + return '<%s object at 0x%x tag %s>' % (self.__class__.__name__, id(self), representation) def __eq__(self, other): return self.__tagClassId == other @@ -168,6 +164,23 @@ class TagSet(object): *superTags: :class:`~pyasn1.type.tag.Tag` Additional *Tag* objects taking part in subtyping. + + Examples + -------- + .. code-block:: python + + class OrderNumber(NumericString): + ''' + ASN.1 specification + + Order-number ::= + [APPLICATION 5] IMPLICIT NumericString + ''' + tagSet = NumericString.tagSet.tagImplicitly( + Tag(tagClassApplication, tagFormatSimple, 5) + ) + + orderNumber = OrderNumber('1234') """ def __init__(self, baseTag=(), *superTags): self.__baseTag = baseTag @@ -178,13 +191,15 @@ class TagSet(object): self.__lenOfSuperTags = len(superTags) self.__hash = hash(self.__superTagsClassId) - def __str__(self): - return self.__superTags and '+'.join([str(x) for x in self.__superTags]) or '[untagged]' - def __repr__(self): - return '%s(%s)' % ( - self.__class__.__name__, '(), ' + ', '.join([repr(x) for x in self.__superTags]) - ) + representation = '-'.join(['%s:%s:%s' % (x.tagClass, x.tagFormat, x.tagId) + for x in self.__superTags]) + if representation: + representation = 'tags ' + representation + else: + representation = 'untagged' + + return '<%s object at 0x%x %s>' % (self.__class__.__name__, id(self), representation) def __add__(self, superTag): return self.__class__(self.__baseTag, *self.__superTags + (superTag,)) diff --git a/src/pyasn1/type/tagmap.py b/src/pyasn1/type/tagmap.py index 32b74473..a9d237f2 100644 --- a/src/pyasn1/type/tagmap.py +++ b/src/pyasn1/type/tagmap.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # from pyasn1 import error @@ -56,24 +56,18 @@ class TagMap(object): return iter(self.__presentTypes) def __repr__(self): - s = self.__class__.__name__ + '(' - if self.__presentTypes: - s += 'presentTypes=%r, ' % (self.__presentTypes,) - if self.__skipTypes: - s += 'skipTypes=%r, ' % (self.__skipTypes,) - if self.__defaultType is not None: - s += 'defaultType=%r' % (self.__defaultType,) - return s + ')' + representation = '%s object at 0x%x' % (self.__class__.__name__, id(self)) - def __str__(self): - s = self.__class__.__name__ + ': ' if self.__presentTypes: - s += 'presentTypes: %s, ' % ', '.join([x.prettyPrintType() for x in self.__presentTypes.values()]) + representation += ' present %s' % repr(self.__presentTypes) + if self.__skipTypes: - s += 'skipTypes: %s, ' % ', '.join([x.prettyPrintType() for x in self.__skipTypes.values()]) + representation += ' skip %s' % repr(self.__skipTypes) + if self.__defaultType is not None: - s += 'defaultType: %s, ' % self.__defaultType.prettyPrintType() - return s + representation += ' default %s' % repr(self.__defaultType) + + return '<%s>' % representation @property def presentTypes(self): diff --git a/src/pyasn1/type/univ.py b/src/pyasn1/type/univ.py index a90c6483..a19f6baa 100644 --- a/src/pyasn1/type/univ.py +++ b/src/pyasn1/type/univ.py @@ -1,22 +1,31 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # -import sys import math -from pyasn1.type import base, tag, constraint, namedtype, namedval, tagmap -from pyasn1.codec.ber import eoo -from pyasn1.compat import octets, integer, binary +import sys + from pyasn1 import error +from pyasn1.codec.ber import eoo +from pyasn1.compat import binary +from pyasn1.compat import integer +from pyasn1.compat import octets +from pyasn1.type import base +from pyasn1.type import constraint +from pyasn1.type import namedtype +from pyasn1.type import namedval +from pyasn1.type import tag +from pyasn1.type import tagmap NoValue = base.NoValue noValue = NoValue() __all__ = ['Integer', 'Boolean', 'BitString', 'OctetString', 'Null', - 'ObjectIdentifier', 'Real', 'Enumerated', 'SequenceOfAndSetOfBase', 'SequenceOf', - 'SetOf', 'SequenceAndSetBase', 'Sequence', 'Set', 'Choice', 'Any', + 'ObjectIdentifier', 'Real', 'Enumerated', + 'SequenceOfAndSetOfBase', 'SequenceOf', 'SetOf', + 'SequenceAndSetBase', 'Sequence', 'Set', 'Choice', 'Any', 'NoValue', 'noValue'] # "Simple" ASN.1 types (yet incomplete) @@ -27,9 +36,9 @@ class Integer(base.AbstractSimpleAsn1Item): |ASN.1| objects are immutable and duck-type Python :class:`int` objects. - Parameters - ---------- - value : :class:`int`, :class:`str` or |ASN.1| object + Keyword Args + ------------ + value: :class:`int`, :class:`str` or |ASN.1| object Python integer or string literal or |ASN.1| class instance. tagSet: :py:class:`~pyasn1.type.tag.TagSet` @@ -43,8 +52,30 @@ class Integer(base.AbstractSimpleAsn1Item): Raises ------ - : :py:class:`pyasn1.error.PyAsn1Error` + :py:class:`~pyasn1.error.PyAsn1Error` On constraint violation or bad initializer. + + Examples + -------- + + .. code-block:: python + + class ErrorCode(Integer): + ''' + ASN.1 specification: + + ErrorCode ::= + INTEGER { disk-full(1), no-disk(-1), + disk-not-formatted(2) } + + error ErrorCode ::= disk-full + ''' + namedValues = NamedValues( + ('disk-full', 1), ('no-disk', -1), + ('disk-not-formatted', 2) + ) + + error = ErrorCode('disk-full') """ #: Set (on class, not on instance) or return a #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) @@ -71,12 +102,6 @@ class Integer(base.AbstractSimpleAsn1Item): base.AbstractSimpleAsn1Item.__init__(self, value, **kwargs) - def __repr__(self): - if self.namedValues is not self.__class__.namedValues: - return '%s, %r)' % (base.AbstractSimpleAsn1Item.__repr__(self)[:-1], self.namedValues) - else: - return base.AbstractSimpleAsn1Item.__repr__(self) - def __and__(self, value): return self.clone(self._value & value) @@ -239,77 +264,11 @@ class Integer(base.AbstractSimpleAsn1Item): def prettyOut(self, value): try: - return repr(self.namedValues[value]) + return str(self.namedValues[value]) except KeyError: return str(value) - def clone(self, value=noValue, **kwargs): - """Create a copy of a |ASN.1| type or object. - - Any parameters to the *clone()* method will replace corresponding - properties of the |ASN.1| object. - - Parameters - ---------- - value: :class:`int`, :class:`str` or |ASN.1| object - Initialization value to pass to new ASN.1 object instead of - inheriting one from the caller. - - tagSet: :py:class:`~pyasn1.type.tag.TagSet` - Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller - - subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller - - namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` - Object representing symbolic aliases for numbers to use instead of inheriting from caller - - Returns - ------- - : - new instance of |ASN.1| type/value - """ - return base.AbstractSimpleAsn1Item.clone(self, value, **kwargs) - - def subtype(self, value=noValue, **kwargs): - """Create a copy of a |ASN.1| type or object. - - Any parameters to the *subtype()* method will be added to the corresponding - properties of the |ASN.1| object. - - Parameters - ---------- - value: :class:`int`, :class:`str` or |ASN.1| object - Initialization value to pass to new ASN.1 object instead of - inheriting one from the caller. - - implicitTag: :py:class:`~pyasn1.type.tag.Tag` - Implicitly apply given ASN.1 tag object to caller's - :py:class:`~pyasn1.type.tag.TagSet`, then use the result as - new object's ASN.1 tag(s). - - explicitTag: :py:class:`~pyasn1.type.tag.Tag` - Explicitly apply given ASN.1 tag object to caller's - :py:class:`~pyasn1.type.tag.TagSet`, then use the result as - new object's ASN.1 tag(s). - - subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Add ASN.1 constraints object to one of the caller, then - use the result as new object's ASN.1 constraints. - - namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` - Add given object representing symbolic aliases for numbers - to one of the caller, then use the result as new object's - named numbers. - - Returns - ------- - : - new instance of |ASN.1| type/value - """ - return base.AbstractSimpleAsn1Item.subtype(self, value, **kwargs) - # backward compatibility def getNamedValues(self): @@ -317,8 +276,45 @@ class Integer(base.AbstractSimpleAsn1Item): class Boolean(Integer): - __doc__ = Integer.__doc__ + """Create |ASN.1| type or object. + |ASN.1| objects are immutable and duck-type Python :class:`int` objects. + + Keyword Args + ------------ + value: :class:`int`, :class:`str` or |ASN.1| object + Python integer or boolean or string literal or |ASN.1| class instance. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` + Object representing non-default symbolic aliases for numbers + + Raises + ------ + :py:class:`~pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + + Examples + -------- + .. code-block:: python + + class RoundResult(Boolean): + ''' + ASN.1 specification: + + RoundResult ::= BOOLEAN + + ok RoundResult ::= TRUE + ko RoundResult ::= FALSE + ''' + ok = RoundResult(True) + ko = RoundResult(False) + """ #: Set (on class, not on instance) or return a #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) #: associated with |ASN.1| type. @@ -338,16 +334,36 @@ class Boolean(Integer): # Optimization for faster codec lookup typeId = Integer.getTypeId() +if sys.version_info[0] < 3: + SizedIntegerBase = long +else: + SizedIntegerBase = int + + +class SizedInteger(SizedIntegerBase): + bitLength = leadingZeroBits = None + + def setBitLength(self, bitLength): + self.bitLength = bitLength + self.leadingZeroBits = max(bitLength - integer.bitLength(self), 0) + return self + + def __len__(self): + if self.bitLength is None: + self.setBitLength(integer.bitLength(self)) + + return self.bitLength + class BitString(base.AbstractSimpleAsn1Item): - """Create |ASN.1| type or object. + """Create |ASN.1| schema or value object. |ASN.1| objects are immutable and duck-type both Python :class:`tuple` (as a tuple of bits) and :class:`int` objects. - Parameters - ---------- - value : :class:`int`, :class:`str` or |ASN.1| object + Keyword Args + ------------ + value: :class:`int`, :class:`str` or |ASN.1| object Python integer or string literal representing binary or hexadecimal number or sequence of integer bits or |ASN.1| object. @@ -370,8 +386,34 @@ class BitString(base.AbstractSimpleAsn1Item): Raises ------ - : :py:class:`pyasn1.error.PyAsn1Error` + :py:class:`~pyasn1.error.PyAsn1Error` On constraint violation or bad initializer. + + Examples + -------- + .. code-block:: python + + class Rights(BitString): + ''' + ASN.1 specification: + + Rights ::= BIT STRING { user-read(0), user-write(1), + group-read(2), group-write(3), + other-read(4), other-write(5) } + + group1 Rights ::= { group-read, group-write } + group2 Rights ::= '0011'B + group3 Rights ::= '3'H + ''' + namedValues = NamedValues( + ('user-read', 0), ('user-write', 1), + ('group-read', 2), ('group-write', 3), + ('other-read', 4), ('other-write', 5) + ) + + group1 = Rights(('group-read', 'group-write')) + group2 = Rights('0011') + group3 = Rights(0x3) """ #: Set (on class, not on instance) or return a #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) @@ -394,134 +436,33 @@ class BitString(base.AbstractSimpleAsn1Item): defaultBinValue = defaultHexValue = noValue - if sys.version_info[0] < 3: - SizedIntegerBase = long - else: - SizedIntegerBase = int - - class SizedInteger(SizedIntegerBase): - bitLength = leadingZeroBits = None - - def setBitLength(self, bitLength): - self.bitLength = bitLength - self.leadingZeroBits = max(bitLength - integer.bitLength(self), 0) - return self - - def __len__(self): - if self.bitLength is None: - self.setBitLength(integer.bitLength(self)) - - return self.bitLength - def __init__(self, value=noValue, **kwargs): - if value is noValue or value is None: + if value is noValue: if kwargs: try: - value = self.fromBinaryString(kwargs.pop('binValue')) + value = self.fromBinaryString(kwargs.pop('binValue'), internalFormat=True) except KeyError: pass try: - value = self.fromHexString(kwargs.pop('hexValue')) + value = self.fromHexString(kwargs.pop('hexValue'), internalFormat=True) except KeyError: pass - if value is noValue or value is None: + if value is noValue: if self.defaultBinValue is not noValue: - value = self.fromBinaryString(self.defaultBinValue) + value = self.fromBinaryString(self.defaultBinValue, internalFormat=True) elif self.defaultHexValue is not noValue: - value = self.fromHexString(self.defaultHexValue) + value = self.fromHexString(self.defaultHexValue, internalFormat=True) if 'namedValues' not in kwargs: kwargs['namedValues'] = self.namedValues base.AbstractSimpleAsn1Item.__init__(self, value, **kwargs) - def clone(self, value=noValue, **kwargs): - """Create a copy of a |ASN.1| type or object. - - Any parameters to the *clone()* method will replace corresponding - properties of the |ASN.1| object. - - Parameters - ---------- - value : :class:`int`, :class:`str` or |ASN.1| object - Initialization value to pass to new ASN.1 object instead of - inheriting one from the caller. - - tagSet: :py:class:`~pyasn1.type.tag.TagSet` - Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller - - subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller - - namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` - Class instance representing BitString type enumerations - - binValue: :py:class:`str` - Binary string initializer to use instead of the *value*. - Example: '10110011'. - - hexValue: :py:class:`str` - Hexadecimal string initializer to use instead of the *value*. - Example: 'DEADBEEF'. - - Returns - ------- - : - new instance of |ASN.1| type/value - """ - return base.AbstractSimpleAsn1Item.clone(self, value, **kwargs) - - def subtype(self, value=noValue, **kwargs): - """Create a copy of a |ASN.1| type or object. - - Any parameters to the *subtype()* method will be added to the corresponding - properties of the |ASN.1| object. - - Parameters - ---------- - value: :class:`int`, :class:`str` or |ASN.1| object - Initialization value to pass to new ASN.1 object instead of - inheriting one from the caller. - - implicitTag: :py:class:`~pyasn1.type.tag.Tag` - Implicitly apply given ASN.1 tag object to caller's - :py:class:`~pyasn1.type.tag.TagSet`, then use the result as - new object's ASN.1 tag(s). - - explicitTag: :py:class:`~pyasn1.type.tag.Tag` - Explicitly apply given ASN.1 tag object to caller's - :py:class:`~pyasn1.type.tag.TagSet`, then use the result as - new object's ASN.1 tag(s). - - subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Add ASN.1 constraints object to one of the caller, then - use the result as new object's ASN.1 constraints. - - namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` - Add given object representing symbolic aliases for numbers - to one of the caller, then use the result as new object's - named numbers. - - binValue: :py:class:`str` - Binary string initializer to use instead of the *value*. - Example: '10110011'. - - hexValue: :py:class:`str` - Hexadecimal string initializer to use instead of the *value*. - Example: 'DEADBEEF'. - - Returns - ------- - : - new instance of |ASN.1| type/value - """ - return base.AbstractSimpleAsn1Item.subtype(self, value, **kwargs) - def __str__(self): return self.asBinary() @@ -576,11 +517,11 @@ class BitString(base.AbstractSimpleAsn1Item): def __add__(self, value): value = self.prettyIn(value) - return self.clone(self.SizedInteger(self._value << len(value) | value).setBitLength(len(self._value) + len(value))) + return self.clone(SizedInteger(self._value << len(value) | value).setBitLength(len(self._value) + len(value))) def __radd__(self, value): value = self.prettyIn(value) - return self.clone(self.SizedInteger(value << len(self._value) | self._value).setBitLength(len(self._value) + len(value))) + return self.clone(SizedInteger(value << len(self._value) | self._value).setBitLength(len(self._value) + len(value))) def __mul__(self, value): bitString = self._value @@ -594,10 +535,10 @@ class BitString(base.AbstractSimpleAsn1Item): return self * value def __lshift__(self, count): - return self.clone(self.SizedInteger(self._value << count).setBitLength(len(self._value) + count)) + return self.clone(SizedInteger(self._value << count).setBitLength(len(self._value) + count)) def __rshift__(self, count): - return self.clone(self.SizedInteger(self._value >> count).setBitLength(max(0, len(self._value) - count))) + return self.clone(SizedInteger(self._value >> count).setBitLength(max(0, len(self._value) - count))) def __int__(self): return self._value @@ -637,22 +578,32 @@ class BitString(base.AbstractSimpleAsn1Item): return '0' * (len(self._value) - len(binString)) + binString @classmethod - def fromHexString(cls, value): + def fromHexString(cls, value, internalFormat=False, prepend=None): """Create a |ASN.1| object initialized from the hex string. - + Parameters ---------- value: :class:`str` Text string like 'DEADBEEF' """ try: - return cls.SizedInteger(value, 16).setBitLength(len(value) * 4) + value = SizedInteger(value, 16).setBitLength(len(value) * 4) except ValueError: raise error.PyAsn1Error('%s.fromHexString() error: %s' % (cls.__name__, sys.exc_info()[1])) + if prepend is not None: + value = SizedInteger( + (SizedInteger(prepend) << len(value)) | value + ).setBitLength(len(prepend) + len(value)) + + if not internalFormat: + value = cls(value) + + return value + @classmethod - def fromBinaryString(cls, value): + def fromBinaryString(cls, value, internalFormat=False, prepend=None): """Create a |ASN.1| object initialized from a string of '0' and '1'. Parameters @@ -661,13 +612,23 @@ class BitString(base.AbstractSimpleAsn1Item): Text string like '1010111' """ try: - return cls.SizedInteger(value or '0', 2).setBitLength(len(value)) + value = SizedInteger(value or '0', 2).setBitLength(len(value)) except ValueError: raise error.PyAsn1Error('%s.fromBinaryString() error: %s' % (cls.__name__, sys.exc_info()[1])) + if prepend is not None: + value = SizedInteger( + (SizedInteger(prepend) << len(value)) | value + ).setBitLength(len(prepend) + len(value)) + + if not internalFormat: + value = cls(value) + + return value + @classmethod - def fromOctetString(cls, value, padding=0): + def fromOctetString(cls, value, internalFormat=False, prepend=None, padding=0): """Create a |ASN.1| object initialized from a string. Parameters @@ -675,18 +636,30 @@ class BitString(base.AbstractSimpleAsn1Item): value: :class:`str` (Py2) or :class:`bytes` (Py3) Text string like '\\\\x01\\\\xff' (Py2) or b'\\\\x01\\\\xff' (Py3) """ - return cls(cls.SizedInteger(integer.from_bytes(value) >> padding).setBitLength(len(value) * 8 - padding)) + value = SizedInteger(integer.from_bytes(value) >> padding).setBitLength(len(value) * 8 - padding) + + if prepend is not None: + value = SizedInteger( + (SizedInteger(prepend) << len(value)) | value + ).setBitLength(len(prepend) + len(value)) + + if not internalFormat: + value = cls(value) + + return value def prettyIn(self, value): - if octets.isStringType(value): + if isinstance(value, SizedInteger): + return value + elif octets.isStringType(value): if not value: - return self.SizedInteger(0).setBitLength(0) + return SizedInteger(0).setBitLength(0) elif value[0] == '\'': # "'1011'B" -- ASN.1 schema representation (deprecated) if value[-2:] == '\'B': - return self.fromBinaryString(value[1:-2]) + return self.fromBinaryString(value[1:-2], internalFormat=True) elif value[-2:] == '\'H': - return self.fromHexString(value[1:-2]) + return self.fromHexString(value[1:-2], internalFormat=True) else: raise error.PyAsn1Error( 'Bad BIT STRING value notation %s' % (value,) @@ -708,34 +681,31 @@ class BitString(base.AbstractSimpleAsn1Item): for bitPosition in bitPositions: number |= 1 << (rightmostPosition - bitPosition) - return self.SizedInteger(number).setBitLength(rightmostPosition + 1) + return SizedInteger(number).setBitLength(rightmostPosition + 1) elif value.startswith('0x'): - return self.fromHexString(value[2:]) + return self.fromHexString(value[2:], internalFormat=True) elif value.startswith('0b'): - return self.fromBinaryString(value[2:]) + return self.fromBinaryString(value[2:], internalFormat=True) else: # assume plain binary string like '1011' - return self.fromBinaryString(value) + return self.fromBinaryString(value, internalFormat=True) elif isinstance(value, (tuple, list)): - return self.fromBinaryString(''.join([b and '1' or '0' for b in value])) + return self.fromBinaryString(''.join([b and '1' or '0' for b in value]), internalFormat=True) - elif isinstance(value, (self.SizedInteger, BitString)): - return self.SizedInteger(value).setBitLength(len(value)) + elif isinstance(value, BitString): + return SizedInteger(value).setBitLength(len(value)) elif isinstance(value, intTypes): - return self.SizedInteger(value) + return SizedInteger(value) else: raise error.PyAsn1Error( 'Bad BitString initializer type \'%s\'' % (value,) ) - def prettyOut(self, value): - return '\'%s\'' % str(self) - try: # noinspection PyStatementEffect @@ -751,17 +721,17 @@ except NameError: # Python 2.4 class OctetString(base.AbstractSimpleAsn1Item): - """Create |ASN.1| type or object. + """Create |ASN.1| schema or value object. |ASN.1| objects are immutable and duck-type Python 2 :class:`str` or Python 3 :class:`bytes`. - When used in Unicode context, |ASN.1| type assumes "|encoding|" serialization. + When used in Unicode context, |ASN.1| type assumes "|encoding|" serialisation. - Parameters - ---------- - value : :class:`str`, :class:`bytes` or |ASN.1| object + Keyword Args + ------------ + value: :class:`str`, :class:`bytes` or |ASN.1| object string (Python 2) or bytes (Python 3), alternatively unicode object (Python 2) or string (Python 3) representing character string to be - serialized into octets (note `encoding` parameter) or |ASN.1| object. + serialised into octets (note `encoding` parameter) or |ASN.1| object. tagSet: :py:class:`~pyasn1.type.tag.TagSet` Object representing non-default ASN.1 tag(s) @@ -777,15 +747,31 @@ class OctetString(base.AbstractSimpleAsn1Item): binValue: :py:class:`str` Binary string initializer to use instead of the *value*. Example: '10110011'. - + hexValue: :py:class:`str` Hexadecimal string initializer to use instead of the *value*. Example: 'DEADBEEF'. Raises ------ - : :py:class:`pyasn1.error.PyAsn1Error` + :py:class:`~pyasn1.error.PyAsn1Error` On constraint violation or bad initializer. + + Examples + -------- + .. code-block:: python + + class Icon(OctetString): + ''' + ASN.1 specification: + + Icon ::= OCTET STRING + + icon1 Icon ::= '001100010011001000110011'B + icon2 Icon ::= '313233'H + ''' + icon1 = Icon.fromBinaryString('001100010011001000110011') + icon2 = Icon.fromHexString('313233') """ #: Set (on class, not on instance) or return a #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) @@ -807,7 +793,7 @@ class OctetString(base.AbstractSimpleAsn1Item): def __init__(self, value=noValue, **kwargs): if kwargs: - if value is noValue or value is None: + if value is noValue: try: value = self.fromBinaryString(kwargs.pop('binValue')) @@ -820,7 +806,7 @@ class OctetString(base.AbstractSimpleAsn1Item): except KeyError: pass - if value is noValue or value is None: + if value is noValue: if self.defaultBinValue is not noValue: value = self.fromBinaryString(self.defaultBinValue) @@ -832,86 +818,6 @@ class OctetString(base.AbstractSimpleAsn1Item): base.AbstractSimpleAsn1Item.__init__(self, value, **kwargs) - def clone(self, value=noValue, **kwargs): - """Create a copy of a |ASN.1| type or object. - - Any parameters to the *clone()* method will replace corresponding - properties of the |ASN.1| object. - - Parameters - ---------- - value : :class:`str`, :class:`bytes` or |ASN.1| object - Initialization value to pass to new ASN.1 object instead of - inheriting one from the caller. - - tagSet: :py:class:`~pyasn1.type.tag.TagSet` - Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller - - subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller - - encoding: :py:class:`str` - Unicode codec ID to encode/decode :class:`unicode` (Python 2) - or :class:`str` (Python 3) the payload when |ASN.1| - object is used in string context. - - binValue: :py:class:`str` - Binary string initializer. Example: '10110011'. - - hexValue: :py:class:`str` - Hexadecimal string initializer. Example: 'DEADBEEF'. - - Returns - ------- - : - new instance of |ASN.1| type/value - """ - return base.AbstractSimpleAsn1Item.clone(self, value, **kwargs) - - def subtype(self, value=noValue, **kwargs): - """Create a copy of a |ASN.1| type or object. - - Any parameters to the *subtype()* method will be added to the corresponding - properties of the |ASN.1| object. - - Parameters - ---------- - value : :class:`str`, :class:`bytes` or |ASN.1| object - Initialization value to pass to new ASN.1 object instead of - inheriting one from the caller. - - implicitTag: :py:class:`~pyasn1.type.tag.Tag` - Implicitly apply given ASN.1 tag object to |ASN.1| object tag set - :py:class:`~pyasn1.type.tag.TagSet`, then use the result as - new object's ASN.1 tag(s). - - explicitTag: :py:class:`~pyasn1.type.tag.Tag` - Explicitly apply given ASN.1 tag object to |ASN.1| object tag set - :py:class:`~pyasn1.type.tag.TagSet`, then use the result as - new object's ASN.1 tag(s). - - subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Add ASN.1 constraints object to one of the caller, then - use the result as new object's ASN.1 constraints. - - encoding: :py:class:`str` - Unicode codec ID to encode/decode :class:`unicode` (Python 2) - or :class:`str` (Python 3) the payload when *OctetString* - object is used in string context. - - binValue: :py:class:`str` - Binary string initializer. Example: '10110011'. - - hexValue: :py:class:`str` - Hexadecimal string initializer. Example: 'DEADBEEF'. - - Returns - ------- - : - new instance of |ASN.1| type/value - """ - return base.AbstractSimpleAsn1Item.subtype(self, value, **kwargs) - if sys.version_info[0] <= 2: def prettyIn(self, value): if isinstance(value, str): @@ -928,7 +834,7 @@ class OctetString(base.AbstractSimpleAsn1Item): return ''.join([chr(x) for x in value]) except ValueError: raise error.PyAsn1Error( - 'Bad %s initializer \'%s\'' % (self.__class__.__name__, value) + "Bad %s initializer '%s'" % (self.__class__.__name__, value) ) else: return str(value) @@ -960,7 +866,7 @@ class OctetString(base.AbstractSimpleAsn1Item): return value.encode(self.encoding) except UnicodeEncodeError: raise error.PyAsn1Error( - 'Can\'t encode string \'%s\' with \'%s\' codec' % (value, self.encoding) + "Can't encode string '%s' with '%s' codec" % (value, self.encoding) ) elif isinstance(value, OctetString): # a shortcut, bytes() would work the same way return value.asOctets() @@ -977,7 +883,7 @@ class OctetString(base.AbstractSimpleAsn1Item): except UnicodeDecodeError: raise error.PyAsn1Error( - 'Can\'t decode string \'%s\' with \'%s\' codec at \'%s\'' % (self._value, self.encoding, self.__class__.__name__) + "Can't decode string '%s' with '%s' codec at '%s'" % (self._value, self.encoding, self.__class__.__name__) ) def __bytes__(self): @@ -989,22 +895,43 @@ class OctetString(base.AbstractSimpleAsn1Item): def asNumbers(self): return tuple(self._value) + # + # Normally, `.prettyPrint()` is called from `__str__()`. Historically, + # OctetString.prettyPrint() used to return hexified payload + # representation in cases when non-printable content is present. At the + # same time `str()` used to produce either octet-stream (Py2) or + # text (Py3) representations. + # + # Therefore `OctetString.__str__()` -> `.prettyPrint()` call chain is + # reversed to preserve the original behaviour. + # + # Eventually we should deprecate `.prettyPrint()` / `.prettyOut()` harness + # and end up with just `__str__()` producing hexified representation while + # both text and octet-stream representation should only be requested via + # the `.asOctets()` method. + # + # Note: ASN.1 OCTET STRING is never mean to contain text! + # + def prettyOut(self, value): - if sys.version_info[0] <= 2: - numbers = tuple((ord(x) for x in value)) - else: - numbers = tuple(value) + return value + + def prettyPrint(self, scope=0): + # first see if subclass has its own .prettyOut() + value = self.prettyOut(self._value) + + if value is not self._value: + return value + + numbers = self.asNumbers() + for x in numbers: + # hexify if needed if x < 32 or x > 126: return '0x' + ''.join(('%.2x' % x for x in numbers)) else: - try: - return value.decode(self.encoding) - - except UnicodeDecodeError: - raise error.PyAsn1Error( - "Can't decode string '%s' with '%s' codec at '%s'" % (value, self.encoding, self.__class__.__name__) - ) + # this prevents infinite recursion + return OctetString.__str__(self) @staticmethod def fromBinaryString(value): @@ -1059,26 +986,6 @@ class OctetString(base.AbstractSimpleAsn1Item): return octets.ints2octs(r) - def __repr__(self): - r = [] - doHex = False - if self._value is not self.defaultValue: - for x in self.asNumbers(): - if x < 32 or x > 126: - doHex = True - break - if not doHex: - r.append('%r' % (self._value,)) - if self.tagSet is not self.__class__.tagSet: - r.append('tagSet=%r' % (self.tagSet,)) - if self.subtypeSpec is not self.__class__.subtypeSpec: - r.append('subtypeSpec=%r' % (self.subtypeSpec,)) - if self.encoding is not self.__class__.encoding: - r.append('encoding=%r' % (self.encoding,)) - if doHex: - r.append('hexValue=%r' % ''.join(['%.2x' % x for x in self.asNumbers()])) - return '%s(%s)' % (self.__class__.__name__, ', '.join(r)) - # Immutable sequence object protocol def __len__(self): @@ -1119,24 +1026,35 @@ class OctetString(base.AbstractSimpleAsn1Item): class Null(OctetString): - """Create |ASN.1| type or object. + """Create |ASN.1| schema or value object. |ASN.1| objects are immutable and duck-type Python :class:`str` objects (always empty). - Parameters - ---------- - value : :class:`str` or :py:class:`~pyasn1.type.univ.Null` object - Python empty string literal or *Null* class instance. + Keyword Args + ------------ + value: :class:`str` or :py:class:`~pyasn1.type.univ.Null` object + Python empty string literal or any object that evaluates to `False` tagSet: :py:class:`~pyasn1.type.tag.TagSet` Object representing non-default ASN.1 tag(s) Raises ------ - : :py:class:`pyasn1.error.PyAsn1Error` + :py:class:`~pyasn1.error.PyAsn1Error` On constraint violation or bad initializer. + + Examples + -------- + .. code-block:: python + + class Ack(Null): + ''' + ASN.1 specification: + + Ack ::= NULL + ''' + ack = Ack('') """ - defaultValue = ''.encode() # This is tightly constrained #: Set (on class, not on instance) or return a #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) @@ -1149,57 +1067,11 @@ class Null(OctetString): # Optimization for faster codec lookup typeId = OctetString.getTypeId() - def clone(self, value=noValue, **kwargs): - """Create a copy of a |ASN.1| type or object. - - Any parameters to the *clone()* method will replace corresponding - properties of the |ASN.1| object. - - Parameters - ---------- - value: :class:`str` or |ASN.1| object - Initialization value to pass to new ASN.1 object instead of - inheriting one from the caller. - - tagSet: :py:class:`~pyasn1.type.tag.TagSet` - Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller - - Returns - ------- - : :py:class:`~pyasn1.type.univ.Null` - new instance of NULL type/value - """ - return OctetString.clone(self, value, **kwargs) - - def subtype(self, value=noValue, **kwargs): - """Create a copy of a |ASN.1| type or object. - - Any parameters to the *subtype()* method will be added to the corresponding - properties of the |ASN.1| object. - - Parameters - ---------- - value: :class:`int`, :class:`str` or |ASN.1| object - Initialization value to pass to new ASN.1 object instead of - inheriting one from the caller. - - implicitTag: :py:class:`~pyasn1.type.tag.Tag` - Implicitly apply given ASN.1 tag object to caller's - :py:class:`~pyasn1.type.tag.TagSet`, then use the result as - new object's ASN.1 tag(s). - - explicitTag: :py:class:`~pyasn1.type.tag.Tag` - Explicitly apply given ASN.1 tag object to caller's - :py:class:`~pyasn1.type.tag.TagSet`, then use the result as - new object's ASN.1 tag(s). - - Returns - ------- - : :py:class:`~pyasn1.type.univ.Null` - new instance of NULL type/value - """ - return OctetString.subtype(self, value, **kwargs) + def prettyIn(self, value): + if value: + return value + return octets.str2octs('') if sys.version_info[0] <= 2: intTypes = (int, long) @@ -1210,12 +1082,12 @@ numericTypes = intTypes + (float,) class ObjectIdentifier(base.AbstractSimpleAsn1Item): - """Create |ASN.1| type or object. + """Create |ASN.1| schema or value object. |ASN.1| objects are immutable and duck-type Python :class:`tuple` objects (tuple of non-negative integers). - Parameters - ---------- + Keyword Args + ------------ value: :class:`tuple`, :class:`str` or |ASN.1| object Python sequence of :class:`int` or string literal or |ASN.1| object. @@ -1227,8 +1099,24 @@ class ObjectIdentifier(base.AbstractSimpleAsn1Item): Raises ------ - : :py:class:`pyasn1.error.PyAsn1Error` + :py:class:`~pyasn1.error.PyAsn1Error` On constraint violation or bad initializer. + + Examples + -------- + .. code-block:: python + + class ID(ObjectIdentifier): + ''' + ASN.1 specification: + + ID ::= OBJECT IDENTIFIER + + id-edims ID ::= { joint-iso-itu-t mhs-motif(6) edims(7) } + id-bp ID ::= { id-edims 11 } + ''' + id_edims = ID('2.6.7') + id_bp = id_edims + (11,) """ #: Set (on class, not on instance) or return a #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) @@ -1271,12 +1159,6 @@ class ObjectIdentifier(base.AbstractSimpleAsn1Item): def __contains__(self, value): return value in self._value - def __str__(self): - return self.prettyPrint() - - def __repr__(self): - return '%s(%r)' % (self.__class__.__name__, self.prettyPrint()) - def index(self, suboid): return self._value.index(suboid) @@ -1333,14 +1215,14 @@ class ObjectIdentifier(base.AbstractSimpleAsn1Item): class Real(base.AbstractSimpleAsn1Item): - """Create |ASN.1| type or object. + """Create |ASN.1| schema or value object. |ASN.1| objects are immutable and duck-type Python :class:`float` objects. Additionally, |ASN.1| objects behave like a :class:`tuple` in which case its elements are mantissa, base and exponent. - Parameters - ---------- + Keyword Args + ------------ value: :class:`tuple`, :class:`float` or |ASN.1| object Python sequence of :class:`int` (representing mantissa, base and exponent) or float instance or *Real* class instance. @@ -1353,16 +1235,31 @@ class Real(base.AbstractSimpleAsn1Item): Raises ------ - : :py:class:`pyasn1.error.PyAsn1Error` + :py:class:`~pyasn1.error.PyAsn1Error` On constraint violation or bad initializer. + Examples + -------- + .. code-block:: python + + class Pi(Real): + ''' + ASN.1 specification: + + Pi ::= REAL + + pi Pi ::= { mantissa 314159, base 10, exponent -5 } + + ''' + pi = Pi((314159, 10, -5)) """ binEncBase = None # binEncBase = 16 is recommended for large numbers try: _plusInf = float('inf') _minusInf = float('-inf') - _inf = (_plusInf, _minusInf) + _inf = _plusInf, _minusInf + except ValueError: # Infinity support is platform and Python dependent _plusInf = _minusInf = None @@ -1383,63 +1280,6 @@ class Real(base.AbstractSimpleAsn1Item): # Optimization for faster codec lookup typeId = base.AbstractSimpleAsn1Item.getTypeId() - def clone(self, value=noValue, **kwargs): - """Create a copy of a |ASN.1| type or object. - - Any parameters to the *clone()* method will replace corresponding - properties of the |ASN.1| object. - - Parameters - ---------- - value: :class:`tuple`, :class:`float` or |ASN.1| object - Initialization value to pass to new ASN.1 object instead of - inheriting one from the caller. - - tagSet: :py:class:`~pyasn1.type.tag.TagSet` - Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller - - subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller - - Returns - ------- - : - new instance of |ASN.1| type/value - """ - return base.AbstractSimpleAsn1Item.clone(self, value, **kwargs) - - def subtype(self, value=noValue, **kwargs): - """Create a copy of a |ASN.1| type or object. - - Any parameters to the *subtype()* method will be added to the corresponding - properties of the |ASN.1| object. - - Parameters - ---------- - value: :class:`tuple`, :class:`float` or |ASN.1| object - Initialization value to pass to new ASN.1 object instead of - inheriting one from the caller. - - implicitTag: :py:class:`~pyasn1.type.tag.Tag` - Implicitly apply given ASN.1 tag object to caller's - :py:class:`~pyasn1.type.tag.TagSet`, then use the result as - new object's ASN.1 tag(s). - - explicitTag: :py:class:`~pyasn1.type.tag.Tag` - Explicitly apply given ASN.1 tag object to caller's - :py:class:`~pyasn1.type.tag.TagSet`, then use the result as - new object's ASN.1 tag(s). - - subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller - - Returns - ------- - : - new instance of |ASN.1| type/value - """ - return base.AbstractSimpleAsn1Item.subtype(self, value, **kwargs) - @staticmethod def __normalizeBase10(value): m, b, e = value @@ -1450,12 +1290,12 @@ class Real(base.AbstractSimpleAsn1Item): def prettyIn(self, value): if isinstance(value, tuple) and len(value) == 3: - if not isinstance(value[0], numericTypes) or \ - not isinstance(value[1], intTypes) or \ - not isinstance(value[2], intTypes): + if (not isinstance(value[0], numericTypes) or + not isinstance(value[1], intTypes) or + not isinstance(value[2], intTypes)): raise error.PyAsn1Error('Lame Real value syntax: %s' % (value,)) - if isinstance(value[0], float) and \ - self._inf and value[0] in self._inf: + if (isinstance(value[0], float) and + self._inf and value[0] in self._inf): return value[0] if value[1] not in (2, 10): raise error.PyAsn1Error( @@ -1488,21 +1328,12 @@ class Real(base.AbstractSimpleAsn1Item): 'Bad real value syntax: %s' % (value,) ) - def prettyOut(self, value): - if value in self._inf: - return '\'%s\'' % value - else: - return str(value) - def prettyPrint(self, scope=0): - if self.isInf: - return self.prettyOut(self._value) - else: - try: - return str(float(self)) + try: + return self.prettyOut(float(self)) - except OverflowError: - return '' + except OverflowError: + return '' @property def isPlusInf(self): @@ -1533,9 +1364,6 @@ class Real(base.AbstractSimpleAsn1Item): def isInf(self): return self._value in self._inf - def __str__(self): - return str(float(self)) - def __add__(self, value): return self.clone(float(self) + value) @@ -1672,8 +1500,50 @@ class Real(base.AbstractSimpleAsn1Item): class Enumerated(Integer): - __doc__ = Integer.__doc__ + """Create |ASN.1| type or object. + |ASN.1| objects are immutable and duck-type Python :class:`int` objects. + + Keyword Args + ------------ + value: :class:`int`, :class:`str` or |ASN.1| object + Python integer or string literal or |ASN.1| class instance. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` + Object representing non-default symbolic aliases for numbers + + Raises + ------ + :py:class:`~pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + + Examples + -------- + + .. code-block:: python + + class RadioButton(Enumerated): + ''' + ASN.1 specification: + + RadioButton ::= ENUMERATED { button1(0), button2(1), + button3(2) } + + selected-by-default RadioButton ::= button1 + ''' + namedValues = NamedValues( + ('button1', 0), ('button2', 1), + ('button3', 2) + ) + + selected_by_default = RadioButton('button1') + """ #: Set (on class, not on instance) or return a #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) #: associated with |ASN.1| type. @@ -1701,8 +1571,8 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): |ASN.1| objects are mutable and duck-type Python :class:`list` objects. - Parameters - ---------- + Keyword Args + ------------ componentType : :py:class:`~pyasn1.type.base.PyAsn1Item` derivative A pyasn1 object representing ASN.1 type allowed within |ASN.1| type @@ -1714,8 +1584,23 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): sizeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` Object representing collection size constraint - """ + Examples + -------- + + .. code-block:: python + + class LotteryDraw(SequenceOf): # SetOf is similar + ''' + ASN.1 specification: + + LotteryDraw ::= SEQUENCE OF INTEGER + ''' + componentType = Integer() + + lotteryDraw = LotteryDraw() + lotteryDraw.extend([123, 456, 789]) + """ def __init__(self, *args, **kwargs): # support positional params for backward compatibility if args: @@ -1784,7 +1669,7 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): else: myClone.setComponentByPosition(idx, componentValue.clone()) - def getComponentByPosition(self, idx): + def getComponentByPosition(self, idx, default=noValue, instantiate=True): """Return |ASN.1| type component value by position. Equivalent to Python sequence subscription operation (e.g. `[]`). @@ -1797,16 +1682,74 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): case a new component type gets instantiated and appended to the |ASN.1| sequence. + Keyword Args + ------------ + default: :class:`object` + If set and requested component is a schema object, return the `default` + object instead of the requested component. + + instantiate: :class:`bool` + If `True` (default), inner component will be automatically instantiated. + If 'False' either existing component or the `noValue` object will be + returned. + Returns ------- : :py:class:`~pyasn1.type.base.PyAsn1Item` - a pyasn1 object + Instantiate |ASN.1| component type or return existing component value + + Examples + -------- + + .. code-block:: python + + # can also be SetOf + class MySequenceOf(SequenceOf): + componentType = OctetString() + + s = MySequenceOf() + + # returns component #0 with `.isValue` property False + s.getComponentByPosition(0) + + # returns None + s.getComponentByPosition(0, default=None) + + s.clear() + + # returns noValue + s.getComponentByPosition(0, instantiate=False) + + # sets component #0 to OctetString() ASN.1 schema + # object and returns it + s.getComponentByPosition(0, instantiate=True) + + # sets component #0 to ASN.1 value object + s.setComponentByPosition(0, 'ABCD') + + # returns OctetString('ABCD') value object + s.getComponentByPosition(0, instantiate=False) + + s.clear() + + # returns noValue + s.getComponentByPosition(0, instantiate=False) """ try: - return self._componentValues[idx] + componentValue = self._componentValues[idx] + except IndexError: + if not instantiate: + return default + self.setComponentByPosition(idx) - return self._componentValues[idx] + + componentValue = self._componentValues[idx] + + if default is noValue or componentValue.isValue: + return componentValue + else: + return default def setComponentByPosition(self, idx, value=noValue, verifyConstraints=True, @@ -1825,6 +1768,8 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): type gets instantiated (if *componentType* is set, or given ASN.1 object is taken otherwise) and appended to the |ASN.1| sequence. + Keyword Args + ------------ value: :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative A Python value to initialize |ASN.1| component with (if *componentType* is set) or ASN.1 value object to assign to |ASN.1| component. @@ -1847,9 +1792,6 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): IndexError: When idx > len(self) """ - if value is None: # backward compatibility - value = noValue - componentType = self.componentType try: @@ -1923,32 +1865,33 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): @property def isValue(self): - """Indicate if |ASN.1| object represents ASN.1 type or ASN.1 value. + """Indicate that |ASN.1| object represents ASN.1 value. - In other words, if *isValue* is `True`, then the ASN.1 object is - initialized. + If *isValue* is `False` then this object represents just ASN.1 schema. - For the purpose of this check, empty |ASN.1| object is considered - as initialized. + If *isValue* is `True` then, in addition to its ASN.1 schema features, + this object can also be used like a Python built-in object (e.g. `int`, + `str`, `dict` etc.). Returns ------- : :class:`bool` - :class:`True` if object represents ASN.1 value and type, - :class:`False` if object represents just ASN.1 type. + :class:`False` if object represents just ASN.1 schema. + :class:`True` if object represents ASN.1 schema and can be used as a normal value. Note ---- - There is an important distinction between PyASN1 type and value objects. - The PyASN1 type objects can only participate in ASN.1 type - operations (subtyping, comparison etc) and serve as a - blueprint for serialization codecs to resolve ambiguous types. + There is an important distinction between PyASN1 schema and value objects. + The PyASN1 schema objects can only participate in ASN.1 schema-related + operations (e.g. defining or testing the structure of the data). Most + obvious uses of ASN.1 schema is to guide serialisation codecs whilst + encoding/decoding serialised ASN.1 contents. - The PyASN1 value objects can additionally participate in most - of built-in Python operations. + The PyASN1 value objects can **additionally** participate in many operations + involving regular Python objects (e.g. arithmetic, comprehension etc). """ for componentValue in self._componentValues: - if not componentValue.isValue: + if componentValue is noValue or not componentValue.isValue: return False return True @@ -2013,8 +1956,8 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): |ASN.1| objects are mutable and duck-type Python :class:`dict` objects. - Parameters - ---------- + Keyword Args + ------------ componentType: :py:class:`~pyasn1.type.namedtype.NamedType` Object holding named ASN.1 types allowed within this collection @@ -2026,6 +1969,31 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): sizeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` Object representing collection size constraint + + Examples + -------- + + .. code-block:: python + + class Description(Sequence): # Set is similar + ''' + ASN.1 specification: + + Description ::= SEQUENCE { + surname IA5String, + first-name IA5String OPTIONAL, + age INTEGER DEFAULT 40 + } + ''' + componentType = NamedTypes( + NamedType('surname', IA5String()), + OptionalNamedType('first-name', IA5String()), + DefaultedNamedType('age', Integer(40)) + ) + + descr = Description() + descr['surname'] = 'Smith' + descr['first-name'] = 'John' """ #: Default :py:class:`~pyasn1.type.namedtype.NamedTypes` #: object representing named ASN.1 types allowed within |ASN.1| type @@ -2157,16 +2125,27 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): else: myClone.setComponentByPosition(idx, componentValue.clone()) - def getComponentByName(self, name): + def getComponentByName(self, name, default=noValue, instantiate=True): """Returns |ASN.1| type component by name. Equivalent to Python :class:`dict` subscription operation (e.g. `[]`). Parameters ---------- - name : :class:`str` + name: :class:`str` |ASN.1| type component name + Keyword Args + ------------ + default: :class:`object` + If set and requested component is a schema object, return the `default` + object instead of the requested component. + + instantiate: :class:`bool` + If `True` (default), inner component will be automatically instantiated. + If 'False' either existing component or the `noValue` object will be + returned. + Returns ------- : :py:class:`~pyasn1.type.base.PyAsn1Item` @@ -2181,7 +2160,7 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): except KeyError: raise error.PyAsn1Error('Name %s not found' % (name,)) - return self.getComponentByPosition(idx) + return self.getComponentByPosition(idx, default=default, instantiate=instantiate) def setComponentByName(self, name, value=noValue, verifyConstraints=True, @@ -2196,7 +2175,9 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): name: :class:`str` |ASN.1| type component name - value : :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative + Keyword Args + ------------ + value: :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative A Python value to initialize |ASN.1| component with (if *componentType* is set) or ASN.1 value object to assign to |ASN.1| component. @@ -2226,32 +2207,94 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): idx, value, verifyConstraints, matchTags, matchConstraints ) - def getComponentByPosition(self, idx): + def getComponentByPosition(self, idx, default=noValue, instantiate=True): """Returns |ASN.1| type component by index. Equivalent to Python sequence subscription operation (e.g. `[]`). Parameters ---------- - idx : :class:`int` + idx: :class:`int` Component index (zero-based). Must either refer to an existing - component or (if *componentType* is set) new ASN.1 type object gets + component or (if *componentType* is set) new ASN.1 schema object gets instantiated. + Keyword Args + ------------ + default: :class:`object` + If set and requested component is a schema object, return the `default` + object instead of the requested component. + + instantiate: :class:`bool` + If `True` (default), inner component will be automatically instantiated. + If 'False' either existing component or the `noValue` object will be + returned. + Returns ------- : :py:class:`~pyasn1.type.base.PyAsn1Item` a PyASN1 object + + Examples + -------- + + .. code-block:: python + + # can also be Set + class MySequence(Sequence): + componentType = NamedTypes( + NamedType('id', OctetString()) + ) + + s = MySequence() + + # returns component #0 with `.isValue` property False + s.getComponentByPosition(0) + + # returns None + s.getComponentByPosition(0, default=None) + + s.clear() + + # returns noValue + s.getComponentByPosition(0, instantiate=False) + + # sets component #0 to OctetString() ASN.1 schema + # object and returns it + s.getComponentByPosition(0, instantiate=True) + + # sets component #0 to ASN.1 value object + s.setComponentByPosition(0, 'ABCD') + + # returns OctetString('ABCD') value object + s.getComponentByPosition(0, instantiate=False) + + s.clear() + + # returns noValue + s.getComponentByPosition(0, instantiate=False) """ try: componentValue = self._componentValues[idx] + except IndexError: componentValue = noValue + if not instantiate: + if componentValue is noValue or not componentValue.isValue: + return default + else: + return componentValue + if componentValue is noValue: self.setComponentByPosition(idx) - return self._componentValues[idx] + componentValue = self._componentValues[idx] + + if default is noValue or componentValue.isValue: + return componentValue + else: + return default def setComponentByPosition(self, idx, value=noValue, verifyConstraints=True, @@ -2269,7 +2312,9 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): otherwise. In the latter case a new component of given ASN.1 type gets instantiated and appended to |ASN.1| sequence. - value : :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative + Keyword Args + ------------ + value: :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative A Python value to initialize |ASN.1| component with (if *componentType* is set) or ASN.1 value object to assign to |ASN.1| component. @@ -2286,45 +2331,51 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): ------- self """ - if value is None: # backward compatibility - value = noValue - componentType = self.componentType componentTypeLen = self._componentTypeLen try: currentValue = self._componentValues[idx] + except IndexError: currentValue = noValue if componentTypeLen: if componentTypeLen < idx: raise error.PyAsn1Error('component index out of range') + self._componentValues = [noValue] * componentTypeLen if value is noValue: if componentTypeLen: value = componentType.getTypeByPosition(idx).clone() + elif currentValue is noValue: raise error.PyAsn1Error('Component type not defined') + elif not isinstance(value, base.Asn1Item): if componentTypeLen: subComponentType = componentType.getTypeByPosition(idx) if isinstance(subComponentType, base.AbstractSimpleAsn1Item): value = subComponentType.clone(value=value) + else: raise error.PyAsn1Error('%s can cast only scalar values' % componentType.__class__.__name__) + elif currentValue is not noValue and isinstance(currentValue, base.AbstractSimpleAsn1Item): value = currentValue.clone(value=value) + else: raise error.PyAsn1Error('%s undefined component type' % componentType.__class__.__name__) + elif (matchTags or matchConstraints) and componentTypeLen: subComponentType = componentType.getTypeByPosition(idx) if subComponentType is not noValue: - if self.strictConstraints: - if not subComponentType.isSameTypeWith(value, matchTags, matchConstraints): - raise error.PyAsn1Error('Component value is tag-incompatible: %r vs %r' % (value, componentType)) - else: - if not subComponentType.isSuperTypeOf(value, matchTags, matchConstraints): + subtypeChecker = (self.strictConstraints and + subComponentType.isSameTypeWith or + subComponentType.isSuperTypeOf) + + if not subtypeChecker(value, matchTags, matchConstraints): + if not componentType[idx].openType: raise error.PyAsn1Error('Component value is tag-incompatible: %r vs %r' % (value, componentType)) if verifyConstraints and value.isValue: @@ -2337,9 +2388,11 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): if componentTypeLen or idx in self._dynamicNames: self._componentValues[idx] = value + elif len(self._componentValues) == idx: self._componentValues.append(value) self._dynamicNames.addField(idx) + else: raise error.PyAsn1Error('Component index out of range') @@ -2347,29 +2400,30 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): @property def isValue(self): - """Indicate if |ASN.1| object represents ASN.1 type or ASN.1 value. + """Indicate that |ASN.1| object represents ASN.1 value. - In other words, if *isValue* is `True`, then the ASN.1 object is - initialized. + If *isValue* is `False` then this object represents just ASN.1 schema. - For the purpose of check, the *OPTIONAL* and *DEFAULT* fields are - unconditionally considered as initialized. + If *isValue* is `True` then, in addition to its ASN.1 schema features, + this object can also be used like a Python built-in object (e.g. `int`, + `str`, `dict` etc.). Returns ------- : :class:`bool` - :class:`True` if object represents ASN.1 value and type, - :class:`False` if object represents just ASN.1 type. + :class:`False` if object represents just ASN.1 schema. + :class:`True` if object represents ASN.1 schema and can be used as a normal value. Note ---- - There is an important distinction between PyASN1 type and value objects. - The PyASN1 type objects can only participate in ASN.1 type - operations (subtyping, comparison etc) and serve as a - blueprint for serialization codecs to resolve ambiguous types. + There is an important distinction between PyASN1 schema and value objects. + The PyASN1 schema objects can only participate in ASN.1 schema-related + operations (e.g. defining or testing the structure of the data). Most + obvious uses of ASN.1 schema is to guide serialisation codecs whilst + encoding/decoding serialised ASN.1 contents. - The PyASN1 value objects can additionally participate in most - of built-in Python operations. + The PyASN1 value objects can **additionally** participate in many operations + involving regular Python objects (e.g. arithmetic, comprehension etc). """ componentType = self.componentType @@ -2377,13 +2431,17 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): for idx, subComponentType in enumerate(componentType.namedTypes): if subComponentType.isDefaulted or subComponentType.isOptional: continue - if (not self._componentValues or - not self._componentValues[idx].isValue): + + if not self._componentValues: + return False + + componentValue = self._componentValues[idx] + if componentValue is noValue or not componentValue.isValue: return False else: for componentValue in self._componentValues: - if not componentValue.isValue: + if componentValue is noValue or not componentValue.isValue: return False return True @@ -2506,7 +2564,8 @@ class Set(SequenceAndSetBase): def getComponent(self, innerFlag=False): return self - def getComponentByType(self, tagSet, innerFlag=False): + def getComponentByType(self, tagSet, default=noValue, + instantiate=True, innerFlag=False): """Returns |ASN.1| type component by ASN.1 tag. Parameters @@ -2515,20 +2574,32 @@ class Set(SequenceAndSetBase): Object representing ASN.1 tags to identify one of |ASN.1| object component + Keyword Args + ------------ + default: :class:`object` + If set and requested component is a schema object, return the `default` + object instead of the requested component. + + instantiate: :class:`bool` + If `True` (default), inner component will be automatically instantiated. + If 'False' either existing component or the `noValue` object will be + returned. + Returns ------- : :py:class:`~pyasn1.type.base.PyAsn1Item` a pyasn1 object """ - component = self.getComponentByPosition( - self.componentType.getPositionByType(tagSet) + componentValue = self.getComponentByPosition( + self.componentType.getPositionByType(tagSet), + default=default, instantiate=instantiate ) - if innerFlag and isinstance(component, Set): + if innerFlag and isinstance(componentValue, Set): # get inner component by inner tagSet - return component.getComponent(innerFlag=True) + return componentValue.getComponent(innerFlag=True) else: # get outer component by inner tagSet - return component + return componentValue def setComponentByType(self, tagSet, value=noValue, verifyConstraints=True, @@ -2543,7 +2614,9 @@ class Set(SequenceAndSetBase): Object representing ASN.1 tags to identify one of |ASN.1| object component - value : :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative + Keyword Args + ------------ + value: :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative A Python value to initialize |ASN.1| component with (if *componentType* is set) or ASN.1 value object to assign to |ASN.1| component. @@ -2589,8 +2662,50 @@ class Set(SequenceAndSetBase): class Choice(Set): - __doc__ = Set.__doc__ + """Create |ASN.1| type. + |ASN.1| objects are mutable and duck-type Python :class:`dict` objects. + + Keyword Args + ------------ + componentType: :py:class:`~pyasn1.type.namedtype.NamedType` + Object holding named ASN.1 types allowed within this collection + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + sizeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing collection size constraint + + Examples + -------- + + .. code-block:: python + + class Afters(Choice): + ''' + ASN.1 specification: + + Afters ::= CHOICE { + cheese [0] IA5String, + dessert [1] IA5String + } + ''' + componentType = NamedTypes( + NamedType('cheese', IA5String().subtype( + implicitTag=Tag(tagClassContext, tagFormatSimple, 0) + ), + NamedType('dessert', IA5String().subtype( + implicitTag=Tag(tagClassContext, tagFormatSimple, 1) + ) + ) + + afters = Afters() + afters['cheese'] = 'Mascarpone' + """ #: Set (on class, not on instance) or return a #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) #: associated with |ASN.1| type. @@ -2701,11 +2816,12 @@ class Choice(Set): else: myClone.setComponentByType(tagSet, component.clone()) - def getComponentByPosition(self, idx): + def getComponentByPosition(self, idx, default=noValue, instantiate=True): __doc__ = Set.__doc__ if self._currentIdx is None or self._currentIdx != idx: - return Set.getComponentByPosition(self, idx) + return Set.getComponentByPosition(self, idx, default=default, + instantiate=instantiate) return self._componentValues[idx] @@ -2725,6 +2841,8 @@ class Choice(Set): type gets instantiated (if *componentType* is set, or given ASN.1 object is taken otherwise) and appended to the |ASN.1| sequence. + Keyword Args + ------------ value: :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative A Python value to initialize |ASN.1| component with (if *componentType* is set) or ASN.1 value object to assign to |ASN.1| component. Once a new value is @@ -2747,16 +2865,9 @@ class Choice(Set): Set.setComponentByPosition(self, idx, value, verifyConstraints, matchTags, matchConstraints) self._currentIdx = idx if oldIdx is not None and oldIdx != idx: - self._componentValues[oldIdx] = None + self._componentValues[oldIdx] = noValue return self - @property - def minTagSet(self): - if self.tagSet: - return self.tagSet - else: - return self.componentType.minTagSet - @property def effectiveTagSet(self): """Return a :class:`~pyasn1.type.tag.TagSet` object of the currently initialized component or self (if |ASN.1| is tagged).""" @@ -2776,7 +2887,7 @@ class Choice(Set): else: return self.componentType.tagMapUnique - def getComponent(self, innerFlag=0): + def getComponent(self, innerFlag=False): """Return currently assigned component of the |ASN.1| object. Returns @@ -2812,25 +2923,41 @@ class Choice(Set): @property def isValue(self): - """Indicate if |ASN.1| component is set and represents ASN.1 type or ASN.1 value. + """Indicate that |ASN.1| object represents ASN.1 value. - The PyASN1 type objects can only participate in types comparison - and serve as a blueprint for serialization codecs to resolve - ambiguous types. + If *isValue* is `False` then this object represents just ASN.1 schema. - The PyASN1 value objects can additionally participate in most - of built-in Python operations. + If *isValue* is `True` then, in addition to its ASN.1 schema features, + this object can also be used like a Python built-in object (e.g. `int`, + `str`, `dict` etc.). Returns ------- : :class:`bool` - :class:`True` if |ASN.1| component is set and represent value and type, - :class:`False` if |ASN.1| component is not set or it represents just ASN.1 type. + :class:`False` if object represents just ASN.1 schema. + :class:`True` if object represents ASN.1 schema and can be used as a normal value. + + Note + ---- + There is an important distinction between PyASN1 schema and value objects. + The PyASN1 schema objects can only participate in ASN.1 schema-related + operations (e.g. defining or testing the structure of the data). Most + obvious uses of ASN.1 schema is to guide serialisation codecs whilst + encoding/decoding serialised ASN.1 contents. + + The PyASN1 value objects can **additionally** participate in many operations + involving regular Python objects (e.g. arithmetic, comprehension etc). """ if self._currentIdx is None: return False - return self._componentValues[self._currentIdx].isValue + componentValue = self._componentValues[self._currentIdx] + + return componentValue is not noValue and componentValue.isValue + + def clear(self): + self._currentIdx = None + Set.clear(self) # compatibility stubs @@ -2839,8 +2966,67 @@ class Choice(Set): class Any(OctetString): - __doc__ = OctetString.__doc__ + """Create |ASN.1| schema or value object. + |ASN.1| objects are immutable and duck-type Python 2 :class:`str` or Python 3 + :class:`bytes`. When used in Unicode context, |ASN.1| type assumes "|encoding|" + serialisation. + + Keyword Args + ------------ + value: :class:`str`, :class:`bytes` or |ASN.1| object + string (Python 2) or bytes (Python 3), alternatively unicode object + (Python 2) or string (Python 3) representing character string to be + serialised into octets (note `encoding` parameter) or |ASN.1| object. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + encoding: :py:class:`str` + Unicode codec ID to encode/decode :class:`unicode` (Python 2) or + :class:`str` (Python 3) the payload when |ASN.1| object is used + in text string context. + + binValue: :py:class:`str` + Binary string initializer to use instead of the *value*. + Example: '10110011'. + + hexValue: :py:class:`str` + Hexadecimal string initializer to use instead of the *value*. + Example: 'DEADBEEF'. + + Raises + ------ + :py:class:`~pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + + Examples + -------- + .. code-block:: python + + class Error(Sequence): + ''' + ASN.1 specification: + + Error ::= SEQUENCE { + code INTEGER, + parameter ANY DEFINED BY code -- Either INTEGER or REAL + } + ''' + componentType=NamedTypes( + NamedType('code', Integer()), + NamedType('parameter', Any(), + openType=OpenType('code', {1: Integer(), + 2: Real()})) + ) + + error = Error() + error['code'] = 1 + error['parameter'] = Integer(1234) + """ #: Set (on class, not on instance) or return a #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) #: associated with |ASN.1| type. diff --git a/src/pyasn1/type/useful.py b/src/pyasn1/type/useful.py index a05a9a60..146916d4 100644 --- a/src/pyasn1/type/useful.py +++ b/src/pyasn1/type/useful.py @@ -1,13 +1,17 @@ # # This file is part of pyasn1 software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # import datetime -from pyasn1.type import univ, char, tag -from pyasn1.compat import string, dateandtime + from pyasn1 import error +from pyasn1.compat import dateandtime +from pyasn1.compat import string +from pyasn1.type import char +from pyasn1.type import tag +from pyasn1.type import univ __all__ = ['ObjectDescriptor', 'GeneralizedTime', 'UTCTime'] @@ -61,7 +65,7 @@ class TimeMixIn(object): Returns ------- : - new instance of :py:class:`datetime.datetime` object + new instance of :py:class:`datetime.datetime` object """ text = str(self) if text.endswith('Z'): @@ -100,7 +104,7 @@ class TimeMixIn(object): text, _, ms = string.partition(text, ',') try: - ms = int(ms) * 10000 + ms = int(ms) * 1000 except ValueError: raise error.PyAsn1Error('bad sub-second time specification %s' % self) @@ -127,10 +131,10 @@ class TimeMixIn(object): Parameters ---------- - dt : :py:class:`datetime.datetime` object - The `datetime.datetime` object to initialize the |ASN.1| object from - - + dt: :py:class:`datetime.datetime` object + The `datetime.datetime` object to initialize the |ASN.1| object + from + Returns ------- : @@ -138,7 +142,7 @@ class TimeMixIn(object): """ text = dt.strftime(cls._yearsDigits == 4 and '%Y%m%d%H%M%S' or '%y%m%d%H%M%S') if cls._hasSubsecond: - text += '.%d' % (dt.microsecond // 10000) + text += '.%d' % (dt.microsecond // 1000) if dt.utcoffset(): seconds = dt.utcoffset().seconds diff --git a/src/pyasn1_modules/__init__.py b/src/pyasn1_modules/__init__.py index 2a2bbbf6..a3aedb62 100644 --- a/src/pyasn1_modules/__init__.py +++ b/src/pyasn1_modules/__init__.py @@ -1,2 +1,2 @@ # http://www.python.org/dev/peps/pep-0396/ -__version__ = '0.1.4' +__version__ = '0.2.2' diff --git a/src/pyasn1_modules/pem.py b/src/pyasn1_modules/pem.py index 9f16308a..e72b97fd 100644 --- a/src/pyasn1_modules/pem.py +++ b/src/pyasn1_modules/pem.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # import base64 import sys diff --git a/src/pyasn1_modules/rfc1155.py b/src/pyasn1_modules/rfc1155.py index 4980a38e..efe39bc3 100644 --- a/src/pyasn1_modules/rfc1155.py +++ b/src/pyasn1_modules/rfc1155.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # SNMPv1 message syntax # @@ -12,7 +12,10 @@ # Sample captures from: # http://wiki.wireshark.org/SampleCaptures/ # -from pyasn1.type import univ, namedtype, tag, constraint +from pyasn1.type import constraint +from pyasn1.type import namedtype +from pyasn1.type import tag +from pyasn1.type import univ class ObjectName(univ.ObjectIdentifier): diff --git a/src/pyasn1_modules/rfc1157.py b/src/pyasn1_modules/rfc1157.py index 1ad1d271..c616dfcf 100644 --- a/src/pyasn1_modules/rfc1157.py +++ b/src/pyasn1_modules/rfc1157.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # SNMPv1 message syntax # @@ -12,7 +12,11 @@ # Sample captures from: # http://wiki.wireshark.org/SampleCaptures/ # -from pyasn1.type import univ, namedtype, namedval, tag +from pyasn1.type import namedtype +from pyasn1.type import namedval +from pyasn1.type import tag +from pyasn1.type import univ + from pyasn1_modules import rfc1155 diff --git a/src/pyasn1_modules/rfc1901.py b/src/pyasn1_modules/rfc1901.py index eadf9aa3..16c83327 100644 --- a/src/pyasn1_modules/rfc1901.py +++ b/src/pyasn1_modules/rfc1901.py @@ -1,15 +1,17 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # SNMPv2c message syntax # # ASN.1 source from: # http://www.ietf.org/rfc/rfc1901.txt # -from pyasn1.type import univ, namedtype, namedval +from pyasn1.type import namedtype +from pyasn1.type import namedval +from pyasn1.type import univ class Message(univ.Sequence): diff --git a/src/pyasn1_modules/rfc1902.py b/src/pyasn1_modules/rfc1902.py index 5e9307e5..b4373f5e 100644 --- a/src/pyasn1_modules/rfc1902.py +++ b/src/pyasn1_modules/rfc1902.py @@ -1,15 +1,18 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # SNMPv2c message syntax # # ASN.1 source from: # http://www.ietf.org/rfc/rfc1902.txt # -from pyasn1.type import univ, namedtype, tag, constraint +from pyasn1.type import constraint +from pyasn1.type import namedtype +from pyasn1.type import tag +from pyasn1.type import univ class Integer(univ.Integer): diff --git a/src/pyasn1_modules/rfc1905.py b/src/pyasn1_modules/rfc1905.py index de5bb031..e35f37df 100644 --- a/src/pyasn1_modules/rfc1905.py +++ b/src/pyasn1_modules/rfc1905.py @@ -1,15 +1,20 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # SNMPv2c PDU syntax # # ASN.1 source from: # http://www.ietf.org/rfc/rfc1905.txt # -from pyasn1.type import univ, namedtype, namedval, tag, constraint +from pyasn1.type import constraint +from pyasn1.type import namedtype +from pyasn1.type import namedval +from pyasn1.type import tag +from pyasn1.type import univ + from pyasn1_modules import rfc1902 max_bindings = rfc1902.Integer(2147483647) diff --git a/src/pyasn1_modules/rfc2251.py b/src/pyasn1_modules/rfc2251.py index 94ba5891..88ee9a87 100644 --- a/src/pyasn1_modules/rfc2251.py +++ b/src/pyasn1_modules/rfc2251.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # LDAP message syntax # @@ -12,7 +12,11 @@ # Sample captures from: # http://wiki.wireshark.org/SampleCaptures/ # -from pyasn1.type import tag, namedtype, namedval, univ, constraint +from pyasn1.type import constraint +from pyasn1.type import namedtype +from pyasn1.type import namedval +from pyasn1.type import tag +from pyasn1.type import univ maxInt = univ.Integer(2147483647) diff --git a/src/pyasn1_modules/rfc2314.py b/src/pyasn1_modules/rfc2314.py index ef6a65bb..5a6d9273 100644 --- a/src/pyasn1_modules/rfc2314.py +++ b/src/pyasn1_modules/rfc2314.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # PKCS#10 syntax # diff --git a/src/pyasn1_modules/rfc2315.py b/src/pyasn1_modules/rfc2315.py index cf732b05..c7e53b9b 100644 --- a/src/pyasn1_modules/rfc2315.py +++ b/src/pyasn1_modules/rfc2315.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # PKCS#7 message syntax # @@ -25,7 +25,8 @@ class Attribute(univ.Sequence): class AttributeValueAssertion(univ.Sequence): componentType = namedtype.NamedTypes( namedtype.NamedType('attributeType', AttributeType()), - namedtype.NamedType('attributeValue', AttributeValue()) + namedtype.NamedType('attributeValue', AttributeValue(), + openType=opentype.OpenType('type', certificateAttributesMap)) ) @@ -50,12 +51,19 @@ class EncryptedContent(univ.OctetString): pass +contentTypeMap = {} + + class EncryptedContentInfo(univ.Sequence): componentType = namedtype.NamedTypes( namedtype.NamedType('contentType', ContentType()), namedtype.NamedType('contentEncryptionAlgorithm', ContentEncryptionAlgorithmIdentifier()), - namedtype.OptionalNamedType('encryptedContent', EncryptedContent().subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))) + namedtype.OptionalNamedType( + 'encryptedContent', EncryptedContent().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0) + ), + openType=opentype.OpenType('contentType', contentTypeMap) + ) ) @@ -85,8 +93,11 @@ class Digest(univ.OctetString): class ContentInfo(univ.Sequence): componentType = namedtype.NamedTypes( namedtype.NamedType('contentType', ContentType()), - namedtype.OptionalNamedType('content', univ.Any().subtype( - explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))) + namedtype.OptionalNamedType( + 'content', + univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)), + openType=opentype.OpenType('contentType', contentTypeMap) + ) ) @@ -270,3 +281,14 @@ class SignedData(univ.Sequence): class Data(univ.OctetString): pass + +_contentTypeMapUpdate = { + data: Data(), + signedData: SignedData(), + envelopedData: EnvelopedData(), + signedAndEnvelopedData: SignedAndEnvelopedData(), + digestedData: DigestedData(), + encryptedData: EncryptedData() +} + +contentTypeMap.update(_contentTypeMapUpdate) diff --git a/src/pyasn1_modules/rfc2437.py b/src/pyasn1_modules/rfc2437.py index 678d92d5..0866f570 100644 --- a/src/pyasn1_modules/rfc2437.py +++ b/src/pyasn1_modules/rfc2437.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # PKCS#1 syntax # @@ -11,7 +11,10 @@ # # Sample captures could be obtained with "openssl genrsa" command # -from pyasn1.type import tag, namedtype, univ +from pyasn1.type import namedtype +from pyasn1.type import tag +from pyasn1.type import univ + from pyasn1_modules.rfc2459 import AlgorithmIdentifier pkcs_1 = univ.ObjectIdentifier('1.2.840.113549.1.1') diff --git a/src/pyasn1_modules/rfc2459.py b/src/pyasn1_modules/rfc2459.py index c988c4f2..3d00adf7 100644 --- a/src/pyasn1_modules/rfc2459.py +++ b/src/pyasn1_modules/rfc2459.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # X.509 message syntax # @@ -13,7 +13,14 @@ # Sample captures from: # http://wiki.wireshark.org/SampleCaptures/ # -from pyasn1.type import tag, namedtype, namedval, univ, constraint, char, useful +from pyasn1.type import char +from pyasn1.type import constraint +from pyasn1.type import namedtype +from pyasn1.type import namedval +from pyasn1.type import opentype +from pyasn1.type import tag +from pyasn1.type import univ +from pyasn1.type import useful MAX = float('inf') @@ -84,26 +91,6 @@ id_ad_ocsp = univ.ObjectIdentifier('1.3.6.1.5.5.7.48.1') id_ad_caIssuers = univ.ObjectIdentifier('1.3.6.1.5.5.7.48.2') -class AttributeValue(univ.Any): - pass - - -class AttributeType(univ.ObjectIdentifier): - pass - - -class AttributeTypeAndValue(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('type', AttributeType()), - namedtype.NamedType('value', AttributeValue()) - ) - - -class Attribute(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('type', AttributeType()), - namedtype.NamedType('vals', univ.SetOf(componentType=AttributeValue())) - ) id_at = univ.ObjectIdentifier('2.5.4') @@ -277,19 +264,6 @@ class DSAPrivateKey(univ.Sequence): # ---- -class RelativeDistinguishedName(univ.SetOf): - componentType = AttributeTypeAndValue() - - -class RDNSequence(univ.SequenceOf): - componentType = RelativeDistinguishedName() - - -class Name(univ.Choice): - componentType = namedtype.NamedTypes( - namedtype.NamedType('', RDNSequence()) - ) - class DirectoryString(univ.Choice): componentType = namedtype.NamedTypes( @@ -316,111 +290,6 @@ class AlgorithmIdentifier(univ.Sequence): ) -class Extension(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('extnID', univ.ObjectIdentifier()), - namedtype.DefaultedNamedType('critical', univ.Boolean('False')), - namedtype.NamedType('extnValue', univ.Any()) - ) - - -class Extensions(univ.SequenceOf): - componentType = Extension() - sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX) - - -class SubjectPublicKeyInfo(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('algorithm', AlgorithmIdentifier()), - namedtype.NamedType('subjectPublicKey', univ.BitString()) - ) - - -class UniqueIdentifier(univ.BitString): - pass - - -class Time(univ.Choice): - componentType = namedtype.NamedTypes( - namedtype.NamedType('utcTime', useful.UTCTime()), - namedtype.NamedType('generalTime', useful.GeneralizedTime()) - ) - - -class Validity(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('notBefore', Time()), - namedtype.NamedType('notAfter', Time()) - ) - - -class CertificateSerialNumber(univ.Integer): - pass - - -class Version(univ.Integer): - namedValues = namedval.NamedValues( - ('v1', 0), ('v2', 1), ('v3', 2) - ) - - -class TBSCertificate(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.DefaultedNamedType('version', Version('v1').subtype( - explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), - namedtype.NamedType('serialNumber', CertificateSerialNumber()), - namedtype.NamedType('signature', AlgorithmIdentifier()), - namedtype.NamedType('issuer', Name()), - namedtype.NamedType('validity', Validity()), - namedtype.NamedType('subject', Name()), - namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo()), - namedtype.OptionalNamedType('issuerUniqueID', UniqueIdentifier().subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), - namedtype.OptionalNamedType('subjectUniqueID', UniqueIdentifier().subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), - namedtype.OptionalNamedType('extensions', Extensions().subtype( - explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))) - ) - - -class Certificate(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('tbsCertificate', TBSCertificate()), - namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()), - namedtype.NamedType('signatureValue', univ.BitString()) - ) - - -# CRL structures - -class RevokedCertificate(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('userCertificate', CertificateSerialNumber()), - namedtype.NamedType('revocationDate', Time()), - namedtype.OptionalNamedType('crlEntryExtensions', Extensions()) - ) - - -class TBSCertList(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.OptionalNamedType('version', Version()), - namedtype.NamedType('signature', AlgorithmIdentifier()), - namedtype.NamedType('issuer', Name()), - namedtype.NamedType('thisUpdate', Time()), - namedtype.OptionalNamedType('nextUpdate', Time()), - namedtype.OptionalNamedType('revokedCertificates', univ.SequenceOf(componentType=RevokedCertificate())), - namedtype.OptionalNamedType('crlExtensions', Extensions().subtype( - explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))) - ) - - -class CertificateList(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('tbsCertList', TBSCertList()), - namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()), - namedtype.NamedType('signature', univ.BitString()) - ) - # Algorithm OIDs and parameter structures @@ -972,11 +841,6 @@ class BasicConstraints(univ.Sequence): id_ce_subjectDirectoryAttributes = univ.ObjectIdentifier('2.5.29.9') -class SubjectDirectoryAttributes(univ.SequenceOf): - componentType = Attribute() - subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) - - class EDIPartyName(univ.Sequence): componentType = namedtype.NamedTypes( namedtype.OptionalNamedType('nameAssigner', DirectoryString().subtype( @@ -986,76 +850,10 @@ class EDIPartyName(univ.Sequence): ) -class AnotherName(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('type-id', univ.ObjectIdentifier()), - namedtype.NamedType('value', - univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))) - ) - - -class GeneralName(univ.Choice): - componentType = namedtype.NamedTypes( - namedtype.NamedType('otherName', - AnotherName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), - namedtype.NamedType('rfc822Name', - char.IA5String().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), - namedtype.NamedType('dNSName', - char.IA5String().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), - namedtype.NamedType('x400Address', - ORAddress().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))), - namedtype.NamedType('directoryName', - Name().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))), - namedtype.NamedType('ediPartyName', - EDIPartyName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 5))), - namedtype.NamedType('uniformResourceIdentifier', - char.IA5String().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 6))), - namedtype.NamedType('iPAddress', univ.OctetString().subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 7))), - namedtype.NamedType('registeredID', univ.ObjectIdentifier().subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 8))) - ) - - -class GeneralNames(univ.SequenceOf): - componentType = GeneralName() - subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) - - -class AccessDescription(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('accessMethod', univ.ObjectIdentifier()), - namedtype.NamedType('accessLocation', GeneralName()) - ) - - -class AuthorityInfoAccessSyntax(univ.SequenceOf): - componentType = AccessDescription() - subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) - id_ce_deltaCRLIndicator = univ.ObjectIdentifier('2.5.29.27') -class DistributionPointName(univ.Choice): - componentType = namedtype.NamedTypes( - namedtype.NamedType('fullName', GeneralNames().subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), - namedtype.NamedType('nameRelativeToCRLIssuer', RelativeDistinguishedName().subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))) - ) - - -class DistributionPoint(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.OptionalNamedType('distributionPoint', DistributionPointName().subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), - namedtype.OptionalNamedType('reasons', ReasonFlags().subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), - namedtype.OptionalNamedType('cRLIssuer', GeneralNames().subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))) - ) - class BaseDistance(univ.Integer): subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueRangeConstraint(0, MAX) @@ -1064,56 +862,14 @@ class BaseDistance(univ.Integer): id_ce_cRLDistributionPoints = univ.ObjectIdentifier('2.5.29.31') -class CRLDistPointsSyntax(univ.SequenceOf): - componentType = DistributionPoint() - subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) - - id_ce_issuingDistributionPoint = univ.ObjectIdentifier('2.5.29.28') -class IssuingDistributionPoint(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.OptionalNamedType('distributionPoint', DistributionPointName().subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), - namedtype.NamedType('onlyContainsUserCerts', univ.Boolean(False).subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), - namedtype.NamedType('onlyContainsCACerts', univ.Boolean(False).subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), - namedtype.OptionalNamedType('onlySomeReasons', ReasonFlags().subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))), - namedtype.NamedType('indirectCRL', univ.Boolean(False).subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))) - ) - - -class GeneralSubtree(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('base', GeneralName()), - namedtype.DefaultedNamedType('minimum', BaseDistance(0).subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), - namedtype.OptionalNamedType('maximum', BaseDistance().subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))) - ) - - -class GeneralSubtrees(univ.SequenceOf): - componentType = GeneralSubtree() - subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) id_ce_nameConstraints = univ.ObjectIdentifier('2.5.29.30') -class NameConstraints(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.OptionalNamedType('permittedSubtrees', GeneralSubtrees().subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), - namedtype.OptionalNamedType('excludedSubtrees', GeneralSubtrees().subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))) - ) - - class DisplayText(univ.Choice): componentType = namedtype.NamedTypes( namedtype.NamedType('visibleString', @@ -1232,6 +988,110 @@ class SubjectKeyIdentifier(KeyIdentifier): pass +id_ce_certificateIssuer = univ.ObjectIdentifier('2.5.29.29') + + +id_ce_subjectAltName = univ.ObjectIdentifier('2.5.29.17') + + +id_ce_issuerAltName = univ.ObjectIdentifier('2.5.29.18') + + +class AttributeValue(univ.Any): + pass + + +class AttributeType(univ.ObjectIdentifier): + pass + +certificateAttributesMap = {} + + +class AttributeTypeAndValue(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('type', AttributeType()), + namedtype.NamedType('value', AttributeValue(), + openType=opentype.OpenType('type', certificateAttributesMap)) + ) + + +class Attribute(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('type', AttributeType()), + namedtype.NamedType('vals', univ.SetOf(componentType=AttributeValue())) + ) + + +class SubjectDirectoryAttributes(univ.SequenceOf): + componentType = Attribute() + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) + + +class RelativeDistinguishedName(univ.SetOf): + componentType = AttributeTypeAndValue() + + +class RDNSequence(univ.SequenceOf): + componentType = RelativeDistinguishedName() + + +class Name(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('', RDNSequence()) + ) + +class CertificateSerialNumber(univ.Integer): + pass + + +class AnotherName(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('type-id', univ.ObjectIdentifier()), + namedtype.NamedType('value', + univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))) + ) + + +class GeneralName(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('otherName', + AnotherName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), + namedtype.NamedType('rfc822Name', + char.IA5String().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), + namedtype.NamedType('dNSName', + char.IA5String().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), + namedtype.NamedType('x400Address', + ORAddress().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))), + namedtype.NamedType('directoryName', + Name().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))), + namedtype.NamedType('ediPartyName', + EDIPartyName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 5))), + namedtype.NamedType('uniformResourceIdentifier', + char.IA5String().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 6))), + namedtype.NamedType('iPAddress', univ.OctetString().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 7))), + namedtype.NamedType('registeredID', univ.ObjectIdentifier().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 8))) + ) + + +class GeneralNames(univ.SequenceOf): + componentType = GeneralName() + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) + + +class AccessDescription(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('accessMethod', univ.ObjectIdentifier()), + namedtype.NamedType('accessLocation', GeneralName()) + ) + + +class AuthorityInfoAccessSyntax(univ.SequenceOf): + componentType = AccessDescription() + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) + + class AuthorityKeyIdentifier(univ.Sequence): componentType = namedtype.NamedTypes( namedtype.OptionalNamedType('keyIdentifier', KeyIdentifier().subtype( @@ -1243,30 +1103,189 @@ class AuthorityKeyIdentifier(univ.Sequence): ) -id_ce_certificateIssuer = univ.ObjectIdentifier('2.5.29.29') +class DistributionPointName(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('fullName', GeneralNames().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), + namedtype.NamedType('nameRelativeToCRLIssuer', RelativeDistinguishedName().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))) + ) + + +class DistributionPoint(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('distributionPoint', DistributionPointName().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), + namedtype.OptionalNamedType('reasons', ReasonFlags().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), + namedtype.OptionalNamedType('cRLIssuer', GeneralNames().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))) + ) + + +class CRLDistPointsSyntax(univ.SequenceOf): + componentType = DistributionPoint() + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) + + +class IssuingDistributionPoint(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('distributionPoint', DistributionPointName().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), + namedtype.NamedType('onlyContainsUserCerts', univ.Boolean(False).subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), + namedtype.NamedType('onlyContainsCACerts', univ.Boolean(False).subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), + namedtype.OptionalNamedType('onlySomeReasons', ReasonFlags().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))), + namedtype.NamedType('indirectCRL', univ.Boolean(False).subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))) + ) + + +class GeneralSubtree(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('base', GeneralName()), + namedtype.DefaultedNamedType('minimum', BaseDistance(0).subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), + namedtype.OptionalNamedType('maximum', BaseDistance().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))) + ) + + +class GeneralSubtrees(univ.SequenceOf): + componentType = GeneralSubtree() + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) + + +class NameConstraints(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('permittedSubtrees', GeneralSubtrees().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), + namedtype.OptionalNamedType('excludedSubtrees', GeneralSubtrees().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))) + ) class CertificateIssuer(GeneralNames): pass -id_ce_subjectAltName = univ.ObjectIdentifier('2.5.29.17') - - class SubjectAltName(GeneralNames): pass -id_ce_issuerAltName = univ.ObjectIdentifier('2.5.29.18') - - class IssuerAltName(GeneralNames): pass +certificateExtensionsMap = {} + + +class Extension(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('extnID', univ.ObjectIdentifier()), + namedtype.DefaultedNamedType('critical', univ.Boolean('False')), + namedtype.NamedType('extnValue', univ.OctetString(), + openType=opentype.OpenType('extnID', certificateExtensionsMap)) + ) + + +class Extensions(univ.SequenceOf): + componentType = Extension() + sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX) + + +class SubjectPublicKeyInfo(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('algorithm', AlgorithmIdentifier()), + namedtype.NamedType('subjectPublicKey', univ.BitString()) + ) + + +class UniqueIdentifier(univ.BitString): + pass + + +class Time(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('utcTime', useful.UTCTime()), + namedtype.NamedType('generalTime', useful.GeneralizedTime()) + ) + + +class Validity(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('notBefore', Time()), + namedtype.NamedType('notAfter', Time()) + ) + + +class Version(univ.Integer): + namedValues = namedval.NamedValues( + ('v1', 0), ('v2', 1), ('v3', 2) + ) + + +class TBSCertificate(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.DefaultedNamedType('version', Version('v1').subtype( + explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), + namedtype.NamedType('serialNumber', CertificateSerialNumber()), + namedtype.NamedType('signature', AlgorithmIdentifier()), + namedtype.NamedType('issuer', Name()), + namedtype.NamedType('validity', Validity()), + namedtype.NamedType('subject', Name()), + namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo()), + namedtype.OptionalNamedType('issuerUniqueID', UniqueIdentifier().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), + namedtype.OptionalNamedType('subjectUniqueID', UniqueIdentifier().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), + namedtype.OptionalNamedType('extensions', Extensions().subtype( + explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))) + ) + + +class Certificate(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('tbsCertificate', TBSCertificate()), + namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()), + namedtype.NamedType('signatureValue', univ.BitString()) + ) + +# CRL structures + +class RevokedCertificate(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('userCertificate', CertificateSerialNumber()), + namedtype.NamedType('revocationDate', Time()), + namedtype.OptionalNamedType('crlEntryExtensions', Extensions()) + ) + + +class TBSCertList(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('version', Version()), + namedtype.NamedType('signature', AlgorithmIdentifier()), + namedtype.NamedType('issuer', Name()), + namedtype.NamedType('thisUpdate', Time()), + namedtype.OptionalNamedType('nextUpdate', Time()), + namedtype.OptionalNamedType('revokedCertificates', univ.SequenceOf(componentType=RevokedCertificate())), + namedtype.OptionalNamedType('crlExtensions', Extensions().subtype( + explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))) + ) + + +class CertificateList(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('tbsCertList', TBSCertList()), + namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()), + namedtype.NamedType('signature', univ.BitString()) + ) + # map of AttributeType -> AttributeValue -certificateAttributesMap = { +_certificateAttributesMapUpdate = { id_at_name: X520name(), id_at_surname: X520name(), id_at_givenName: X520name(), @@ -1283,14 +1302,18 @@ certificateAttributesMap = { emailAddress: Pkcs9email(), } +certificateAttributesMap.update(_certificateAttributesMapUpdate) + + # map of Certificate Extension OIDs to Extensions -certificateExtensionsMap = { +_certificateExtensionsMapUpdate = { id_ce_authorityKeyIdentifier: AuthorityKeyIdentifier(), id_ce_subjectKeyIdentifier: SubjectKeyIdentifier(), id_ce_keyUsage: KeyUsage(), id_ce_privateKeyUsagePeriod: PrivateKeyUsagePeriod(), - id_ce_certificatePolicies: PolicyInformation(), # could be a sequence of concat'ed objects? +# TODO +# id_ce_certificatePolicies: PolicyInformation(), # could be a sequence of concat'ed objects? id_ce_policyMappings: PolicyMappings(), id_ce_subjectAltName: SubjectAltName(), id_ce_issuerAltName: IssuerAltName(), @@ -1309,3 +1332,6 @@ certificateExtensionsMap = { id_ce_invalidityDate: useful.GeneralizedTime(), id_ce_certificateIssuer: GeneralNames(), } + +certificateExtensionsMap.update(_certificateExtensionsMapUpdate) + diff --git a/src/pyasn1_modules/rfc2511.py b/src/pyasn1_modules/rfc2511.py index 4ae7db55..00ef4419 100644 --- a/src/pyasn1_modules/rfc2511.py +++ b/src/pyasn1_modules/rfc2511.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # X.509 certificate Request Message Format (CRMF) syntax # @@ -11,8 +11,8 @@ # # Sample captures could be obtained with OpenSSL # -from pyasn1_modules.rfc2459 import * from pyasn1_modules import rfc2315 +from pyasn1_modules.rfc2459 import * MAX = float('inf') diff --git a/src/pyasn1_modules/rfc2560.py b/src/pyasn1_modules/rfc2560.py index 25a4e62d..f6e0df07 100644 --- a/src/pyasn1_modules/rfc2560.py +++ b/src/pyasn1_modules/rfc2560.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # OCSP request/response syntax # @@ -21,7 +21,12 @@ # * dates are left as strings in GeneralizedTime format -- datetime.datetime # would be nicer # -from pyasn1.type import tag, namedtype, namedval, univ, useful +from pyasn1.type import namedtype +from pyasn1.type import namedval +from pyasn1.type import tag +from pyasn1.type import univ +from pyasn1.type import useful + from pyasn1_modules import rfc2459 @@ -124,9 +129,9 @@ class KeyHash(univ.OctetString): class ResponderID(univ.Choice): componentType = namedtype.NamedTypes( namedtype.NamedType('byName', - rfc2459.Name().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), + rfc2459.Name().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), namedtype.NamedType('byKey', - KeyHash().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))) + KeyHash().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))) ) diff --git a/src/pyasn1_modules/rfc2986.py b/src/pyasn1_modules/rfc2986.py new file mode 100644 index 00000000..47562c0b --- /dev/null +++ b/src/pyasn1_modules/rfc2986.py @@ -0,0 +1,124 @@ +# coding: utf-8 +# +# This file is part of pyasn1-modules software. +# +# Created by Joel Johnson with asn1ate tool. +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html +# +# PKCS #10: Certification Request Syntax Specification +# +# ASN.1 source from: +# http://www.ietf.org/rfc/rfc2986.txt +# +from pyasn1.type import univ +from pyasn1.type import char +from pyasn1.type import namedtype +from pyasn1.type import namedval +from pyasn1.type import opentype +from pyasn1.type import tag +from pyasn1.type import constraint +from pyasn1.type import useful + +MAX = float('inf') + + +class AttributeType(univ.ObjectIdentifier): + pass + + +class AttributeValue(univ.Any): + pass + + +certificateAttributesMap = {} + + +class AttributeTypeAndValue(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('type', AttributeType()), + namedtype.NamedType( + 'value', AttributeValue(), + openType=opentype.OpenType('type', certificateAttributesMap) + ) + ) + + +class Attribute(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('type', AttributeType()), + namedtype.NamedType('values', + univ.SetOf(componentType=AttributeValue()), + openType=opentype.OpenType('type', certificateAttributesMap)) + ) + + +class Attributes(univ.SetOf): + pass + + +Attributes.componentType = Attribute() + + +class RelativeDistinguishedName(univ.SetOf): + pass + + +RelativeDistinguishedName.componentType = AttributeTypeAndValue() +RelativeDistinguishedName.subtypeSpec = constraint.ValueSizeConstraint(1, MAX) + + +class RDNSequence(univ.SequenceOf): + pass + + +RDNSequence.componentType = RelativeDistinguishedName() + + +class Name(univ.Choice): + pass + + +Name.componentType = namedtype.NamedTypes( + namedtype.NamedType('rdnSequence', RDNSequence()) +) + + +class AlgorithmIdentifier(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('algorithm', univ.ObjectIdentifier()), + namedtype.OptionalNamedType('parameters', univ.Any()) + ) + + +class SubjectPublicKeyInfo(univ.Sequence): + pass + + +SubjectPublicKeyInfo.componentType = namedtype.NamedTypes( + namedtype.NamedType('algorithm', AlgorithmIdentifier()), + namedtype.NamedType('subjectPublicKey', univ.BitString()) +) + + +class CertificationRequestInfo(univ.Sequence): + pass + + +CertificationRequestInfo.componentType = namedtype.NamedTypes( + namedtype.NamedType('version', univ.Integer()), + namedtype.NamedType('subject', Name()), + namedtype.NamedType('subjectPKInfo', SubjectPublicKeyInfo()), + namedtype.NamedType('attributes', Attributes().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))) +) + + +class CertificationRequest(univ.Sequence): + pass + + +CertificationRequest.componentType = namedtype.NamedTypes( + namedtype.NamedType('certificationRequestInfo', CertificationRequestInfo()), + namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()), + namedtype.NamedType('signature', univ.BitString()) +) diff --git a/src/pyasn1_modules/rfc3279.py b/src/pyasn1_modules/rfc3279.py index f69ff085..428c0e8e 100644 --- a/src/pyasn1_modules/rfc3279.py +++ b/src/pyasn1_modules/rfc3279.py @@ -2,11 +2,13 @@ # This file is part of pyasn1-modules. # # Copyright (c) 2017, Danielle Madeley -# License: http://pyasn1.sf.net/license.html +# License: http://snmplabs.com/pyasn1/license.html # # Derived from RFC 3279 # -from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful +from pyasn1.type import namedtype +from pyasn1.type import namedval +from pyasn1.type import univ def _OID(*components): diff --git a/src/pyasn1_modules/rfc3280.py b/src/pyasn1_modules/rfc3280.py index 3614e6ce..58dba38b 100644 --- a/src/pyasn1_modules/rfc3280.py +++ b/src/pyasn1_modules/rfc3280.py @@ -3,8 +3,8 @@ # This file is part of pyasn1-modules software. # # Created by Stanisław Pitucha with asn1ate tool. -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # Internet X.509 Public Key Infrastructure Certificate and Certificate # Revocation List (CRL) Profile @@ -12,7 +12,13 @@ # ASN.1 source from: # http://www.ietf.org/rfc/rfc3280.txt # -from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful +from pyasn1.type import char +from pyasn1.type import constraint +from pyasn1.type import namedtype +from pyasn1.type import namedval +from pyasn1.type import tag +from pyasn1.type import univ +from pyasn1.type import useful MAX = float('inf') diff --git a/src/pyasn1_modules/rfc3281.py b/src/pyasn1_modules/rfc3281.py index 8aa99d39..9378a45e 100644 --- a/src/pyasn1_modules/rfc3281.py +++ b/src/pyasn1_modules/rfc3281.py @@ -3,20 +3,20 @@ # This file is part of pyasn1-modules software. # # Created by Stanisław Pitucha with asn1ate tool. -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # An Internet Attribute Certificate Profile for Authorization # # ASN.1 source from: # http://www.ietf.org/rfc/rfc3281.txt # -from pyasn1.type import univ from pyasn1.type import char +from pyasn1.type import constraint from pyasn1.type import namedtype from pyasn1.type import namedval from pyasn1.type import tag -from pyasn1.type import constraint +from pyasn1.type import univ from pyasn1.type import useful from pyasn1_modules import rfc3280 diff --git a/src/pyasn1_modules/rfc3412.py b/src/pyasn1_modules/rfc3412.py index b3f5a929..8644c627 100644 --- a/src/pyasn1_modules/rfc3412.py +++ b/src/pyasn1_modules/rfc3412.py @@ -1,15 +1,18 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # SNMPv3 message syntax # # ASN.1 source from: # http://www.ietf.org/rfc/rfc3412.txt # -from pyasn1.type import univ, namedtype, constraint +from pyasn1.type import constraint +from pyasn1.type import namedtype +from pyasn1.type import univ + from pyasn1_modules import rfc1905 diff --git a/src/pyasn1_modules/rfc3414.py b/src/pyasn1_modules/rfc3414.py index aeb82aa2..28183796 100644 --- a/src/pyasn1_modules/rfc3414.py +++ b/src/pyasn1_modules/rfc3414.py @@ -1,15 +1,17 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # SNMPv3 message syntax # # ASN.1 source from: # http://www.ietf.org/rfc/rfc3414.txt # -from pyasn1.type import univ, namedtype, constraint +from pyasn1.type import constraint +from pyasn1.type import namedtype +from pyasn1.type import univ class UsmSecurityParameters(univ.Sequence): diff --git a/src/pyasn1_modules/rfc3447.py b/src/pyasn1_modules/rfc3447.py index 57c99faa..ff5c6b52 100644 --- a/src/pyasn1_modules/rfc3447.py +++ b/src/pyasn1_modules/rfc3447.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # PKCS#1 syntax # @@ -11,7 +11,9 @@ # # Sample captures could be obtained with "openssl genrsa" command # -from pyasn1.type import constraint, namedval +from pyasn1.type import constraint +from pyasn1.type import namedval + from pyasn1_modules.rfc2437 import * diff --git a/src/pyasn1_modules/rfc3852.py b/src/pyasn1_modules/rfc3852.py index 872eb88c..04b215e3 100644 --- a/src/pyasn1_modules/rfc3852.py +++ b/src/pyasn1_modules/rfc3852.py @@ -3,15 +3,20 @@ # This file is part of pyasn1-modules software. # # Created by Stanisław Pitucha with asn1ate tool. -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # Cryptographic Message Syntax (CMS) # # ASN.1 source from: # http://www.ietf.org/rfc/rfc3852.txt # -from pyasn1.type import univ, namedtype, namedval, tag, constraint, useful +from pyasn1.type import constraint +from pyasn1.type import namedtype +from pyasn1.type import namedval +from pyasn1.type import tag +from pyasn1.type import univ +from pyasn1.type import useful from pyasn1_modules import rfc3280 from pyasn1_modules import rfc3281 diff --git a/src/pyasn1_modules/rfc4210.py b/src/pyasn1_modules/rfc4210.py index d7e6db09..39b468f5 100644 --- a/src/pyasn1_modules/rfc4210.py +++ b/src/pyasn1_modules/rfc4210.py @@ -1,15 +1,24 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # Certificate Management Protocol structures as per RFC4210 # # Based on Alex Railean's work # -from pyasn1.type import tag, namedtype, namedval, univ, constraint, char, useful -from pyasn1_modules import rfc2459, rfc2511, rfc2314 +from pyasn1.type import char +from pyasn1.type import constraint +from pyasn1.type import namedtype +from pyasn1.type import namedval +from pyasn1.type import tag +from pyasn1.type import univ +from pyasn1.type import useful + +from pyasn1_modules import rfc2314 +from pyasn1_modules import rfc2459 +from pyasn1_modules import rfc2511 MAX = float('inf') diff --git a/src/pyasn1_modules/rfc4211.py b/src/pyasn1_modules/rfc4211.py index d20da787..01c10cd5 100644 --- a/src/pyasn1_modules/rfc4211.py +++ b/src/pyasn1_modules/rfc4211.py @@ -3,8 +3,8 @@ # This file is part of pyasn1-modules software. # # Created by Stanisław Pitucha with asn1ate tool. -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # Internet X.509 Public Key Infrastructure Certificate Request # Message Format (CRMF) @@ -12,7 +12,12 @@ # ASN.1 source from: # http://www.ietf.org/rfc/rfc4211.txt # -from pyasn1.type import univ, char, namedtype, namedval, tag, constraint +from pyasn1.type import char +from pyasn1.type import constraint +from pyasn1.type import namedtype +from pyasn1.type import namedval +from pyasn1.type import tag +from pyasn1.type import univ from pyasn1_modules import rfc3280 from pyasn1_modules import rfc3852 diff --git a/src/pyasn1_modules/rfc5208.py b/src/pyasn1_modules/rfc5208.py index 6b6487d8..85bb5302 100644 --- a/src/pyasn1_modules/rfc5208.py +++ b/src/pyasn1_modules/rfc5208.py @@ -1,8 +1,8 @@ # # This file is part of pyasn1-modules software. # -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # PKCS#8 syntax # @@ -11,8 +11,8 @@ # # Sample captures could be obtained with "openssl pkcs8 -topk8" command # -from pyasn1_modules.rfc2459 import * from pyasn1_modules import rfc2251 +from pyasn1_modules.rfc2459 import * class KeyEncryptionAlgorithms(AlgorithmIdentifier): diff --git a/src/pyasn1_modules/rfc5280.py b/src/pyasn1_modules/rfc5280.py index 7d3aa695..1a01352c 100644 --- a/src/pyasn1_modules/rfc5280.py +++ b/src/pyasn1_modules/rfc5280.py @@ -3,8 +3,8 @@ # This file is part of pyasn1-modules software. # # Created by Stanisław Pitucha with asn1ate tool. -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # Internet X.509 Public Key Infrastructure Certificate and Certificate # Revocation List (CRL) Profile @@ -12,16 +12,18 @@ # ASN.1 source from: # http://www.ietf.org/rfc/rfc5280.txt # -from pyasn1.type import univ from pyasn1.type import char +from pyasn1.type import constraint from pyasn1.type import namedtype from pyasn1.type import namedval +from pyasn1.type import opentype from pyasn1.type import tag -from pyasn1.type import constraint +from pyasn1.type import univ from pyasn1.type import useful MAX = float('inf') + def _buildOid(*components): output = [] for x in tuple(components): @@ -279,13 +281,10 @@ class CertificateSerialNumber(univ.Integer): class AlgorithmIdentifier(univ.Sequence): - pass - - -AlgorithmIdentifier.componentType = namedtype.NamedTypes( - namedtype.NamedType('algorithm', univ.ObjectIdentifier()), - namedtype.OptionalNamedType('parameters', univ.Any()) -) + componentType = namedtype.NamedTypes( + namedtype.NamedType('algorithm', univ.ObjectIdentifier()), + namedtype.OptionalNamedType('parameters', univ.Any()) + ) class Time(univ.Choice): @@ -302,14 +301,17 @@ class AttributeValue(univ.Any): pass +certificateAttributesMap = {} + + class AttributeTypeAndValue(univ.Sequence): - pass - - -AttributeTypeAndValue.componentType = namedtype.NamedTypes( - namedtype.NamedType('type', AttributeType()), - namedtype.NamedType('value', AttributeValue()) -) + componentType = namedtype.NamedTypes( + namedtype.NamedType('type', AttributeType()), + namedtype.NamedType( + 'value', AttributeValue(), + openType=opentype.OpenType('type', certificateAttributesMap) + ) + ) class RelativeDistinguishedName(univ.SetOf): @@ -379,18 +381,21 @@ class PhysicalDeliveryOfficeName(PDSParameter): ub_extension_attributes = univ.Integer(256) +certificateExtensionsMap = { + +} + class ExtensionAttribute(univ.Sequence): - pass - - -ExtensionAttribute.componentType = namedtype.NamedTypes( - namedtype.NamedType('extension-attribute-type', univ.Integer().subtype( - subtypeSpec=constraint.ValueRangeConstraint(0, ub_extension_attributes)).subtype( - implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), - namedtype.NamedType('extension-attribute-value', - univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))) -) + componentType = namedtype.NamedTypes( + namedtype.NamedType( + 'extension-attribute-type', + univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(0, ub_extension_attributes)).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), + namedtype.NamedType( + 'extension-attribute-value', + univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)), + openType=opentype.OpenType('type', certificateExtensionsMap)) + ) id_qt = _buildOid(id_pkix, 2) @@ -737,13 +742,12 @@ X520SerialNumber.subtypeSpec = constraint.ValueSizeConstraint(1, ub_serial_numbe class Attribute(univ.Sequence): - pass - - -Attribute.componentType = namedtype.NamedTypes( - namedtype.NamedType('type', AttributeType()), - namedtype.NamedType('values', univ.SetOf(componentType=AttributeValue())) -) + componentType = namedtype.NamedTypes( + namedtype.NamedType('type', AttributeType()), + namedtype.NamedType('values', + univ.SetOf(componentType=AttributeValue()), + openType=opentype.OpenType('type', certificateAttributesMap)) + ) ub_common_name = univ.Integer(64) @@ -1066,14 +1070,20 @@ PrivateKeyUsagePeriod.componentType = namedtype.NamedTypes( ) +anotherNameMap = { + +} + + class AnotherName(univ.Sequence): - pass - - -AnotherName.componentType = namedtype.NamedTypes( - namedtype.NamedType('type-id', univ.ObjectIdentifier()), - namedtype.NamedType('value', univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))) -) + componentType = namedtype.NamedTypes( + namedtype.NamedType('type-id', univ.ObjectIdentifier()), + namedtype.NamedType( + 'value', + univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)), + openType=opentype.OpenType('type-id', anotherNameMap) + ) + ) class EDIPartyName(univ.Sequence): @@ -1311,14 +1321,19 @@ class PolicyQualifierId(univ.ObjectIdentifier): pass +policyQualifierInfoMap = { + +} + + class PolicyQualifierInfo(univ.Sequence): - pass - - -PolicyQualifierInfo.componentType = namedtype.NamedTypes( - namedtype.NamedType('policyQualifierId', PolicyQualifierId()), - namedtype.NamedType('qualifier', univ.Any()) -) + componentType = namedtype.NamedTypes( + namedtype.NamedType('policyQualifierId', PolicyQualifierId()), + namedtype.NamedType( + 'qualifier', univ.Any(), + openType=opentype.OpenType('policyQualifierId', policyQualifierInfoMap) + ) + ) class CertPolicyId(univ.ObjectIdentifier): @@ -1549,7 +1564,7 @@ id_ce_inhibitAnyPolicy = _buildOid(id_ce, 54) # map of AttributeType -> AttributeValue -certificateAttributesMap = { +_certificateAttributesMapUpdate = { id_at_name: X520name(), id_at_surname: X520name(), id_at_givenName: X520name(), @@ -1569,9 +1584,12 @@ certificateAttributesMap = { id_emailAddress: EmailAddress(), } +certificateAttributesMap.update(_certificateAttributesMapUpdate) + + # map of Certificate Extension OIDs to Extensions -certificateExtensionsMap = { +_certificateExtensionsMap = { id_ce_authorityKeyIdentifier: AuthorityKeyIdentifier(), id_ce_subjectKeyIdentifier: SubjectKeyIdentifier(), id_ce_keyUsage: KeyUsage(), @@ -1595,3 +1613,5 @@ certificateExtensionsMap = { id_ce_invalidityDate: useful.GeneralizedTime(), id_ce_certificateIssuer: GeneralNames(), } + +certificateExtensionsMap.update(_certificateExtensionsMap) diff --git a/src/pyasn1_modules/rfc5652.py b/src/pyasn1_modules/rfc5652.py index 5fd5b79a..309d1d61 100644 --- a/src/pyasn1_modules/rfc5652.py +++ b/src/pyasn1_modules/rfc5652.py @@ -3,8 +3,8 @@ # This file is part of pyasn1-modules software. # # Created by Stanisław Pitucha with asn1ate tool. -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # Cryptographic Message Syntax (CMS) # diff --git a/src/pyasn1_modules/rfc6402.py b/src/pyasn1_modules/rfc6402.py index c35f855f..3814a3d2 100644 --- a/src/pyasn1_modules/rfc6402.py +++ b/src/pyasn1_modules/rfc6402.py @@ -3,15 +3,21 @@ # This file is part of pyasn1-modules software. # # Created by Stanisław Pitucha with asn1ate tool. -# Copyright (c) 2005-2017, Ilya Etingof -# License: http://pyasn1.sf.net/license.html +# Copyright (c) 2005-2018, Ilya Etingof +# License: http://snmplabs.com/pyasn1/license.html # # Certificate Management over CMS (CMC) Updates # # ASN.1 source from: # http://www.ietf.org/rfc/rfc6402.txt # -from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful +from pyasn1.type import char +from pyasn1.type import constraint +from pyasn1.type import namedtype +from pyasn1.type import namedval +from pyasn1.type import tag +from pyasn1.type import univ +from pyasn1.type import useful from pyasn1_modules import rfc4211 from pyasn1_modules import rfc5280