diff options
Diffstat (limited to 'pow/POW/_der.py')
-rw-r--r-- | pow/POW/_der.py | 2294 |
1 files changed, 2294 insertions, 0 deletions
diff --git a/pow/POW/_der.py b/pow/POW/_der.py new file mode 100644 index 00000000..c7f58411 --- /dev/null +++ b/pow/POW/_der.py @@ -0,0 +1,2294 @@ +#*****************************************************************************# +#* *# +#* Copyright (c) 2002, Peter Shannon *# +#* All rights reserved. *# +#* *# +#* Redistribution and use in source and binary forms, with or without *# +#* modification, are permitted provided that the following conditions *# +#* are met: *# +#* *# +#* * Redistributions of source code must retain the above *# +#* copyright notice, this list of conditions and the following *# +#* disclaimer. *# +#* *# +#* * Redistributions in binary form must reproduce the above *# +#* copyright notice, this list of conditions and the following *# +#* disclaimer in the documentation and/or other materials *# +#* provided with the distribution. *# +#* *# +#* * The name of the contributors may be used to endorse or promote *# +#* products derived from this software without specific prior *# +#* written permission. *# +#* *# +#* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *# +#* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT *# +#* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS *# +#* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS *# +#* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, *# +#* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *# +#* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, *# +#* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY *# +#* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *# +#* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE *# +#* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *# +#* *# +#*****************************************************************************# + +import exceptions, types, copy, string, time, base64, traceback, cStringIO + +DEBUG = 0 + +# CLASS +CLASS_UNIVERSAL = 0x00 +CLASS_APPLICATION = 0x40 +CLASS_CONTEXT = 0x80 +CLASS_PRIVATE = 0xC0 + +# FORM +FORM_PRIMITIVE = 0x00 +FORM_CONSTRUCTED = 0x20 + +# TAG +TAG_BOOLEAN = 0x01 +TAG_INTEGER = 0x02 +TAG_BITSTRING = 0x03 +TAG_OCTETSTRING = 0x04 +TAG_NULL = 0x05 +TAG_OID = 0x06 +TAG_OBJDESCRIPTOR = 0x07 +TAG_EXTERNAL = 0x08 +TAG_REAL = 0x09 +TAG_ENUMERATED = 0x0A +TAG_EMBEDDED_PDV = 0x0B +TAG_UTF8STRING = 0x0C +TAG_SEQUENCE = 0x10 +TAG_SET = 0x11 +TAG_NUMERICSTRING = 0x12 +TAG_PRINTABLESTRING = 0x13 +TAG_T61STRING = 0x14 +TAG_VIDEOTEXSTRING = 0x15 +TAG_IA5STRING = 0x16 +TAG_UTCTIME = 0x17 +TAG_GENERALIZEDTIME = 0x18 +TAG_GRAPHICSTRING = 0x19 +TAG_VISIBLESTRING = 0x1A +TAG_GENERALSTRING = 0x1B +TAG_UNIVERSALSTRING = 0x1C +TAG_BMPSTRING = 0x1E + +_fragments = [] + +def _docset(): + return _fragments + +def _addFragment(frag): + global _fragments + _fragments.append(frag) + + +_addFragment(''' +<moduleDescription> + <header> + <name>POW.pkix</name> + <author>Peter Shannon</author> + </header> + <body> + <para> + This module is a solution to reading and writing X509v3 written + purely in Python. It does use limited facilities from POW for + signing and verifying but these could be replaced easily. It is + an abstract module and to use it successfully RFC3280 should be + referred to as well as the sourcecode where necessary. The correct + use of many extensions often not clear from the definitions alone. + Do refer to the RFC for details. + </para> + <para> + Each constructed objects defined in the RFC is built from primitives + defined by the ASN1 recommedations. Not all ASN1 primitive are available but all those + required for X509v3 should be. The implementation is more or less + complete for DER encoding the only caveat, aside from a few + missing objects, is the behaviour of <classname>SET</classname> objects + and <classname>SET OF</classname> objects. The order the objects are + written in should be determined at runtime by sorting their tags but this + library does not do this. For X509 it isn't really necessary + since all the <classname>Set</classname> objects are simple and the + order they are written in is defined by the object's constructor. + </para> + <para> + Every documented object in this module supports the functions documented for + <classname>_GeneralObject</classname>. In general the function + will only be documented in descendant classes if the class changes + the behaviour significantly from its ancestor. This would + normally be <classname>_GeneralObject</classname> or + <classname>Sequence</classname>. + </para> + </body> +</moduleDescription> +''') + +class DerError(Exception): + def __init__(self, msg): + if not isinstance(msg, types.StringType): + raise Exception, 'argunment should be a string' + self.msg = msg + + def __repr__(self): + return self.msg + + __str__ = __repr__ + +class _Tag(object): + def __init__(self): + self.tagclass = 0 + self.tagform = 0 + self.tagnumber = 0 + + def __repr__(self): + return '(%s, %s, %s)' % (self.tagclass, self.tagform, self.tagnumber) + + def write(self, file): + if self.tagnumber < 31: + file.write( chr(self.tagclass | self.tagform | self.tagnumber) ) + else: + val = copy.deepcopy(self.tagnumber) + bytes = [] + while val: + byte = val & 0x7F + bytes.append(byte | 0x80) + val = val >> 7 + bytes[0] = bytes[0] ^ 0x80 + bytes.append( self.tagclass | self.tagform | 0x1F ) + bytes.reverse() + file.write( string.join(map(chr, bytes), '') ) + + def read(self, file): + octet1 = ord( file.read(1) ) + self.tagclass = octet1 & 0xC0 + self.tagform = octet1 & 0x20 + value = octet1 & 0x1F + if value < 31: + self.tagnumber = value + else: + total = 0 + byte = 0x80 + while byte & 0x80: + byte = ord( file.read(1) ) + if byte & 0x80: + total = (total << 7) | byte ^ 0x80 + else: + total = (total << 7) | byte + self.tagnumber = total + +class _Length(object): + def __init__(self): + self.length = 0 + + def __repr__(self): + return '(%s)' % self.length + + def write(self, file): + if self.length < 128: + file.write( chr(self.length) ) + else: + val = copy.deepcopy(self.length) + bytes = [] + while val: + byte = val & 0xFF + bytes.append(byte) + val = val >> 8 + lengthOfLength = len(bytes) + if lengthOfLength > 126: + raise DerError, 'object is too long!' + bytes.append(lengthOfLength) + bytes.reverse() + bytes[0] = bytes[0] ^ 0x80 + file.write( string.join(map(chr, bytes), '') ) + + def read(self, file): + octet1 = ord( file.read(1) ) + if octet1 < 128: + self.length = octet1 + else: + total = 0 + byte = 0 + for i in range(octet1 ^ 0x80): + byte = ord( file.read(1) ) + total = (total << 8) | byte + self.length = total + +class _TlvIo(_Tag, _Length): + def __init__(self, file): + self.file = file + self.offset = None + self.valueOffset = None + + def __repr__(self): + return '<TAG:%s Length:%s>' % (_Tag.__repr__(self), _Length.__repr__(self)) + + def __nonzero__(self): + pos = self.file.tell() + self.file.seek(0,2) + if self.file.tell(): + self.file.seek(pos) + return 1 + else: + return 0 + + def read(self): + self.offset = self.file.tell() + _Tag.read( self, self.file ) + _Length.read( self, self.file ) + self.valueOffset = self.file.tell() + self.file.seek( self.length, 1 ) + + def readValue(self): + self.file.seek( self.valueOffset ) + return self.file.read( self.length ) + + def write(self, val): + _Tag.write( self, self.file ) + self.length = len(val) + _Length.write( self, self.file ) + self.file.write(val) + +def _decodeBoolean(val): + 'der encoded value not including tag or length' + if not isinstance(val, types.StringType): + raise DerError, 'argument should be a string' + if ord(val) == 0xFF: + return 1 + elif ord(val) == 0x00: + return 0 + else: + raise DerError, 'boolean should be encode as all 1s or all 0s' + +def _encodeBoolean(val): + 'anything we can test for truth' + if val: + return chr(0xFF) + else: + return chr(0x00) + +def _decodeInteger(val): + 'der encoded value not including tag or length' + if not isinstance(val, types.StringType): + raise DerError, 'argument should be a string' + total = 0L + if ord(val[0]) & 0x80: + val = map( lambda x : ord(x) ^ 0xFF, val ) + for byte in val: + total = (total << 8) | byte + total = -(total+1) + else: + for byte in val: + total = (total << 8) | ord(byte) + return total + +def _encodeInteger(val): + 'python integer' + if not isinstance(val, types.IntType) and not isinstance(val, types.LongType): + raise DerError, 'argument should be an integer' + if val == 0: + return chr(0x00) + else: + val2 = copy.deepcopy(val) + if val2 < 0: + val2 = -(val2+1) + bytes = [] + byte = 0 + while val2: + byte = val2 & 0xFF + bytes.append(byte) + val2 = val2 >> 8 + # if we have no used up the last byte to represent the value we need + # to add one more on to show if this is negative of positive. Also, + # due to adding 1 and inverting -1 would be 0 or if 0 is the encoding + # value, so bytes would empty and this would lead to and empty value + # and this would not be working properly. Adding this null byte + # fixes this, since it is inverted to -1 and preserved for 0. + if byte & 0x80 or not bytes: + bytes.append(0x00) + if val < 0: + bytes = map( lambda x : x ^ 0xFF, bytes ) + bytes.reverse() + + return string.join(map(chr, bytes), '') + +def _decodeBitString(val): + 'der encoded value not including tag or length' + if not isinstance(val, types.StringType): + raise DerError, 'argument should be a string' + bitmasks = [0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01] + unused = ord( val[0] ) + bits = [] + for byte in val[1:]: + for j in range(8): + if ord(byte) & bitmasks[j]: + bits.append(1) + else: + bits.append(0) + if unused == 0: + return tuple(bits) + else: + return tuple(bits[:-unused]) + +def _encodeBitString(val): + 'list of true/false objects ie [0,1,1,0,1,1]' + if not (isinstance(val, types.ListType) or isinstance(val, types.TupleType)): + raise DerError, 'argument should be a list or tuple' + bitmasks = [0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01] + bytes = [] + fits, leftover = divmod(len(val), 8) + nobytes = fits + if leftover > 0: + nobytes = nobytes + 1 + if leftover: + unused = 8 - leftover + else: + unused = 0 + bytes.append(unused) + for i in range(nobytes): + byte = 0 + for j in range(8): + offset = j + i*8 + if offset < len(val): + if val[offset]: + byte = byte | bitmasks[j] + bytes.append(byte) + + return string.join(map(chr, bytes), '') + +def _decodeOid(val): + 'der encoded value not including tag or length' + if not isinstance(val, types.StringType): + raise DerError, 'argument should be a string' + arc12 = ord( val[0] ) + arc1, arc2 = divmod(arc12, 40) + oids = [arc1,arc2] + + total = 0 + for byte in val[1:]: + val = ord(byte) + if val & 0x80: + total = (total << 7) | (val ^ 0x80) + else: + total = (total << 7) | val + oids.append(total) + total = 0 + + return tuple(oids) + +def _encodeOid(val): + 'list of intgers' + if not (isinstance(val, types.ListType) or isinstance(val, types.TupleType)): + raise DerError, 'argument should be a list or tuple' + oids = [] + oids.append( chr(40 * val[0] + val[1]) ) + for val in val[2:]: + if val == 0: + oids.append( chr(0) ) + else: + bytes = [] + while val: + val, rem = divmod(val, 128) + bytes.append(rem | 0x80) + bytes[0] = bytes[0] ^ 0x80 + bytes.reverse() + oids.append( string.join(map(chr, bytes), '') ) + + return string.join(oids, '') + +def _decodeSequence(val): + 'der encoded value not including tag or length' + if not isinstance(val, types.StringType): + raise DerError, 'argument should be a string' + buf = cStringIO.StringIO(val) + buflen = len(val) + tvls = [] + while buf.tell() < buflen: + t = _TlvIo(buf) + t.read() + tvls.append(t) + return tuple(tvls) + +def _encodeSequence(val): + 'list of GenerlObjects' + if not (isinstance(val, types.ListType) or isinstance(val, types.TupleType)): + raise DerError, 'argument should be a list or tuple' + buf = cStringIO.StringIO() + for obj in val: + if obj or isinstance(obj, _GeneralObject): + obj.write(buf) + elif not obj.optional: + raise DerError, 'object not set which should be: %s' % obj + + return buf.getvalue() + +_addFragment(''' +<class> + <header> + <name>_GeneralObject</name> + </header> + <body> + <para> + <classname>_GeneralObject</classname> is the basis for all DER objects, + primitive or constructed. It defines the basic behaviour of an + object which is serialised using the tag, length and value + approach of DER. It is unlikely you would ever want to + instantiate one of these directly but I include a description + since many primatives don't override much of + <classname>_GeneralObject</classname>'s functions. + </para> + </body> +</class> +''') + +class _GeneralObject(object): + + _addFragment(''' + <constructor> + <header> + <memberof>_GeneralObject</memberof> + <parameter>normclass</parameter> + <parameter>normform</parameter> + <parameter>normnumber</parameter> + <parameter>encRoutine</parameter> + <parameter>decRoutine</parameter> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + <body> + <para> + <parameter>normclass</parameter> is the class of the object, + ei: universal, application, context or private. + <parameter>normform</parameter> is the form of the object, ei + primitive or constructed. <parameter>normnumber</parameter> is + the tag number of the object. + <parameter>encRoutine</parameter> is a function which takes a + value and encodes it according the appropriate DER rules. + <parameter>decRoutine</parameter> is a function which reads a + string value and returns a value which is more useful in + Python. <parameter>optional</parameter> is a boolean + indicating if this object is optional. The final parameter, + <parameter>default</parameter> is the base 64 encoded DER + value, which should be used as the default in leu of a value to + read or incase it is unset. + </para> + </body> + </constructor> + ''') + + def __init__(self, normclass, normform, normnumber, encRoutine, decRoutine, optional=0, default=''): + if not isinstance(normclass, types.IntType): + raise DerError, 'nomrclass argument should be an integer : %s' % normclass + if not isinstance(normform, types.IntType): + raise DerError, 'normform argument should be an integer : %s' % normform + if not isinstance(normnumber, types.IntType): + raise DerError, 'normnumber argument should be an integer : %s' % normnumber + if not isinstance(encRoutine, types.FunctionType): + raise DerError, 'encRoutine argument should be an function : %s' % encRoutine + if not isinstance(decRoutine, types.FunctionType): + raise DerError, 'decRoutine argument should be an function : %s' % decRoutine + if not isinstance(optional, types.IntType): + raise DerError, 'optional argument should be an integer : %s' % optional + if not isinstance(default, types.StringType): + raise DerError, 'default argument should be an String : %s' % default + self.normclass = normclass + self.normform = normform + self.normnumber = normnumber + self.encRoutine = encRoutine + self.decRoutine = decRoutine + self.value = None + self.optional = optional + self.default = default + self.reset() + + def _ioSafe(self): + 'is it safe to write this object' + if self.optional or self._isSet(): + return 1 + else: + return 0 + + def _isSet(self): + 'are the values of this object set or not' + if self.value is not None: + return 1 + else: + return 0 + + _addFragment(''' + <method> + <header> + <memberof>_GeneralObject</memberof> + <name>reset</name> + </header> + <body> + <para> + This function re-initialises the object, clearing the value or + setting it to any default. + </para> + </body> + </method> + ''') + def reset(self): + self.value = None + if self.default: + buf = cStringIO.StringIO( base64.decodestring( self.default ) ) + io = _TlvIo(buf) + io.read() + self.read(io) + + _addFragment(''' + <method> + <header> + <memberof>_GeneralObject</memberof> + <name>set</name> + <parameter>value</parameter> + </header> + <body> + <para> + This dosn't do much except store <parameter>value</parameter>, + presumably prior to writing the object. The correct values to + use would be determined by the encoder or decoder this class is + instantiated with. Be careful, there is some flexibility in + setting objects so you might find that once the object has been + written and read back in the value isn't identical. A good + example would be anything which contains a sequence(list or + tuple), all sequence objects are returned as tuples. + </para> + </body> + </method> + ''') + def set(self, value): + if value is not None: + self.value = value + + _addFragment(''' + <method> + <header> + <memberof>_GeneralObject</memberof> + <name>get</name> + </header> + <body> + <para> + Gets the value stored presumably after reading the object. + </para> + </body> + </method> + ''') + def get(self): + return self.value + + _addFragment(''' + <method> + <header> + <memberof>_GeneralObject</memberof> + <name>implied</name> + <parameter>impclass</parameter> + <parameter>impform</parameter> + <parameter>impnumber</parameter> + </header> + <body> + <para> + This function is used to change how the tag is written or read + for a particular object and should be called in the constructor + for derived objects. If you have an example of the structure you need to + process, Pete Gutmann's excellent + <application>dumpasn1</application> can be invaluable for + debugging objects. + </para> + </body> + </method> + ''') + def implied(self, impclass, impform, impnumber): + if not isinstance(impclass, types.IntType): + raise DerError, 'impclass argument should be an integer' + if not isinstance(impform, types.IntType): + raise DerError, 'impform argument should be an integer' + if not isinstance(impnumber, types.IntType): + raise DerError, 'impnumber argument should be an integer' + self.normclass = impclass + self.normform = impform + self.normnumber = impnumber + + _addFragment(''' + <method> + <header> + <memberof>_GeneralObject</memberof> + <name>read</name> + <parameter>io</parameter> + </header> + <body> + <para> + <parameter>io</parameter> should be a file like object. If the + object being read matches the expected class, form and tag the + value is read and decoded using + <function>decRoutine</function>. Else, if it has a default + that is read and stored. + </para> + <para> + The return value of this function does not indicate success but + whether this TLV was processed successfully. This bahaviour is + vital for processing constructed types since the object may be + optional or have a default. Failure to decode would be indicated + by an exception. + </para> + </body> + </method> + ''') + + def read(self, io=None): + + processDefOpt = 0 + if io is None: + processDefOpt = 1 + elif isinstance(io, _TlvIo): + if not io: + processDefOpt = 1 + else: + pos = io.tell() + io.seek(0,2) + if io.tell(): + io.seek(pos) + else: + processDefOpt = 1 + + if processDefOpt: + if self.optional or self.default: + self.reset() + return 0 + else: + raise DerError, 'no TLV is available to read in non-optional/non-default object: %s' % repr(self) + + if not isinstance(io, _TlvIo): + tmp = _TlvIo(io) + tmp.read() + io = tmp + + if io.tagclass != self.normclass or io.tagform != self.normform or io.tagnumber != self.normnumber: + if self.default or self.optional: + self.reset() + return 0 + else: + raise DerError, 'error in encoding, missing object:%s' % repr(self) + else: + derval = io.readValue() + self.value = self.decRoutine( derval ) + return 1 + + _addFragment(''' + <method> + <header> + <memberof>_GeneralObject</memberof> + <name>write</name> + <parameter>io</parameter> + </header> + <body> + <para> + If this object has not been set and is not optional and dosn't + have a default, a <classname>DerError</classname> exception will be raised + </para> + <para> + If no value has been set and this object is optional, nothing + is written. If this object's value is equal to the default, + nothing is written as stipulated by DER. Otherwise the value + is encoded and written. + </para> + </body> + </method> + ''') + + def write(self, file): + if not self._ioSafe(): + raise DerError, 'object not set which must be: %s' % repr(self) + elif self.optional and self.value is None: + pass + else: + buf = cStringIO.StringIO() + io = _TlvIo(buf) + io.tagclass = self.normclass + io.tagform = self.normform + io.tagnumber = self.normnumber + derval = self.encRoutine( self.value ) + io.length = len(derval) + io.write(derval) + if self.default: + if buf.getvalue() != base64.decodestring(self.default): + file.write( buf.getvalue() ) + else: + file.write( buf.getvalue() ) + + _addFragment(''' + <method> + <header> + <memberof>_GeneralObject</memberof> + <name>toString</name> + </header> + <body> + <para> + Encodes the value in DER and returns it as a string. + </para> + </body> + </method> + ''') + + def toString(self): + buf = cStringIO.StringIO() + self.write(buf) + return buf.getvalue() + + _addFragment(''' + <method> + <header> + <memberof>_GeneralObject</memberof> + <name>fromString</name> + </header> + <body> + <para> + Decodes the string and sets the value of this object. + </para> + </body> + </method> + ''') + + def fromString(self, value): + buf = cStringIO.StringIO(value) + self.read(buf) + +class Any(_GeneralObject): + + def __init__(self): + self.value = None + self.normclass = None + self.normform = None + self.normnumber = None + + def _ioSafe(self): + if self.optional or (self._isSet() and self.normclass is not None and self.normform is not None and self.normnumber is not None): + return 1 + else: + return 0 + + def setTag(self, klass, form, number): + self.normclass = klass + self.normform = form + self.normnumber = number + + def reset(self): + self.value = None + + def get(self): + return self.value + + def set(self, value): + self.value = value + + def write(self,file): + if not self._ioSafe(): + raise DerError, 'object not set which must be: %s' % repr(self) + elif self.optional and self.value is None: + pass + else: + buf = cStringIO.StringIO() + io = _TlvIo(buf) + io.tagclass = self.normclass + io.tagform = self.normform + io.tagnumber = self.normnumber + io.length = len(self.value) + io.write(self.value) + file.write(buf.getvalue()) + + def read(self, io=None): + + processDefOpt = 0 + if io is None: + processDefOpt = 1 + elif isinstance(io, _TlvIo): + if not io: + processDefOpt = 1 + else: + pos = io.tell() + io.seek(0,2) + if io.tell(): + io.seek(pos) + else: + processDefOpt = 1 + if processDefOpt: + if self.optional or self.default: + self.reset() + return 0 + else: + raise DerError, 'no TLV is available to read in non-optional/non-default object: %s' % repr(self) + + if not isinstance(io, _TlvIo): + tmp = _TlvIo(io) + tmp.read() + io = tmp + + self.value = io.readValue() + self.normclass = io.tagclass + self.normform = io.tagform + self.normnumber = io.tagnumber + +_addFragment(''' +<class> + <header> + <name>Boolean</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 BOOLEAN type. It can be set + with any object which can be tested for truth. + </para> + </body> +</class> +''') + +class Boolean(_GeneralObject): # 0x01 + + _addFragment(''' + <constructor> + <header> + <memberof>Boolean</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_BOOLEAN, _encodeBoolean, _decodeBoolean, optional, default) + +_addFragment(''' +<class> + <header> + <name>Integer</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 INTEGER type. It should be set + with a Python integer. + </para> + </body> +</class> +''') + +class Integer(_GeneralObject): # 0x02 + + _addFragment(''' + <constructor> + <header> + <memberof>Integer</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_INTEGER, _encodeInteger, _decodeInteger, optional, default) + +_addFragment(''' +<class> + <header> + <name>BitString</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 BIT STRING type. It should be set + with a sequence of integers. A non-zero number will set the bit, + zero will leave the bit unset. + </para> + </body> +</class> +''') + +class BitString(_GeneralObject): # 0x03 + + _addFragment(''' + <constructor> + <header> + <memberof>BitString</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_BITSTRING, _encodeBitString, _decodeBitString, optional, default) + +_addFragment(''' +<class> + <header> + <name>AltBitString</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 BIT STRING type. It differs from + the first <classname>BitString</classname> in that it's coding + routines treat values as binary data and do not interpret the data + in any way. Some application treat the + <classname>BIT STRING</classname> in the same way as + <classname>OCTET STRING</classname> type, hence this extra object. + </para> + </body> +</class> +''') + +class AltBitString(_GeneralObject): # 0x03 + + _addFragment(''' + <constructor> + <header> + <memberof>AltBitString</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_BITSTRING, lambda x : chr(0)+x, lambda x : x[1:], optional, default) + +_addFragment(''' +<class> + <header> + <name>OctetString</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 OCTET STRING type. This object + can be set with any binary data. + </para> + </body> +</class> +''') +class OctetString(_GeneralObject): # 0x04 + + _addFragment(''' + <constructor> + <header> + <memberof>OctetString</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_OCTETSTRING, lambda x : x, lambda x : x, optional, default) + +_addFragment(''' +<class> + <header> + <name>Null</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 NULL type. There is no point in + setting this object, the value will always be ignored when it is + written out. + </para> + </body> +</class> +''') +class Null(_GeneralObject): # 0x05 + + _addFragment(''' + <constructor> + <header> + <memberof>Null</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_NULL, lambda x : '', lambda x : '', optional, default) + self.value = '' + + def _ioSafe(self): + return 1 + + def reset(self): + self.value = '' + +_addFragment(''' +<class> + <header> + <name>Oid</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 OID type. This object should be + set with a list or tuple of integers defining an objects oid. + Please note that the first three arcs have a restricted set of + values, so encoding (5, 3, 7, 1) will produce bad results. + </para> + </body> +</class> +''') +class Oid(_GeneralObject): # 0x06 + + _addFragment(''' + <constructor> + <header> + <memberof>Oid</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_OID, _encodeOid, _decodeOid, optional, default) + +_addFragment(''' +<class> + <header> + <name>Enum</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 ENUM type. This should be set + using a Python integer, the meaning should be described in the + ASN1 document for the object you are encoding. + </para> + </body> +</class> +''') +class Enum(_GeneralObject): # 0x0A + + _addFragment(''' + <constructor> + <header> + <memberof>Enum</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_ENUMERATED, _encodeInteger, _decodeInteger, optional, default) + +_addFragment(''' +<class> + <header> + <name>Utf8String</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 UTF8String type. This object + should be set with a string. It is up to the application to ensure + it only contains valid characters for this type. + </para> + </body> +</class> +''') +class Utf8String(_GeneralObject): # 0x0C + + _addFragment(''' + <constructor> + <header> + <memberof>Utf8String</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_UTF8STRING, lambda x : x, lambda x : x, optional, default) + +_addFragment(''' +<class> + <header> + <name>NumericString</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 NumericString type. This should + object should be set with a string. It is up to the application to ensure + it only contains valid characters for this type. + </para> + </body> +</class> +''') +class NumericString(_GeneralObject): # 0x12 + + _addFragment(''' + <constructor> + <header> + <memberof>NumericString</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_NUMERICSTRING, lambda x : x, lambda x : x, optional, default) +_addFragment(''' +<class> + <header> + <name>PrintableString</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 PrintableString type. This should + object should be set with a string. It is up to the application to ensure + it only contains valid characters for this type. + </para> + </body> +</class> +''') +class PrintableString(_GeneralObject): # 0x13 + + _addFragment(''' + <constructor> + <header> + <memberof>PrintableString</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_PRINTABLESTRING, lambda x : x, lambda x : x, optional, default) + +_addFragment(''' +<class> + <header> + <name>T61String</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 T61String type. This object + should be set with a string. It is up to the application to ensure + it only contains valid characters for this type. + </para> + </body> +</class> +''') +class T61String(_GeneralObject): # 0x14 + + _addFragment(''' + <constructor> + <header> + <memberof>T61String</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_T61STRING, lambda x : x, lambda x : x, optional, default) + +_addFragment(''' +<class> + <header> + <name>VideotexString</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 VideotexString type. This should + object should be set with a string. It is up to the application to ensure + it only contains valid characters for this type. + </para> + </body> +</class> +''') +class VideotexString(_GeneralObject): # 0x15 + + _addFragment(''' + <constructor> + <header> + <memberof>VideotexString</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_VIDEOTEXSTRING, lambda x : x, lambda x : x, optional, default) + +_addFragment(''' +<class> + <header> + <name>IA5String</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 IA5String type. This object + should be set with a string. It is up to the application to ensure + it only contains valid characters for this type. + </para> + </body> +</class> +''') +class IA5String(_GeneralObject): # 0x16 + + _addFragment(''' + <constructor> + <header> + <memberof>IA5String</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_IA5STRING, lambda x : x, lambda x : x, optional, default) + +_addFragment(''' +<class> + <header> + <name>UtcTime</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 UTCTime type. This object should + be set with a string of the general format YYMMDDhhmmssZ. The + helper functions <function>time2utc</function> and + <function>utc2time</function> can be used to handle the conversion + from an integer to a string and back. + </para> + </body> +</class> +''') +class UtcTime(_GeneralObject): # 0x17 + + _addFragment(''' + <constructor> + <header> + <memberof>UtcTime</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_UTCTIME, lambda x : x, lambda x : x, optional, default) + +_addFragment(''' +<class> + <header> + <name>GeneralizedTime</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 GeneralizedTime type. This object should + be set with a string of the general format YYYYMMDDhhmmssZ. The + helper functions <function>time2utc</function> and + <function>utc2time</function> can be used to handle the conversion + from an integer to a string and back. + </para> + </body> +</class> +''') +class GeneralizedTime(_GeneralObject): # 0x18 + + _addFragment(''' + <constructor> + <header> + <memberof>GeneralizedTime</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_GENERALIZEDTIME, lambda x : x, lambda x : x, optional, default) + +_addFragment(''' +<class> + <header> + <name>GraphicString</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 GraphicString type. This should + object should be set with a string. It is up to the application to + ensure it only contains valid characters for this type. + </para> + </body> +</class> +''') +class GraphicString(_GeneralObject): # 0x19 + + _addFragment(''' + <constructor> + <header> + <memberof>GraphicString</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_GRAPHICSTRING, lambda x : x, lambda x : x, optional, default) + +_addFragment(''' +<class> + <header> + <name>VisibleString</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 VisibleString type. This should + object should be set with a string. It is up to the application to + ensure it only contains valid characters for this type. + </para> + </body> +</class> +''') +class VisibleString(_GeneralObject): # 0xC0 + + _addFragment(''' + <constructor> + <header> + <memberof>VisibleString</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_VISIBLESTRING, lambda x : x, lambda x : x, optional, default) + +_addFragment(''' +<class> + <header> + <name>GeneralString</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 GeneralString type. This should + object should be set with a string. It is up to the application to + ensure it only contains valid characters for this type. + </para> + </body> +</class> +''') +class GeneralString(_GeneralObject): # 0xC0 + + _addFragment(''' + <constructor> + <header> + <memberof>GeneralString</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_GENERALSTRING, lambda x : x, lambda x : x, optional, default) + +_addFragment(''' +<class> + <header> + <name>UniversalString</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 UniversalString type. This should + object should be set with a string. It is up to the application to + ensure it only contains valid characters for this type. + </para> + </body> +</class> +''') +class UniversalString(_GeneralObject): # 0xC0 + + _addFragment(''' + <constructor> + <header> + <memberof>UniversalString</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_UNIVERSALSTRING, lambda x : x, lambda x : x, optional, default) + +_addFragment(''' +<class> + <header> + <name>BmpString</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 BMPString type. This object + should be set with a string. It is up to the application to ensure + it only contains valid characters for this type. + </para> + </body> +</class> +''') +class BmpString(_GeneralObject): # 0xC0 + + _addFragment(''' + <constructor> + <header> + <memberof>BmpString</memberof> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + </constructor> + ''') + + def __init__(self, optional=0, default=''): + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_BMPSTRING, lambda x : x, lambda x : x, optional, default) + +_addFragment(''' +<class> + <header> + <name>Sequence</name> + <super>_GeneralObject</super> + </header> + <body> + <para> + This object represents the ASN1 SEQUENCE type. + </para> + </body> +</class> +''') +class Sequence(_GeneralObject): # 0x10 + + _addFragment(''' + <constructor> + <header> + <memberof>Sequence</memberof> + <super>_GeneralObject</super> + <parameter>contents</parameter> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + <body> + <para> + The <parameter>contents</parameter> should be a list or tuple containing + the contents of the sequence. + Two important members are initialised this this constructor. + First <constant>self.next</constant> this is used to keep track + of which TLVs in this sequence has been read succesfully. The second, + <constant>self.contents</constant> should be set to the list of + objects stored in this sequence. Note that the order they are + specified in is the order in which they are written or read. + </para> + </body> + </constructor> + ''') + + def __init__(self, contents, optional=0, default=''): + self.contents = contents + self.next = 0 + _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_CONSTRUCTED, TAG_SEQUENCE, _encodeSequence, _decodeSequence, optional, default) + + def _childRead(self, obj): + if self.next < len(self.value): + if obj.read( self.value[self.next] ): + self.next += 1 + else: + obj.read() + + _addFragment(''' + <method> + <header> + <memberof>Sequence</memberof> + <name>readContents</name> + <parameter>io</parameter> + <parameter>contents</parameter> + </header> + <body> + <para> + This function implements basic SEQUENCE like reading behaviour. + It will attempt to read each of the objects in + <parameter>contents</parameter> in turn from + <parameter>io</parameter>. It exists as a function, separate + from <function>read</function> for the benefit of the SEQUENCE + OF implementation. + </para> + <para> + The TLV of this SEQUENCE is read and parsed into a list of + TLVs, which are store in <constant>self.value</constant>, by + <classname>_GeneralObject</classname>.<function>read</function>. + Then <function>read</function> is called on each member to + process each TLV in turn. The next TLV is moved onto only when + a member returns TRUE from the read call. + </para> + </body> + </method> + ''') + + def readContents(self, io, contents): + if _GeneralObject.read( self, io ): + for item in contents: + Sequence._childRead( self, item ) + return 1 + else: + return 0 + + _addFragment(''' + <method> + <header> + <memberof>Sequence</memberof> + <name>read</name> + <parameter>io</parameter> + </header> + <body> + <para> + Most of the logic for reading is implemented in <function>readContents</function> + so it can be reused for <classname>SequenceOf</classname>'s + <function>read</function> function. + </para> + </body> + </method> + ''') + + def read(self, io=None): + self.next = 0 + return self.readContents(io, self.contents) + + _addFragment(''' + <method> + <header> + <memberof>Sequence</memberof> + <name>write</name> + <parameter>file</parameter> + </header> + <body> + <para> + <constant>self.value</constant> is set to the contents of this + SEQUENCE and then written by calling + <classname>_GeneralObject</classname>.<function>write</function> + whos encoder will call <function>write</function> of + each element in the list of contents in turn. + </para> + </body> + </method> + ''') + + def write(self, file): + if self._ioSafe(): + if self._isSet(): + _GeneralObject.set( self, self.contents ) + _GeneralObject.write( self, file ) + elif self.optional: + pass + else: + prob = self.findUnset() + raise DerError, '%s is not in a state which can be written, %s is unset' % (repr(self), repr(prob) ) + + _addFragment(''' + <method> + <header> + <memberof>Sequence</memberof> + <name>set</name> + <parameter>values</parameter> + </header> + <body> + <para> + Accessing and setting values for ASN1 objects is a bit of a + thorny issue. The problem stems from the arbitrary complexity + of the data and the possible levels of nesting, which in + practice are used and are quite massive. Designing a good general + approach is a bit tricky, perhaps nearly + impossible. I choose to use a most compact + form which is excellent for simple objects and is very concise. + </para> + <para> + <parameter>value</parameter> should be a list or tuple of + values. Each element of the list (or tuple) will be used in + turn to set a member. Defaults can be specified by using the + default value itself or <constant>None</constant>. Hence, for + SEQUENCES of SEQUENCES, SEQUENCES OF, SET and so on + <parameter>values</parameter> should consist of nested lists or + tuples. Look at the ASN1 specs for that object to figure out + exactly what these should look like. + </para> + </body> + </method> + ''') + + def set(self, values): + if self.contents is None: + raise DerError, 'the contents attribute should be set before using this object' + if not( isinstance(values, types.ListType) or isinstance(values, types.TupleType) ): + raise DerError, 'a sequence should be set with a list or tuple of values' + if len(values) != len(self.contents): + raise DerError, 'wrong number of values have been supplied to set %s. Expecting %i, got %i' % \ + (self.__class__.__name__, len(self.contents), len(values) ) + + i = 0 + for val in values: + self.contents[i].set(val) + i = i + 1 + + _addFragment(''' + <method> + <header> + <memberof>Sequence</memberof> + <name>get</name> + </header> + <body> + <para> + A tuple of the values of the contents of this sequence will be + returned. Hence, for SEQUENCES of SEQUENCES, SEQUENCES OF, SET + and so on nested tuples will be returned. + <function>get</function> always returns tuples even if a list + was used to set and object. + </para> + </body> + </method> + ''') + + def get(self): + if self.contents is None: + return _GeneralObject.get(self) + else: + results = [] + for obj in self.contents: + results.append( obj.get() ) + return tuple(results) + + def reset(self): + if self.contents is None: + raise DerError, 'this object has no members to set' + self.next = 0 + for obj in self.contents: + obj.reset() # clear all child objects prior to possible setting + # via default + _GeneralObject.reset(self) + + def _isSet(self): + if self.contents is None: + raise DerError, 'this object has no members to set' + for obj in self.contents: + if not obj._ioSafe(): + return 0 + return 1 + + def findUnset(self): + if self.contents is None: + raise DerError, 'this object has no members to check' + for obj in self.contents: + if not obj._ioSafe(): + return obj + + def _ioSafe(self): + if self.optional or self._isSet(): + return 1 + else: + for obj in self.contents: + if not obj._ioSafe(): + return 0 + return 1 + +_addFragment(''' +<class> + <header> + <name>SequenceOf</name> + <super>Sequence</super> + </header> + <body> + <para> + This object represents the ASN1 SEQUENCE OF construct. + </para> + </body> +</class> +''') +class SequenceOf(Sequence): + + _addFragment(''' + <constructor> + <header> + <memberof>SequenceOf</memberof> + <super>Sequence</super> + <parameter>contains</parameter> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + <body> + <para> + The <parameter>contains</parameter> should be the constructor + for the objects which this SEQUENCE OF contains. + </para> + </body> + </constructor> + ''') + + def __init__(self, contains, optional=0, default=''): + self.contains = contains + self.sequenceOf = [] + Sequence.__init__(self, [], optional, default) + + def _ioSafe(self): + return 1 + + def reset(self): + if self.contents is None: + raise DerError, 'this object has no members to set' + self.next = 0 + self.sequenceOf = [] + _GeneralObject.reset(self) + + def _isSet(self): + if self.sequenceOf: + for obj in self.contents: + if not obj._ioSafe(): + return 0 + return 1 + else: + return 0 + + def set(self, values): + if isinstance(values, types.NoneType): + return + objects = [] + for val in values: + obj = self.contains() + obj.set(val) + objects.append(obj) + self.sequenceOf = objects + + def get(self): + results = [] + for obj in self.sequenceOf: + results.append( obj.get() ) + return tuple(results) + + def read(self, io=None): + self.sequenceOf = [] + self.next = 0 + if _GeneralObject.read( self, io ): + for tagio in _GeneralObject.get(self): + value = self.contains() + value.read(tagio) + self.sequenceOf.append(value) + return 1 + else: + return 0 + + def write(self, file): + if not self._isSet() and self.optional: + pass + else: + _GeneralObject.set( self, self.sequenceOf ) + _GeneralObject.write( self, file ) + + def __len__(self): + return len(self.sequenceOf) + + def __getitem__(self, key): + return self.sequenceOf[key] + + def __iter__(self): + for i in self.sequenceOf: + yield(i) + + def __contains__(self, item): + return item in self.sequenceOf + +_addFragment(''' +<class> + <header> + <name>Set</name> + <super>Sequence</super> + </header> + <body> + <para> + This object represents the ASN1 Set type. + </para> + </body> +</class> +''') +class Set(Sequence): # 0x11 + + _addFragment(''' + <constructor> + <header> + <memberof>Set</memberof> + <super>Sequence</super> + <parameter>contents</parameter> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + <body> + <para> + The <parameter>contents</parameter> should be a list containing + the contents of the sequence. + </para> + </body> + </constructor> + ''') + + def __init__(self, contents, optional=0, default=''): + Sequence.__init__(self, contents, optional, default) + self.normnumber = TAG_SET + +_addFragment(''' +<class> + <header> + <name>SetOf</name> + <super>SequenceOf</super> + </header> + <body> + <para> + This object represents the ASN1 SET OF construct. + </para> + </body> +</class> +''') +class SetOf(SequenceOf): + + _addFragment(''' + <constructor> + <header> + <memberof>SetOf</memberof> + <super>SequenceOf</super> + <parameter>contains</parameter> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + <body> + <para> + The <parameter>contains</parameter> should be the constructor + for the objects which this SET OF contains. + </para> + </body> + </constructor> + ''') + + def __init__(self, contains, optional=0, default=''): + SequenceOf.__init__(self, contains, optional, default) + self.normnumber = TAG_SET + +_addFragment(''' +<class> + <header> + <name>Explicit</name> + <super>Sequence</super> + </header> + <body> + <para> + Explicit objects support the DER concept of explicit tagging. In + general they behave just like a SEQUENCE which must have only one + element. See below for other differences. + </para> + </body> +</class> +''') +class Explicit(Sequence): + + _addFragment(''' + <constructor> + <header> + <memberof>Explicit</memberof> + <super>Sequence</super> + <parameter>expclass</parameter> + <parameter>expform</parameter> + <parameter>expnumber</parameter> + <parameter>contents</parameter> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + <body> + <para> + <parameter>expclass</parameter>, + <parameter>expform</parameter>, + <parameter>expnumber</parameter> should be as + specified in the ASN1 documentation for this object. + <parameter>contents</parameter> should be an object instance + such as <classname>Integer</classname>, + <classname>Oid</classname> or a derived object which supports + the <classname>_GeneralObjec</classname> interface. + </para> + </body> + </constructor> + ''') + + def __init__(self, expclass, expform, expnumber, contents, optional=0, default=''): + self.contents = [contents] + self.next = 0 + _GeneralObject.__init__(self, expclass, expform, expnumber, _encodeSequence, _decodeSequence, optional, default) + + _addFragment(''' + <method> + <header> + <memberof>Explicit</memberof> + <name>set</name> + <parameter>value</parameter> + </header> + <body> + <para> + <parameter>value</parameter> is passed direct to + <function>set</function> of the explicit object, so it should + not be placed in a list or tuple(unless you are setting a constructed + object). + </para> + </body> + </method> + ''') + def set(self, value): + return Sequence.set(self, [value]) + + _addFragment(''' + <method> + <header> + <memberof>Explicit</memberof> + <name>get</name> + </header> + <body> + <para> + The value of explicit object is returned and not + put in a tuple. + </para> + </body> + </method> + ''') + def get(self): + return Sequence.get(self)[0] + +_addFragment(''' +<class> + <header> + <name>Choice</name> + </header> + <body> + <para> + This object represents the ASN1 Choice type. + </para> + </body> +</class> +''') +class Choice(object): + + _addFragment(''' + <constructor> + <header> + <memberof>Choice</memberof> + <parameter>choices</parameter> + <parameter>optional=0</parameter> + <parameter>default=''</parameter> + </header> + <body> + <para> + <parameter>choices</parameter> should be a dictionary of + objects which support the <classname>_GeneralObject</classname> + interface. The key being the name of the choice specified in the + ASN1 documentation. <parameter>optional</parameter> is a boolean + indicating if this object is optional. The final parameter, + <parameter>default</parameter> is the base 64 encoded DER + value, which should be used as the default in leu of a value to + read or incase it is unset. If neither + <parameter>optional</parameter> or + <parameter>default</parameter> is not set then the first choice + which is optional or has a default will be honored. + </para> + </body> + </constructor> + ''') + + def __init__(self, choices, optional=0, default=''): + self.value = None + self.choices = choices + self.optional = optional + self.default = default + self.choice = None + self.reset() + + def _ioSafe(self): + if self.optional or self._isSet(): + return 1 + elif self.choice and self.choices[ self.choice ]._ioSafe(): + return 1 + else: + return 0 + + def _isSet(self): + if self.choice and self.choices[self.choice]._isSet(): + return 1 + else: + return 0 + + _addFragment(''' + <method> + <header> + <memberof>Choice</memberof> + <name>reset</name> + </header> + <body> + <para> + This function re-initialises the object, clearing the value or + setting it to any default. + </para> + </body> + </method> + ''') + def reset(self): + self.value = None + self.choice = None + if self.default: + buf = cStringIO.StringIO( base64.decodestring( self.default ) ) + io = _TlvIo(buf) + io.read() + self.read(io) + else: + for key in self.choices.keys(): + self.choices[key].reset() + if self.choices[key]._ioSafe(): + self.choice = key + break; + + + _addFragment(''' + <method> + <header> + <memberof>Choice</memberof> + <name>set</name> + <parameter>value</parameter> + </header> + <body> + <para> + <parameter>value</parameter> should be a list or tuple with two + elements. The first value should be the name of the choice to + be set and the second the value to set it with. + </para> + </body> + </method> + ''') + def set(self, val): + if val is None: + return + if not (isinstance(val, types.ListType) or isinstance(val, types.TupleType)): + raise DerError, 'argument should be a list or tuple' + if not self.choices.has_key( val[0] ): + raise DerError, 'unknown choice: %s' % val[0] + self.choices[ val[0] ].set(val[1]) + self.choice = val[0] + + _addFragment(''' + <method> + <header> + <memberof>Choice</memberof> + <name>get</name> + </header> + <body> + <para> + This function will return tuple with two elements. The first + value will be the name of the choice which was set and the second + the value it was set to. + </para> + </body> + </method> + ''') + + def get(self): + if self._isSet(): + return (self.choice, self.choices[ self.choice ].get()) + else: + return None + + _addFragment(''' + <method> + <header> + <memberof>Choice</memberof> + <name>toString</name> + </header> + <body> + <para> + Encodes the value in DER and returns it as a string. + </para> + </body> + </method> + ''') + + def toString(self): + buf = cStringIO.StringIO() + self.write(buf) + return buf.getvalue() + + _addFragment(''' + <method> + <header> + <memberof>Choice</memberof> + <name>fromString</name> + </header> + <body> + <para> + Decodes the string and sets the value of this object. + </para> + </body> + </method> + ''') + + def fromString(self, value): + buf = cStringIO.StringIO(value) + self.read(buf) + + _addFragment(''' + <method> + <header> + <memberof>Choice</memberof> + <name>read</name> + <parameter>io</parameter> + </header> + <body> + <para> + <parameter>io</parameter> should be a file like object. If the + object being read matches the expected class, form and tag the + value is read and decoded using + <function>decRoutine</function>. Else, if it has a default + that is read and stored. + </para> + <para> + The return value of this function does not indicate success but + whether this TLV was processed successfully. This bahaviour is + vital for processing constructed types since the object may be + optional or have a default. Failure to decode would be indicated + by an exception. + </para> + </body> + </method> + ''') + + def _readChoices(self, io): + for key in self.choices.keys(): + try: + readindicator = self.choices[key].read(io) + self.choice = key + break; + except DerError: + if DEBUG: + traceback.print_exc() + return readindicator + + def read(self, io=None): + + self.choice = None + processDefOpt = 0 + readindicator = 0 + + if io is None: + processDefOpt = 1 + elif isinstance(io, _TlvIo): + if not io: + processDefOpt = 1 + else: + pos = io.tell() + io.seek(0,2) + if io.tell(): + io.seek(pos) + else: + processDefOpt = 1 + + if processDefOpt: + if self.optional or self.default: + self.reset() + return 0 + else: + readindicator = self._readChoices(io) + for key in self.choices.keys(): + try: + readindicator = self.choices[key].read(io) + self.choice = key + break; + except DerError: + if DEBUG: + traceback.print_exc() + if not self._isSet(): + raise DerError, 'no TLV is available to read in non-optional/non-default object: %s' % repr(self) + else: + return readindicator + + if not isinstance(io, _TlvIo): + tmp = _TlvIo(io) + tmp.read() + io = tmp + + for key in self.choices.keys(): + try: + if self.choices[key].read(io): + self.choice = key + readindicator = 1 + break; + except DerError: + if DEBUG: + traceback.print_exc() + + if not self._isSet(): + self.reset() + else: + return readindicator + + _addFragment(''' + <method> + <header> + <memberof>Choice</memberof> + <name>write</name> + <parameter>file</parameter> + </header> + <body> + <para> + If this object has not been set and is not optional and dosn't + have a default, a <classname>DerError</classname> exception will be raised + </para> + <para> + If no value has been set and this object is optional, nothing + is written. If this object's value is equal to the default, + nothing is written as stipulated by DER. Otherwise the value + is encoded and written. + </para> + </body> + </method> + ''') + def write(self,file): + if self.optional and not self.choice: + pass + elif not self.choice: + raise DerError, 'choice not set' + elif self.choice: + if self.default: + defval = base64.decodestring( self.default ) + if defval != self.choices[ self.choice ].toString(): + self.choices[ self.choice ].write(file) + else: + self.choices[ self.choice ].write(file) + else: + raise DerError, 'an internal error has occured: %s' % repr(self) + + |