#*****************************************************************************#
#* *#
#* 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('''
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 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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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('''
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 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('''
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('''
The value of explicit object is returned and not
put in a tuple.
''')
def get(self):
return Sequence.get(self)[0]
_addFragment('''
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('''
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('''
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('''
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('''
Encodes the value in DER and returns it as a string.
''')
def toString(self):
buf = cStringIO.StringIO()
self.write(buf)
return buf.getvalue()
_addFragment('''
Decodes the string and sets the value of this object.
''')
def fromString(self, value):
buf = cStringIO.StringIO(value)
self.read(buf)
_addFragment('''
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('''
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)