From 18d1a11ddb94e55e4534bd1c1a54a2b10090631d Mon Sep 17 00:00:00 2001 From: Rick van Rein Date: Thu, 6 Apr 2017 09:17:38 +0100 Subject: [PATCH] Now able to reproduce DER unpacked content with anASN1Object._der_pack() - tested in test/certio.py using test/verisigin.der (X.509 certificate) - works in spite of nested SEQUENCE OF and SET OF, and "funny" types - explicitly used der_pack() / der_unpack() to mean "with DER header" - additionally used der_format() / der_parse() to mean "DER body only" - updated test/der_pack() with changes; renamed it to test/der_format() --- python/quick_der/api.py | 5 +- python/quick_der/classes.py | 188 ++++++++++++++++++++-------- python/quick_der/format.py | 139 ++++++++++++++++++++ python/quick_der/primitive.py | 101 ++++++++------- python/setup.py.in | 2 +- test/CMakeLists.txt | 2 +- test/certio.py | 16 ++- test/{der_pack.py => der_format.py} | 22 ++-- tool/asn2quickder.py | 10 +- 9 files changed, 372 insertions(+), 113 deletions(-) create mode 100644 python/quick_der/format.py rename test/{der_pack.py => der_format.py} (63%) diff --git a/python/quick_der/api.py b/python/quick_der/api.py index f7c5a5a..d5a7426 100644 --- a/python/quick_der/api.py +++ b/python/quick_der/api.py @@ -10,9 +10,12 @@ # Import the DER_TAG_xxx and DER_PACK_xxx symbol definitions from packstx import * -# Import primvite der_pack_TYPE and der_unpack_TYPE routines +# Import primvite der_parse_TYPE and der_parse_TYPE routines from primitive import * +# Import composite der_parse en der_format routines +from format import * + # Import ASN.1 supportive classes ASN1xxx from classes import * diff --git a/python/quick_der/classes.py b/python/quick_der/classes.py index 99e562c..661e6b1 100644 --- a/python/quick_der/classes.py +++ b/python/quick_der/classes.py @@ -6,6 +6,7 @@ from packstx import * import primitive +import format import builder @@ -70,8 +71,8 @@ def __init__ (self, derblob=None, bindata=None, offset=0, der_packer=None, recip self.__init_bindata__ () elif derblob: self._bindata = _quickder.der_unpack (self._der_packer, derblob, self._numcursori) - assert len (self._bindata) == self._numcursori, 'Wrong number of values returned from der_unpack()' self._offset = 0 + assert len (self._bindata) == self._numcursori, 'Wrong number of values returned from der_unpack()' assert offset == 0, 'You supplied a derblob, so you cannot request any offset but 0' self.__init_bindata__ () elif self._numcursori: @@ -151,12 +152,11 @@ def __init_bindata__ (self): print 'Not placing field', subfld, 'subvalue ::', type (subval) elif isinstance (subval, ASN1Object): self._fields [subfld] = subval - #HUH:WHY:DROP# self._bindata [self._offset] = self def _name2idx (self, name): while not self._fields.has_key (name): - if name [-1:] == '_': - name = name [:1] + if name [:1] == '_': + name = name [1:] continue raise AttributeError (name) return self._fields [name] @@ -191,17 +191,26 @@ def _der_pack (self): """ bindata = [] for bd in self._bindata [self._offset:self._offset+self._numcursori]: - #TODO# list, set, ... + #TODO# set, list, atomic... + print 'bindata[] element is a', type (bd) + if bd is not None and type (bd) != str: + # Hope to map the value to DER without hints + #TODO# Currently fails on ASN1Objects + bd = format.der_format (bd) bindata.append (bd) return _quickder.der_pack (self._der_packer, bindata) - def __dir__ (self): - """Explicitly list the contents of the ASN1ConstructedType. - Not sure why, but dir() ends in an infinite loop, probably - due to the __getattr__ definition in this class. Setting - an explicit __dir__() method helps. + def _der_format (self): + """Format the current ASN1ConstructedType using DER notation, + but withhold the DER header consisting of the outer tag + and length. This format is comparable to what is stored + in bindata array elements. To be able to produce proper + DER, it needs some contextual information (specifically, + the tag to prefix before the body). """ - return ['_der_packer','_numcursori','_recipe','_context','_bindata','_offset','_fields'] + self._fields.keys () + packed = self._der_pack (self) + (tag,ilen,hlen) = _quickder.der_header (packed) + return packed [hlen : hlen+ilen] def __str__ (self): retval = '{\n ' @@ -259,9 +268,23 @@ def __init_bindata__ (self): subval = builder.build_asn1 (self._context, subrcp, subcrs, 0) self.append (subval) derblob = derblob [hlen+ilen:] - #TODO:GENERIC# self._bindata [self._offset] = self + self._bindata [self._offset] = self def _der_pack (self): + """Return the result of the `der_pack()` operation on this + element. + """ + return primitive.der_prefixhead (DER_PACK_ENTER | DER_TAG_SEQUENCE, + self._der_format ()) + + def _der_format (self): + """Format the current ASN1SequenceOf using DER notation, + but withhold the DER header consisting of the outer tag + and length. This format is comparable to what is stored + in bindata array elements. To be able to produce proper + DER, it needs some contextual information (specifically, + the tag to prefix before the body). + """ return ''.join ( [ elem._der_pack () for elem in self ] ) def __str__ (self): @@ -308,12 +331,23 @@ def __init_bindata__ (self): subval = builder.build_asn1 (self._context, subrcp, subcrs, 0) self.add (subval) derblob = derblob [hlen+ilen:] - #TODO:GENERIC# self._bindata [self._offset] = self + self._bindata [self._offset] = self def _der_pack (self): """Return the result of the `der_pack()` operation on this element. """ + return primitive.der_prefixhead (DER_PACK_ENTER | DER_TAG_SET, + self._der_format ()) + + def _der_format (self): + """Format the current ASN1SetOf using DER notation, + but withhold the DER header consisting of the outer tag + and length. This format is comparable to what is stored + in bindata array elements. To be able to produce proper + DER, it needs some contextual information (specifically, + the tag to prefix before the body). + """ return ''.join ( [ elem._der_pack () for elem in self ] ) def __str__ (self): @@ -356,34 +390,43 @@ class ASN1Atom (ASN1Object): # with get() and set() methods to see and change it. The functions # mapped to interpret DER content and map it to a native type. _direct_data_map = { - DER_TAG_BOOLEAN: primitive.der_unpack_BOOLEAN, - DER_TAG_INTEGER: primitive.der_unpack_INTEGER, - DER_TAG_BITSTRING: primitive.der_unpack_BITSTRING, - DER_TAG_OCTETSTRING: primitive.der_unpack_STRING, + DER_TAG_BOOLEAN: primitive.der_parse_BOOLEAN, + DER_TAG_INTEGER: primitive.der_parse_INTEGER, + DER_TAG_BITSTRING: primitive.der_parse_BITSTRING, + DER_TAG_OCTETSTRING: primitive.der_parse_STRING, #DEFAULT# DER_TAG_NULL: ASN1Atom, - DER_TAG_OID: primitive.der_unpack_OID, + DER_TAG_OID: primitive.der_parse_OID, #DEFAULT# DER_TAG_OBJECT_DESCRIPTOR: ASN1Atom, #DEFAULT# DER_TAG_EXTERNAL: ASN1Atom, - DER_TAG_REAL: primitive.der_unpack_REAL, - DER_TAG_ENUMERATED: primitive.der_unpack_INTEGER, #TODO# der2enum??? + DER_TAG_REAL: primitive.der_parse_REAL, + DER_TAG_ENUMERATED: primitive.der_parse_INTEGER, #TODO# der2enum??? #DEFAULT# DER_TAG_EMBEDDED_PDV: ASN1Atom, - DER_TAG_UTF8STRING: primitive.der_unpack_STRING, - DER_TAG_RELATIVE_OID: primitive.der_unpack_RELATIVE_OID, - DER_TAG_NUMERICSTRING: primitive.der_unpack_STRING, - DER_TAG_PRINTABLESTRING: primitive.der_unpack_STRING, - DER_TAG_TELETEXSTRING: primitive.der_unpack_STRING, - DER_TAG_VIDEOTEXSTRING: primitive.der_unpack_STRING, - DER_TAG_IA5STRING: primitive.der_unpack_STRING, - DER_TAG_UTCTIME: primitive.der_unpack_UTCTIME, - DER_TAG_GENERALIZEDTIME: primitive.der_unpack_GENERALIZEDTIME, - DER_TAG_GRAPHICSTRING: primitive.der_unpack_STRING, - DER_TAG_VISIBLESTRING: primitive.der_unpack_STRING, - DER_TAG_GENERALSTRING: primitive.der_unpack_STRING, - DER_TAG_UNIVERSALSTRING: primitive.der_unpack_STRING, - DER_TAG_CHARACTERSTRING: primitive.der_unpack_STRING, - DER_TAG_BMPSTRING: primitive.der_unpack_STRING, + DER_TAG_UTF8STRING: primitive.der_parse_STRING, + DER_TAG_RELATIVE_OID: primitive.der_parse_RELATIVE_OID, + DER_TAG_NUMERICSTRING: primitive.der_parse_STRING, + DER_TAG_PRINTABLESTRING: primitive.der_parse_STRING, + DER_TAG_TELETEXSTRING: primitive.der_parse_STRING, + DER_TAG_VIDEOTEXSTRING: primitive.der_parse_STRING, + DER_TAG_IA5STRING: primitive.der_parse_STRING, + DER_TAG_UTCTIME: primitive.der_parse_UTCTIME, + DER_TAG_GENERALIZEDTIME: primitive.der_parse_GENERALIZEDTIME, + DER_TAG_GRAPHICSTRING: primitive.der_parse_STRING, + DER_TAG_VISIBLESTRING: primitive.der_parse_STRING, + DER_TAG_GENERALSTRING: primitive.der_parse_STRING, + DER_TAG_UNIVERSALSTRING: primitive.der_parse_STRING, + DER_TAG_CHARACTERSTRING: primitive.der_parse_STRING, + DER_TAG_BMPSTRING: primitive.der_parse_STRING, } + # The tags in the _direct_data_replace set are replaced in the + # _bindata [_offset] entry, so this is no longer a binary string + # but rather the current ASN1Atom subclass + _direct_data_replace = set ([ + DER_TAG_BOOLEAN, DER_TAG_BITSTRING, DER_TAG_OID, + DER_TAG_ENUMERATED, DER_TAG_RELATIVE_OID, + DER_TAG_UTCTIME, DER_TAG_GENERALIZEDTIME, + ]) + def __init_bindata__ (self): """The object has been setup with structural information in _der_packer and _recipe, as well as instance data in @@ -396,18 +439,22 @@ def __init_bindata__ (self): counting the overhead should be tolerable, though it may be avoided in a more clever approach. """ + self._value = self._bindata [self._offset] mytag = ord (self._der_packer [0]) & DER_PACK_MATCHBITS - if mytag in self._direct_data_map: + if self._direct_data_map.has_key (mytag): mapfun = self._direct_data_map [mytag] if self._bindata [self._offset] is None: - myrepr = None + pass # Keep the None value in _bindata [_offset] else: - myrepr = mapfun (self._bindata [self._offset]) + mapped_value = mapfun (self._value) + if mytag in self._direct_data_replace: + # Replace the _value + self._value = mapped_value + # Replace the _bindata [_offset] + self._bindata [self._offset] = self else: - myrepr = self - # Keep the binary string in _value; possibly change _bindata - self._value = self._bindata [self._offset] - self._bindata [self._offset] = myrepr + pass # Keep binary string in _bindata [_offset] + # # Keep binary string in _value: self is usable def get (self): return self._value @@ -427,14 +474,15 @@ def set (self, derblob): def __str__ (self): retval = self.get () if retval: - retval = "'" + self.get ().encode ('hex') + "'H" + retval = "'" + self._der_format ().encode ('hex') + "'H" else: retval = 'None' return retval def __len__ (self): if self._value: - return len (self._value) + # A bit wasteful to compute the DER format for len + return len (self._der_format ()) else: return 0 @@ -442,7 +490,17 @@ def _der_pack (self): """Return the result of the `der_pack()` operation on this element. """ - #TODO# insert the header! + return primitive.der_prefixhead (self._der_packer [0], + self._der_format ()) + + def _der_format (self): + """Format the current ASN1Atom using DER notation, + but withhold the DER header consisting of the outer tag + and length. This format is comparable to what is stored + in bindata array elements. To be able to produce proper + DER, it needs some contextual information (specifically, + the tag to prefix before the body). + """ return self._bindata [self._offset] @@ -456,6 +514,9 @@ def __str__ (self): else: return 'FALSE' + def _der_format (self): + return primitive.der_format_BOOLEAN (self.get ()) + class ASN1Integer (ASN1Atom): @@ -464,12 +525,12 @@ class ASN1Integer (ASN1Atom): def get (self): val = super (ASN1Integer,self).get () if val is not None: - val = primitive.der_unpack_INTEGER (val) + val = primitive.der_parse_INTEGER (val) return val def set (self, val): if val is not None: - val = primitive.der_pack_INTEGER (val) + val = primitive.der_format_INTEGER (val) super (ASN1Integer, self).set (val) def __int__ (self): @@ -478,6 +539,9 @@ def __int__ (self): def __str__ (self): return str (self.__int__ ()) + def _der_format (self): + return primitive.der_format_INTEGER (self.get ()) + class ASN1BitString (ASN1Atom): @@ -492,6 +556,9 @@ def set (self, bit): def clear (self, bit): self._bindata [self._offset].remove (bit) + def _der_format (self): + return primitive.der_format_BITSTRING (self.get ()) + class ASN1OctetString (ASN1Atom): @@ -511,19 +578,28 @@ class ASN1OID (ASN1Atom): _der_packer = chr(DER_PACK_STORE | DER_TAG_OID) + chr(DER_PACK_END) def __str__ (self): - oidstr = primitive.der_unpack_OID (self.get ()) + oidstr = primitive.der_parse_OID (self.get ()) return '{ ' + oidstr.replace ('.', ' ') + ' }' + def _der_format (self): + return primitive.der_format_OID (self.get ()) + class ASN1Real (ASN1Atom): _der_packer = chr(DER_PACK_STORE | DER_TAG_REAL) + chr(DER_PACK_END) + def _der_format (self): + return primitive.der_format_REAL (self.get ()) + class ASN1Enumerated (ASN1Atom): _der_packer = chr(DER_PACK_STORE | DER_TAG_ENUMERATED) + chr(DER_PACK_END) + def _der_format (self): + return primitive.der_format_INTEGER (self.get ()) + class ASN1UTF8String (ASN1Atom): @@ -542,6 +618,9 @@ class ASN1RelativeOID (ASN1Atom): _der_packer = chr(DER_PACK_STORE | DER_TAG_RELATIVE_OID) + chr(DER_PACK_END) + def _der_format (self): + return primitive.der_format_RELATIVEOID (self.get ()) + class ASN1NumericString (ASN1Atom): @@ -615,11 +694,14 @@ class ASN1UTCTime (ASN1Atom): def __str__ (self): retval = self.get () if retval: - retval = '"' + self.get () + '"' + retval = '"' + self._der_format () + '"' else: retval = 'None' return retval + def _der_format (self): + return primitive.der_format_UTCTIME (self.get ()) + class ASN1GeneralizedTime (ASN1Atom): @@ -628,11 +710,14 @@ class ASN1GeneralizedTime (ASN1Atom): def __str__ (self): retval = self.get () if retval: - retval = '"' + self.get () + '"' + retval = '"' + self._der_format () + '"' else: retval = 'None' return retval + def _der_format (self): + return primitive.der_format_GENERALIZEDTIME (self.get ()) + class ASN1GraphicString (ASN1Atom): @@ -722,4 +807,7 @@ class must be a subclass of ASN1Object. offset = 0, context = cls._context ) + def der_pack (self): + return format.der_pack (self._value, cls=self._class) + diff --git a/python/quick_der/format.py b/python/quick_der/format.py new file mode 100644 index 0000000..aac0fdb --- /dev/null +++ b/python/quick_der/format.py @@ -0,0 +1,139 @@ +# format.py -- Taking the turn from DER to native, format as DER +# +# Terminology: +# * der_pack() and der_unpack() work against a _der_packer syntax +# * der_format() and der_parse() only look at the body (and needs hints) +# +# For primitive operations, such as on INTEGER and BOOLEAN, see primitive.py + + +import time + +import classes as c +import primitive as p +import packstx as t + +import _quickder + + +# +# Various mappings from hint names (with some aliases) to a packer function +# + +# Report an error, stating that a hint is required for the map at hand +def _hintmap_needs_hint (value): + raise Exception ('To pack ' + str(type(value)) + ', please provide a hint') + +_hintmap_int = { + 'no_hint': (t.DER_TAG_INTEGER, p.der_format_INTEGER), + 'INTEGER': (t.DER_TAG_INTEGER, p.der_format_INTEGER), + 'BITSTRING': (t.DER_TAG_BITSTRING, p.der_format_BITSTRING), +} + +_hintmap_unicode = { + 'no_hint': (None, _hintmap_needs_hint), + 'UTF8': (t.DER_TAG_UTF8STRING, p.der_format_STRING), + 'OCTET': (t.DER_TAG_OCTETSTRING, p.der_format_STRING), + 'GENERALIZED': (t.DER_TAG_GENERALSTRING, p.der_format_STRING), + 'GENERAL': (t.DER_TAG_GENERALSTRING, p.der_format_STRING), +} + +_hintmap_str = { + 'no_hint': (None, _hintmap_needs_hint), + 'IA5': (t.DER_TAG_IA5STRING, p.der_format_STRING), + 'ASCII': (t.DER_TAG_IA5STRING, p.der_format_STRING), + 'OCTET': (t.DER_TAG_OCTETSTRING, p.der_format_STRING), + 'GENERALIZED': (t.DER_TAG_GENERALSTRING, p.der_format_STRING), + 'GENERAL': (t.DER_TAG_GENERALSTRING, p.der_format_STRING), + 'PRINTABLE': (t.DER_TAG_PRINTABLESTRING, p.der_format_STRING), + 'OID': (t.DER_TAG_OID, p.der_format_OID), + 'RELATIVE_OID': (t.DER_TAG_RELATIVEOID, p.der_format_RELATIVE_OID), + 'RELATIVEOID': (t.DER_TAG_RELATIVEOID, p.der_format_RELATIVE_OID), +} + +_hintmap_time = { + 'no_hint': (t.DER_TAG_GENERALIZEDTIME, p.der_format_GENERALIZEDTIME), + 'GENERAL': (t.DER_TAG_GENERALIZEDTIME, p.der_format_GENERALIZEDTIME), + 'GENERALIZED': (t.DER_TAG_GENERALIZEDTIME, p.der_format_GENERALIZEDTIME), + 'UTC': (t.DER_TAG_UTCTIME, p.der_format_UTCTIME), + 'GMT': (t.DER_TAG_UTCTIME, p.der_format_UTCTIME), +} + + +###TODO### hintmaps' (a,b) have tag a implies formatter b, use b = a2b [a] ??? +###TODO### that also delivers the parser c, through c = a2c [a] + + +# Based on a value's type and a possible added hint, find a pair of +# - a value packing function +# - a suitable tag for a prefixed head +# The tuple found in hints maps or constructed locally is returned as-is. +def _der_hintmapping (tp, hint='no_hint'): + if tp == int or tp == long: + return _hintmap_int [hint] + elif tp == unicode: + return _hintmap_unicode [hint] + elif tp == str: + return _hintmap_string [hint] + elif tp == bool: + return (t.DER_TAG_BOOLEAN, p.der_format_BOOLEAN) + elif tp == time.struct_time: + return _hintmap_time [hint] + elif tp == float: + return (t.DER_TAG_REAL, p.der_format_REAL) + elif issubclass (tp, c.ASN1Object): + return (tp._der_packer [0], lambda v: v._der_format ()) + else: + raise Exception ('Not able to map ' + str(tp) + ' value to DER') + + +def der_pack (value, hint='no_hint', der_packer=None, cls=None): + """Pack a Python value into a binary string holding a DER blob; see + der_format() for a version that does not include the DER header. + Since native types may have been produced from a variety of + ASN.1 types, the hint can be used to signal the type name + (though any STRING trailer is stripped). Alternatively, + use cls to provide an ASN1Object subclass for packing or + a der_packer instruction to apply. The latter could crash + if not properly formatted -- at least make sure its DER_ENTER_ + and DER_LEAVE nest properly and that the der_packer ends in + DER_PACK_END. + """ + if der_packer is not None: + if not type (value) == list: + value = [value] + if cls is not None: + raise Exception ('der_pack(...,der_packer=...,cls=...) is ambiguous') + if hint != 'no_hint': + raise Exception ('der_pack(...,der_packer=...,hint=...) is ambiguous') + return _quickder.der_pack (der_packer, value) + elif cls is not None: + if not issubclass (cls,c.ASN1Obejct): + raise Exception ('der_pack(value,cls=...) requires cls to be a subclass of ASN1Object') + if not type (value) == list: + value = [value] + if hint != 'no_hint': + raise Exception ('der_pack(...,cls=...,hint=...) is ambiguous') + return _quickder.der_pack (cls._der_packer, value) + elif isinstance (value, c.ASN1Object): + return value._der_pack () + else: + (tag,packfun) = _der_hintmapping (type(value), hint) + return p.der_prefixhead (tag, packfun (value)) + +#TODO# der_unpack() -- is useful + + +def der_format (value, hint='no_hint'): + """Pack a Python value into a binary string holding the contents of + a DER blob, but not the surrounding DER header. See der_pack(). + Since native types may have been produced from a variety of + ASN.1 types, the hint can be used to signal the type name + (though any STRING trailer is stripped). + """ + (_,packfun) = _der_hintmapping (type(value), hint) + return packfun (value) + +#TODO# der_parse() -- would it be useful ??? + + diff --git a/python/quick_der/primitive.py b/python/quick_der/primitive.py index 8e625a6..8569d84 100644 --- a/python/quick_der/primitive.py +++ b/python/quick_der/primitive.py @@ -1,4 +1,10 @@ -# primitive.py -- der_pack_TYPE and der_unpack_TYPE for primitive content +# primitive.py -- der_format_TYPE and der_parse_TYPE for primitive content +# +# Terminology: +# * der_pack() and der_unpack() work against a _der_packer syntax +# * der_format() and der_parse() only look at the body (and needs hints) +# +# For more complex operations, such as on classes, see format.py import _quickder @@ -13,20 +19,41 @@ intern = lambda s: s +# +# Utility functions +# + + +# Prefix a DER header (tag and length) to a body +# This is a currying function, used as: _der_prefix_head_fn (tag) (body) +def der_prefixhead (tag, body): + blen = len (body) + if blen == 0: + lenh = chr (0) + elif blen <= 127: + lenh = chr (blen) + else: + lenh = '' + while blen > 0: + lenh = chr (blen % 256) + lenh + blen >>= 8 + lenh = chr (0x80 + len (lenh)) + return chr (tag) + lenh + body + + # # Mappings for primitive DER elements that map to native Python objects # -def der_pack_STRING (sval): +def der_format_STRING (sval): return sval - -def der_unpack_STRING (derblob): +def der_parse_STRING (derblob): return derblob -def der_pack_OID (oidstr, hdr=False): +def der_format_OID (oidstr, hdr=False): oidvals = map (int, oidstr.split ('.')) oidvals [1] += 40 * oidvals [0] enc = '' @@ -41,7 +68,7 @@ def der_pack_OID (oidstr, hdr=False): return enc -def der_unpack_OID (derblob): +def der_parse_OID (derblob): oidvals = [0] for byte in map (ord, derblob): if byte & 0x80 != 0x00: @@ -56,63 +83,52 @@ def der_unpack_OID (derblob): return intern (retval) -def der_pack_RELATIVE_OID (oidstr): - raise NotImplementedError ('der_pack_RELATIVE_OID') +def der_format_RELATIVE_OID (oidstr): + raise NotImplementedError ('der_format_RELATIVE_OID') -def der_unpack_RELATIVE_OID (oidstr): - raise NotImplementedError ('der_unpack_RELATIVE_OID') +def der_parse_RELATIVE_OID (oidstr): + raise NotImplementedError ('der_parse_RELATIVE_OID') -def der_pack_BITSTRING (bitset): - bits = [0] - for bit in bitset: - byte = 1 + (bit >> 3) - if len (bits) < byte + 1: - bits = bits + [0] * (byte + 1 - len (bits)) - bits [byte] |= (1 << (bit & 0x07)) - return ''.join (map (chr,bits)) +def der_format_BITSTRING (bitint): + return chr (0) + der_format_INTEGER (bitint) -def der_unpack_BITSTRING (derblob): - #TODO# Consider support of constructed BIT STRING types - assert len (derblob) >= 1, 'Empty BIT STRING values cannot occur in DER' - assert ord (derblob [0]) <= 7, 'BIT STRING values must have a first byte up to 7' - bitnum = 8 * len (derblob) - 8 - ord (derblob [0]) - bitset = set () - for bit in range (bitnum): - if ord (derblob [(bit >> 3) + 1]) & (1 << (bit & 0x07)) != 0: - bitset.add (bit) - return bitset +def der_parse_BITSTRING (derblob): + assert len (derblob) > 0, 'BITSTRING elements cannot be empty' + if ord (derblob [0]) != 0: + raise NotImplementedError ('BISTRING with more than 0 trailing bits are not implemented') + return der_parse_INTEGER (derblob [1:]) -def der_pack_UTCTIME (tstamp): - return time.strftime (pstamp, '%y%m%d%H%M%SZ') +def der_format_UTCTIME (tstamp): + return time.strftime ('%y%m%d%H%M%SZ', tstamp) -def der_unpack_UTCTIME (derblob): +def der_parse_UTCTIME (derblob): return time.strptime (derblob, '%y%m%d%H%M%SZ') -def der_pack_GENERALIZEDTIME (tstamp): +def der_format_GENERALIZEDTIME (tstamp): #TODO# No support for fractional seconds - return time.strftime (tstamp, '%Y%m%d%H%M%SZ') + return time.strftime ('%Y%m%d%H%M%SZ', tstamp) -def der_unpack_GENERALIZEDTIME (derblob): +def der_parse_GENERALIZEDTIME (derblob): #TODO# No support for fractional seconds return time.strptime (derblob, '%Y%m%d%H%M%SZ') -def der_pack_BOOLEAN (bval): +def der_format_BOOLEAN (bval): return '\xff' if bval else '\x00' -def der_unpack_BOOLEAN (derblob): +def der_parse_BOOLEAN (derblob): return derblob != '\x00' * len (derblob) -def der_pack_INTEGER (ival, hdr=False): +def der_format_INTEGER (ival, hdr=False): retval = '' while ival not in [0,-1]: byt = ival & 0xff @@ -129,7 +145,7 @@ def der_pack_INTEGER (ival, hdr=False): return retval -def der_unpack_INTEGER (derblob): +def der_parse_INTEGER (derblob): if derblob == '': return 0 retval = 0 @@ -140,16 +156,17 @@ def der_unpack_INTEGER (derblob): return retval -def der_pack_REAL (rval): - raise NotImplementedError ('der_pack_REAL -- too many variations') +def der_format_REAL (rval): + raise NotImplementedError ('der_format_REAL -- too many variations') # See X.690 section 8.5 -- base2, base10, ... yikes! if rval == 0.0: return '' else: pass -def der_unpack_REAL (): - raise NotImplementedError ('der_pack_REAL -- too many variations') +def der_parse_REAL (): + raise NotImplementedError ('der_parse_REAL -- too many variations') # See X.690 section 8.5 -- base2, base10, ... yikes! + diff --git a/python/setup.py.in b/python/setup.py.in index 5e574c7..0c9e4b7 100755 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -1,6 +1,6 @@ #!/usr/bin/env python -from distutils.core import setup, Extension +from setuptools import setup, Extension sharedlib = Extension ( "_quickder", diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8378d72..98b5ac9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,7 +14,7 @@ add_test(certio-py-test add_test(empty-py-test python ${CMAKE_CURRENT_SOURCE_DIR}/empty-instance.py) add_test(pack-py-test - python ${CMAKE_CURRENT_SOURCE_DIR}/der_pack.py) + python ${CMAKE_CURRENT_SOURCE_DIR}/der_format.py) add_executable (ldap.test ldapsearch.c) diff --git a/test/certio.py b/test/certio.py index 40619ad..619815a 100755 --- a/test/certio.py +++ b/test/certio.py @@ -8,14 +8,26 @@ # Normal programming uses the package name, but in the build tree we don't # from quick_der.rfc5280 import Certificate from quick_der.rfc5280 import Certificate +from quick_der.format import der_pack -der = open (sys.argv [1]).read () -crt = Certificate (derblob=der) +der_in = open (sys.argv [1]).read () +crt = Certificate (derblob=der_in) print 'WHOLE CERT:' print crt print +der_out = der_pack (crt) +if der_out != der_in: + print 'DIFFERENT DER BLOBS IN AND OUT!!!' + print 'der_in =', der_in [:30].encode ('hex'), '...' + print 'der_out =', der_out[:30].encode ('hex'), '...' + of = open ('/tmp/verisign.out', 'w') + of.write (der_out) + of.close () + print 'der_out written to /tmp/verisign.out -- perhaps compare with derdump' + sys.exit (1) + print 'TBSCERTIFICATE:' print 'type is', type (crt.tbsCertificate) print diff --git a/test/der_pack.py b/test/der_format.py similarity index 63% rename from test/der_pack.py rename to test/der_format.py index b431c52..8a1895e 100644 --- a/test/der_pack.py +++ b/test/der_format.py @@ -25,10 +25,10 @@ (BOOL, False), (BOOL, 0), (BOOL, 1), - (BIT, set([1,5,7])), - (BIT, set()), - (BIT, set([22])), - (BIT, set([100, 101])), + (BIT, 162), + (BIT, 0), + (BIT, 4194304), + (BIT, 3802951800684688204490109616128L), (STR, "cow"), (STR, ""), (STR, chr(0) + "cow"), @@ -36,12 +36,12 @@ (OID, "1.2.3.4"), (OID, "3.14.159.2653.58979.323812"), ): - pack_func = getattr(qd, "der_pack_" + typename) - unpack_func = getattr(qd, "der_unpack_" + typename) + format_func = getattr(qd, "der_format_" + typename) + parse_func = getattr(qd, "der_parse_" + typename) - assert pack_func is not None - assert unpack_func is not None - assert callable(pack_func) - assert callable(unpack_func) + assert format_func is not None + assert parse_func is not None + assert callable(format_func) + assert callable(parse_func) - assert unpack_func(pack_func(value)) == value + assert parse_func(format_func(value)) == value, "Value " + str (value) + " :: " + str (typename) + " is not properly reproduced by parse . format" diff --git a/tool/asn2quickder.py b/tool/asn2quickder.py index e2c5837..4bd9ccf 100755 --- a/tool/asn2quickder.py +++ b/tool/asn2quickder.py @@ -812,26 +812,26 @@ def pygenValueAssignment (self, node): val = self.pyvalOID (node.value) cls = api_prefix + '.ASN1OID' else: - val = 'MAP2DER("""' + str (node.value) + '""")' + val = 'UNDEF_MAP2DER("""' + str (node.value) + '""")' self.comment (str (node)) # Must provide a context for name resolution, even {} will do - self.writeln (var + ' = ' + cls + ' (' + val + ', context={})') + self.writeln (var + ' = ' + cls + ' (bindata=[' + val + '], context={})') self.writeln () def pyvalInteger (self, valnode): - return api_prefix + '.der_pack_INTEGER (' + str (int (valnode)) + ', hdr=True)' + return api_prefix + '.der_format_INTEGER (' + str (int (valnode)) + ')' def pyvalOID (self, valnode): retc = [] for oidcompo in valnode.components: if type (oidcompo) == NameForm: - retc.append (api_prefix + '.der_unpack_OID (' + tosym (oidcompo.name) + '.get())') + retc.append (api_prefix + '.der_parse_OID (' + tosym (oidcompo.name) + '.get())') elif type (oidcompo) == NumberForm: retc.append ("'" + str (oidcompo.value) + "'") elif type (oidcompo) == NameAndNumberForm: retc.append ("'" + str (oidcompo.number) + "'") retval = " + '.' + ".join (retc) - retval = api_prefix + '.der_pack_OID (' + retval.replace ("' + '", '') + ', hdr=True)' + retval = api_prefix + '.der_format_OID (' + retval.replace ("' + '", '') + ')' return retval def generate_classes (self):