#*****************************************************************************# #* *# #* 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('''
POW.pkix Peter Shannon
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. 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 SET objects and SET OF 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 Set objects are simple and the order they are written in is defined by the object's constructor. Every documented object in this module supports the functions documented for _GeneralObject. 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 _GeneralObject or Sequence.
''') 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.__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('''
_GeneralObject
_GeneralObject 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 _GeneralObject's functions.
''') class _GeneralObject(object): _addFragment('''
_GeneralObject normclass normform normnumber encRoutine decRoutine optional=0 default=''
normclass is the class of the object, ei: universal, application, context or private. normform is the form of the object, ei primitive or constructed. normnumber is the tag number of the object. encRoutine is a function which takes a value and encodes it according the appropriate DER rules. decRoutine is a function which reads a string value and returns a value which is more useful in Python. optional is a boolean indicating if this object is optional. The final parameter, default 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.
''') 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('''
_GeneralObject reset
This function re-initialises the object, clearing the value or setting it to any default.
''') 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('''
_GeneralObject set value
This dosn't do much except store value, 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.
''') def set(self, value): if value is not None: self.value = value _addFragment('''
_GeneralObject get
Gets the value stored presumably after reading the object.
''') def get(self): return self.value _addFragment('''
_GeneralObject implied impclass impform impnumber
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 dumpasn1 can be invaluable for debugging objects.
''') 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('''
_GeneralObject read io
io 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 decRoutine. Else, if it has a default that is read and stored. 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.
''') 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('''
_GeneralObject write io
If this object has not been set and is not optional and dosn't have a default, a DerError exception will be raised 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.
''') 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('''
_GeneralObject toString
Encodes the value in DER and returns it as a string.
''') def toString(self): buf = cStringIO.StringIO() self.write(buf) return buf.getvalue() _addFragment('''
_GeneralObject fromString
Decodes the string and sets the value of this object.
''') 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('''
Boolean _GeneralObject
This object represents the ASN1 BOOLEAN type. It can be set with any object which can be tested for truth.
''') class Boolean(_GeneralObject): # 0x01 _addFragment('''
Boolean optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_BOOLEAN, _encodeBoolean, _decodeBoolean, optional, default) _addFragment('''
Integer _GeneralObject
This object represents the ASN1 INTEGER type. It should be set with a Python integer.
''') class Integer(_GeneralObject): # 0x02 _addFragment('''
Integer optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_INTEGER, _encodeInteger, _decodeInteger, optional, default) _addFragment('''
BitString _GeneralObject
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.
''') class BitString(_GeneralObject): # 0x03 _addFragment('''
BitString optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_BITSTRING, _encodeBitString, _decodeBitString, optional, default) _addFragment('''
AltBitString _GeneralObject
This object represents the ASN1 BIT STRING type. It differs from the first BitString in that it's coding routines treat values as binary data and do not interpret the data in any way. Some application treat the BIT STRING in the same way as OCTET STRING type, hence this extra object.
''') class AltBitString(_GeneralObject): # 0x03 _addFragment('''
AltBitString optional=0 default=''
''') 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('''
OctetString _GeneralObject
This object represents the ASN1 OCTET STRING type. This object can be set with any binary data.
''') class OctetString(_GeneralObject): # 0x04 _addFragment('''
OctetString optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_OCTETSTRING, lambda x : x, lambda x : x, optional, default) _addFragment('''
Null _GeneralObject
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.
''') class Null(_GeneralObject): # 0x05 _addFragment('''
Null optional=0 default=''
''') 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('''
Oid _GeneralObject
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.
''') class Oid(_GeneralObject): # 0x06 _addFragment('''
Oid optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_OID, _encodeOid, _decodeOid, optional, default) _addFragment('''
Enum _GeneralObject
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.
''') class Enum(_GeneralObject): # 0x0A _addFragment('''
Enum optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_ENUMERATED, _encodeInteger, _decodeInteger, optional, default) _addFragment('''
Utf8String _GeneralObject
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.
''') class Utf8String(_GeneralObject): # 0x0C _addFragment('''
Utf8String optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_UTF8STRING, lambda x : x, lambda x : x, optional, default) _addFragment('''
NumericString _GeneralObject
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.
''') class NumericString(_GeneralObject): # 0x12 _addFragment('''
NumericString optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_NUMERICSTRING, lambda x : x, lambda x : x, optional, default) _addFragment('''
PrintableString _GeneralObject
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.
''') class PrintableString(_GeneralObject): # 0x13 _addFragment('''
PrintableString optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_PRINTABLESTRING, lambda x : x, lambda x : x, optional, default) _addFragment('''
T61String _GeneralObject
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.
''') class T61String(_GeneralObject): # 0x14 _addFragment('''
T61String optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_T61STRING, lambda x : x, lambda x : x, optional, default) _addFragment('''
VideotexString _GeneralObject
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.
''') class VideotexString(_GeneralObject): # 0x15 _addFragment('''
VideotexString optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_VIDEOTEXSTRING, lambda x : x, lambda x : x, optional, default) _addFragment('''
IA5String _GeneralObject
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.
''') class IA5String(_GeneralObject): # 0x16 _addFragment('''
IA5String optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_IA5STRING, lambda x : x, lambda x : x, optional, default) _addFragment('''
UtcTime _GeneralObject
This object represents the ASN1 UTCTime type. This object should be set with a string of the general format YYMMDDhhmmssZ. The helper functions time2utc and utc2time can be used to handle the conversion from an integer to a string and back.
''') class UtcTime(_GeneralObject): # 0x17 _addFragment('''
UtcTime optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_UTCTIME, lambda x : x, lambda x : x, optional, default) _addFragment('''
GeneralizedTime _GeneralObject
This object represents the ASN1 GeneralizedTime type. This object should be set with a string of the general format YYYYMMDDhhmmssZ. The helper functions time2utc and utc2time can be used to handle the conversion from an integer to a string and back.
''') class GeneralizedTime(_GeneralObject): # 0x18 _addFragment('''
GeneralizedTime optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_GENERALIZEDTIME, lambda x : x, lambda x : x, optional, default) _addFragment('''
GraphicString _GeneralObject
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.
''') class GraphicString(_GeneralObject): # 0x19 _addFragment('''
GraphicString optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_GRAPHICSTRING, lambda x : x, lambda x : x, optional, default) _addFragment('''
VisibleString _GeneralObject
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.
''') class VisibleString(_GeneralObject): # 0xC0 _addFragment('''
VisibleString optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_VISIBLESTRING, lambda x : x, lambda x : x, optional, default) _addFragment('''
GeneralString _GeneralObject
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.
''') class GeneralString(_GeneralObject): # 0xC0 _addFragment('''
GeneralString optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_GENERALSTRING, lambda x : x, lambda x : x, optional, default) _addFragment('''
UniversalString _GeneralObject
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.
''') class UniversalString(_GeneralObject): # 0xC0 _addFragment('''
UniversalString optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_UNIVERSALSTRING, lambda x : x, lambda x : x, optional, default) _addFragment('''
BmpString _GeneralObject
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.
''') class BmpString(_GeneralObject): # 0xC0 _addFragment('''
BmpString optional=0 default=''
''') def __init__(self, optional=0, default=''): _GeneralObject.__init__(self, CLASS_UNIVERSAL, FORM_PRIMITIVE, TAG_BMPSTRING, lambda x : x, lambda x : x, optional, default) _addFragment('''
Sequence _GeneralObject
This object represents the ASN1 SEQUENCE type.
''') class Sequence(_GeneralObject): # 0x10 _addFragment('''
Sequence _GeneralObject contents optional=0 default=''
The contents should be a list or tuple containing the contents of the sequence. Two important members are initialised this this constructor. First self.next this is used to keep track of which TLVs in this sequence has been read succesfully. The second, self.contents 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.
''') 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('''
Sequence readContents io contents
This function implements basic SEQUENCE like reading behaviour. It will attempt to read each of the objects in contents in turn from io. It exists as a function, separate from read for the benefit of the SEQUENCE OF implementation. The TLV of this SEQUENCE is read and parsed into a list of TLVs, which are store in self.value, by _GeneralObject.read. Then read 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.
''') def readContents(self, io, contents): if _GeneralObject.read( self, io ): for item in contents: Sequence._childRead( self, item ) return 1 else: return 0 _addFragment('''
Sequence read io
Most of the logic for reading is implemented in readContents so it can be reused for SequenceOf's read function.
''') def read(self, io=None): self.next = 0 return self.readContents(io, self.contents) _addFragment('''
Sequence write file
self.value is set to the contents of this SEQUENCE and then written by calling _GeneralObject.write whos encoder will call write of each element in the list of contents in turn.
''') 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('''
Sequence set values
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. value 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 None. Hence, for SEQUENCES of SEQUENCES, SEQUENCES OF, SET and so on values should consist of nested lists or tuples. Look at the ASN1 specs for that object to figure out exactly what these should look like.
''') 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('''
Sequence get
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. get always returns tuples even if a list was used to set and object.
''') 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('''
SequenceOf Sequence
This object represents the ASN1 SEQUENCE OF construct.
''') class SequenceOf(Sequence): _addFragment('''
SequenceOf Sequence contains optional=0 default=''
The contains should be the constructor for the objects which this SEQUENCE OF contains.
''') 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('''
Set Sequence
This object represents the ASN1 Set type.
''') class Set(Sequence): # 0x11 _addFragment('''
Set Sequence contents optional=0 default=''
The contents should be a list containing the contents of the sequence.
''') def __init__(self, contents, optional=0, default=''): Sequence.__init__(self, contents, optional, default) self.normnumber = TAG_SET _addFragment('''
SetOf SequenceOf
This object represents the ASN1 SET OF construct.
''') class SetOf(SequenceOf): _addFragment('''
SetOf SequenceOf contains optional=0 default=''
The contains should be the constructor for the objects which this SET OF contains.
''') def __init__(self, contains, optional=0, default=''): SequenceOf.__init__(self, contains, optional, default) self.normnumber = TAG_SET _addFragment('''
Explicit Sequence
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.
''') class Explicit(Sequence): _addFragment('''
Explicit Sequence expclass expform expnumber contents optional=0 default=''
expclass, expform, expnumber should be as specified in the ASN1 documentation for this object. contents should be an object instance such as Integer, Oid or a derived object which supports the _GeneralObjec interface.
''') 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('''
Explicit set value
value is passed direct to set of the explicit object, so it should not be placed in a list or tuple(unless you are setting a constructed object).
''') def set(self, value): return Sequence.set(self, [value]) _addFragment('''
Explicit get
The value of explicit object is returned and not put in a tuple.
''') def get(self): return Sequence.get(self)[0] _addFragment('''
Choice
This object represents the ASN1 Choice type.
''') class Choice(object): _addFragment('''
Choice choices optional=0 default=''
choices should be a dictionary of objects which support the _GeneralObject interface. The key being the name of the choice specified in the ASN1 documentation. optional is a boolean indicating if this object is optional. The final parameter, default 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 optional or default is not set then the first choice which is optional or has a default will be honored.
''') 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('''
Choice reset
This function re-initialises the object, clearing the value or setting it to any default.
''') 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('''
Choice set value
value 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.
''') 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('''
Choice get
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.
''') def get(self): if self._isSet(): return (self.choice, self.choices[ self.choice ].get()) else: return None _addFragment('''
Choice toString
Encodes the value in DER and returns it as a string.
''') def toString(self): buf = cStringIO.StringIO() self.write(buf) return buf.getvalue() _addFragment('''
Choice fromString
Decodes the string and sets the value of this object.
''') def fromString(self, value): buf = cStringIO.StringIO(value) self.read(buf) _addFragment('''
Choice read io
io 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 decRoutine. Else, if it has a default that is read and stored. 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.
''') 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('''
Choice write file
If this object has not been set and is not optional and dosn't have a default, a DerError exception will be raised 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.
''') 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)