# -*- coding: UTF-8 -*- #/** # * Software Name : pycrate # * Version : 0.4 # * # * Copyright 2017. Benoit Michau. ANSSI. # * Copyright 2018. Benoit Michau. P1Sec. # * # * This library is free software; you can redistribute it and/or # * modify it under the terms of the GNU Lesser General Public # * License as published by the Free Software Foundation; either # * version 2.1 of the License, or (at your option) any later version. # * # * This library is distributed in the hope that it will be useful, # * but WITHOUT ANY WARRANTY; without even the implied warranty of # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # * Lesser General Public License for more details. # * # * You should have received a copy of the GNU Lesser General Public # * License along with this library; if not, write to the Free Software # * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # * MA 02110-1301 USA # * # *-------------------------------------------------------- # * File Name : pycrate_asn1rt/asnobj_str.py # * Created : 2017-01-31 # * Authors : Benoit Michau # *-------------------------------------------------------- #*/ from .utils import * from .err import * from .dictobj import * from .glob import * from .refobj import * from .setobj import * from .asnobj import * from .codecs import * from .codecs import _with_json try: from datetime import datetime, timedelta _with_datetime = True # required for UTC decay encoding in GeneralizedTime except ImportError: _with_datetime = False try: from time import strptime, asctime _with_time = True except ImportError: _with_time = False #------------------------------------------------------------------------------# # BIT STRING and OCTET STRING #------------------------------------------------------------------------------# class BIT_STR(ASN1Obj): __doc__ = """ ASN.1 basic type BIT STRING object Single value: Python 2-tuple of int 1st int is the unsigned integral value, 2nd int is the length in bits Alternative single value: Python set of str (from the object's NamedBitList) This is only to be used in set_val() method, and is converted to a Python 2-tuple of int when set Alternative single value: Python 2-tuple the 1st item corresponds to a reference to another ASN.1 object, it can be: - a str corresponding to an ASN.1 typeref taken from the CONTAINING constraint of self - a 2-tuple (module_name, object_name) corresponding to any user-defined ASN.1 object and the 2nd item corresponds to a single value compliant to the object referenced in the 1st item Specific attribute: - cont: None or ASN1Dict {ident (str): bit offset position (int)}, provides the content of the BIT STRING object Specific constraints attributes: - const_sz: None or ASN1Set (TYPE_INT), provides the set of sizes that constraints the type - const_cont: None or ASN1Obj, provides the contained object - const_cont_enc: None or OID value, only set if const_cont is set, provides an OID for a specific codec %s """ % ASN1Obj_docstring _const_sz = None _const_cont = None TYPE = TYPE_BIT_STR TAG = 3 _ASN_RE = re.compile('(?:\'([\s01]{0,})\'B)|(?:\'([\s0-9A-F]{0,})\'H)') # _ASN_WASC potentially add the ascii representation of the BIT STRING in comment # when returned by _to_asn1() _ASN_WASC = True def _get_val_obj(self, ref): if isinstance(ref, str_types) and self._const_cont: if self._const_cont._typeref: ident = self._const_cont._typeref.called[1] else: ident = self._const_cont.TYPE if ref == ident: return self._const_cont else: raise(ASN1ObjErr('{0}: invalid object reference, {1!r}'\ .format(self.fullname(), ref))) else: try: return GLOBAL.MOD[ref[0]][ref[1]] except: raise(ASN1ObjErr('{0}: invalid object reference, {1!r}'\ .format(self.fullname(), ref))) def _safechk_val(self, val): if isinstance(val, tuple) and len(val) == 2: if isinstance(val[0], integer_types): # raw value if not isinstance(val[1], integer_types): raise(ASN1ObjErr('{0}: invalid value, {1!r}'.format(self.fullname(), val))) else: # CONTAINING value self._get_val_obj(val[0])._safechk_val(val[1]) else: raise(ASN1ObjErr('{0}: invalid value, {1!r}'.format(self.fullname(), val))) def _safechk_bnd(self, val): if isinstance(val[0], integer_types): # check val against potential constraints ASN1Obj._safechk_bnd(self, val) if self._const_sz and \ self._const_sz.ext is None and \ val[1] not in self._const_sz: raise(ASN1ObjErr('{0}: value out of size constraint, {1!r}'\ .format(self.fullname(), val))) elif self._const_cont: if self._const_cont._typeref: ident = self._const_cont._typeref.called[1] else: ident = self._const_cont.TYPE if val[0] != ident: raise(ASN1ObjErr('{0}: value out of containing constraint, {1!r}'\ .format(self.fullname(), val))) def set_val(self, val): if isinstance(val, set): off = [] for name in val: try: off.append(self._cont[name]) except: raise(ASN1ObjErr('{0}: invalid named bit, {1!r}'.format(self.fullname(), val))) if off: moff = max(off) val = (sum([1<<(moff-i) for i in off]), 1+moff) else: moff = 0 val = (0, 0) if self._const_sz and self._const_sz.ext is None \ and self._const_sz.lb and val[1] < self._const_sz.lb: # need to extend the value to the lower bound of the size constraint diff = self._const_sz.lb - val[1] self._val = (val[0] << diff, val[1] + diff) else: self._val = val else: self._val = val if self._SAFE_VAL: self._safechk_val(self._val) if self._SAFE_BND: self._safechk_bnd(self._val) def get_names(self): """Returns the set of names from the NamedBitList corresponding to the internal value currently set """ if isinstance(self._val, set): return self._val names = set() if self._cont is None or not isinstance(self._val, tuple) \ or len(self._val) != 2 or not isinstance(self._val[0], integer_types): # self._val has not the correct format return names for off, bit in enumerate(uint_to_bitstr(self._val[0], self._val[1])): if bit == '1': names.add(self._cont_rev[off]) return names ### # conversion between internal value and ASN.1 syntax ### def _from_asn1(self, txt): m = self._ASN_RE.match(txt) if m: grp = m.groups() if grp[0] is not None: # BSTRING bs = re.subn('\s{1,}', '', grp[0])[0] if not bs: # null length bit string self._val = (0, 0) else: self._val = (int(bs, 2), len(bs)) else: # HSTRING hs = re.subn('\s{1,}', '', grp[1])[0] if not hs: self._val = (0, 0) else: self._val = (int(hs, 16), 4*len(hs)) return txt[m.end():].strip() elif txt[:1] == '{' and self._cont: if not hasattr(self, '_ASN_RE_CONT'): kw = '|'.join(self._cont.keys()) self._ASN_RE_CONT = re.compile( '\{((?:\s{0,}(?:' + kw + ')\s{0,},){0,}(?:\s{0,}(?:' + kw + ')\s{0,}){0,1})\}') m = self._ASN_RE_CONT.match(txt) if m: # named offsets off = set(map(str.strip, m.group(1).split(','))) # converting to integral offsets (starting from 0) off = [self._cont[no] for no in off] moff = max(off) self._val = (sum([1<<(moff-i) for i in off]), 1+moff) return txt[m.end():].strip() elif self._const_cont: # CHOICE-like value notation if self._const_cont._typeref: ident = self._const_cont._typeref.called[1] else: ident = self._const_cont.TYPE m = re.match('%s\s{0,}:' % ident, txt) if m: txt = txt[m.end():].strip() txt = self._const_cont.from_asn1(txt) self._val = (ident, self._const_cont._val) return txt raise(ASN1ASNDecodeErr('{0}: invalid text, {1!r}'.format(self.fullname(), txt))) def _to_asn1(self): if isinstance(self._val[0], integer_types): uint = Uint('bs', val=self._val[0], bl=self._val[1]) if self._val[1] % 4 == 0: # HSTRING ret = '\'%s\'H' % uint_to_hex(self._val[0], self._val[1]).upper() else: # BSTRING ret = '\'%s\'B' % uint_to_bitstr(self._val[0], self._val[1]) if self._cont: # add flags in comment flags = [] for i, v in enumerate(uint_to_bitstr(self._val[0], self._val[1])): if i in self._cont_rev and v == '1': flags.append(self._cont_rev[i]) return ret + ' -- %s --' % ' | '.join(flags) elif self._ASN_WASC and self._val[1] % 8 == 0: # eventually add printable repr try: if python_version < 3: s = uint_to_bytes(self._val[0], self._val[1]) else: s = uint_to_bytes(self._val[0], self._val[1]).decode('ascii') if is_printable(s): return ret + ' -- %s --' % s else: return ret except: return ret else: return ret elif self._const_cont: # CHOICE-like value notation if self._const_cont._typeref: ident = self._const_cont._typeref.called[1] else: ident = self._const_cont.TYPE if self._val[0] == ident: self._const_cont._val = self._val[1] return '%s: %s' % (self._val[0], self._const_cont._to_asn1()) raise(ASN1ASNEncodeErr('{0}: non-encodable value, {1!r}'\ .format(self.fullname(), self._val))) ### # conversion between internal value and ASN.1 unaligned PER encoding ### def _from_per_ws(self, char): GEN, ldet = [], None if self._const_sz: if self._const_sz._ev is not None: E = Uint('E', bl=1) E._from_char(char) GEN.append(E) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 if E(): # 1) size in the extension part # decoded as unconstraint integer self.__from_per_ws_szunconst(char, GEN) return # size in the root part if self._const_sz.rdyn: # 2) defined range of possible sizes if self._const_sz.ub >= 65536: self.__from_per_ws_szunconst(char, GEN) return else: ldet, _gen = ASN1CodecPER.decode_intconst_ws(char, self._const_sz) GEN.extend(_gen) if ASN1CodecPER.ALIGNED: # realignment if ASN1CodecPER._off[-1] % 8: GEN.extend( ASN1CodecPER.decode_pad_ws(char) ) ASN1CodecPER._off[-1] += ldet V = Buf('V', bl=ldet, rep=REPR_BIN) V._from_char(char) GEN.append(V) self.__from_per_ws_buf(V) self._struct = Envelope(self._name, GEN=tuple(GEN)) return elif self._const_sz.rdyn == 0: # 3) size has a single possible size (damned ASN.1 !!!) if self._const_sz.ub >= 65536: self.__from_per_ws_szunconst(char, GEN) return else: ldet = self._const_sz.lb if ASN1CodecPER.ALIGNED: if ldet > 16 and ASN1CodecPER._off[-1] % 8: # realignment GEN.extend( ASN1CodecPER.decode_pad_ws(char) ) ASN1CodecPER._off[-1] += ldet V = Buf('V', bl=ldet, rep=REPR_BIN) V._from_char(char) GEN.append(V) self.__from_per_ws_buf(V) self._struct = Envelope(self._name, GEN=tuple(GEN)) return if ldet is None: # 4) size is semi-constrained or has no constraint # decoded as unconstrained integer self.__from_per_ws_szunconst(char, GEN) return assert() def __from_per_ws_szunconst(self, char, GEN): # size is semi-constrained or unconstrained # anyway, it is decoded as unconstrained integer if ASN1CodecPER.ALIGNED and ASN1CodecPER._off[-1] % 8: GEN.extend( ASN1CodecPER.decode_pad_ws(char) ) ldet, _gen = ASN1CodecPER.decode_count_ws(char) GEN.extend(_gen) if ldet in (65536, 49152, 32768, 16384): # requires defragmentation (buf, ldet), _gen = ASN1CodecPER.decode_fragbytes_ws(char, ldet, bits=True) GEN.extend(_gen) V = Buf('V', val=buf, bl=ldet) else: # use Buf() structure for storing the content V = Buf('V', bl=ldet, rep=REPR_BIN) V._from_char(char) GEN.append(V) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += ldet self.__from_per_ws_buf(V) self._struct = Envelope(self._name, GEN=tuple(GEN)) def __from_per_ws_buf(self, Buf): # set the BIT STRING value according to Buf instance if self._const_cont is not None: if self._const_cont_enc is not None: # TODO: different codec to be used if not self._SILENT: asnlog('BIT_STR.__from_per_ws_buf: %s, specific CONTAINING encoder unhandled'\ % self._name) if Buf._bl: self._val = (Buf.to_uint(), Buf.get_bl()) else: self._val = (0, 0) else: char = Charpy(Buf()) char._len_bit = Buf.get_bl() try: if ASN1CodecPER.ALIGNED: self._const_cont.from_aper_ws(char) else: self._const_cont.from_uper_ws(char) except: if not self._SILENT: asnlog('BIT_STR.__from_per_ws_buf: %s, CONTAINING object decoding failed'\ % self._name) self._val = (bytes_to_uint(buf, bl), bl) else: if self._const_cont._typeref: ident = self._const_cont._typeref.called[1] else: ident = self._const_cont.TYPE self._val = (ident, self._const_cont._val) else: if Buf._bl: self._val = (Buf.to_uint(), Buf.get_bl()) else: self._val = (0, 0) def _from_per(self, char): ldet = None if self._const_sz: if self._const_sz._ev is not None: E = char.get_uint(1) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 if E: # 1) size in the extension part # decoded as unconstraint integer self.__from_per_szunconst(char) return # size in the root part if self._const_sz.rdyn: # 2) defined range of possible sizes if self._const_sz.ub >= 65536: self.__from_per_szunconst(char) return else: ldet = ASN1CodecPER.decode_intconst(char, self._const_sz) if ASN1CodecPER.ALIGNED: # realignment if ASN1CodecPER._off[-1] % 8: ASN1CodecPER.decode_pad(char) ASN1CodecPER._off[-1] += ldet buf = char.get_bytes(ldet) self.__from_per_buf(buf, ldet) return elif self._const_sz.rdyn == 0: # 3) size has a single possible size if self._const_sz.ub >= 65536: self.__from_per_szunconst(char) return else: ldet = self._const_sz.lb if ASN1CodecPER.ALIGNED: if ldet > 16 and ASN1CodecPER._off[-1] % 8: # realignment ASN1CodecPER.decode_pad(char) ASN1CodecPER._off[-1] += ldet buf = char.get_bytes(ldet) self.__from_per_buf(buf, ldet) return if ldet is None: # 4) size is semi-constrained or has no constraint # decoded as unconstrained integer self.__from_per_szunconst(char) return assert() def __from_per_szunconst(self, char): # size is semi-constrained or unconstrained # anyway, it is decoded as unconstrained integer if ASN1CodecPER.ALIGNED and ASN1CodecPER._off[-1] % 8: ASN1CodecPER.decode_pad(char) ldet = ASN1CodecPER.decode_count(char) if ldet in (65536, 49152, 32768, 16384): # requires defragmentation buf, ldet = ASN1CodecPER.decode_fragbytes(char, ldet, bits=True) else: # use Buf() structure for storing the content buf = char.get_bytes(ldet) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += ldet self.__from_per_buf(buf, ldet) def __from_per_buf(self, buf, bl): # set the BIT STRING value according to buf and bit length if self._const_cont is not None: if self._const_cont_enc is not None: # TODO: different codec to be used if not self._SILENT: asnlog('BIT_STR.__from_per_buf: %s, specific CONTAINING encoder unhandled'\ % self._name) if bl: self._val = (bytes_to_uint(buf, bl), bl) else: # empty bit string self._val = (0, 0) else: char = Charpy(buf) char._len_bit = bl try: if ASN1CodecPER.ALIGNED: self._const_cont.from_aper(char) else: self._const_cont.from_uper(char) except: if not self._SILENT: asnlog('BIT_STR.__from_per_buf: %s, CONTAINING object decoding failed'\ % self._name) if bl: self._val = (bytes_to_uint(buf, bl), bl) else: # empty bit string self._val = (0, 0) else: if self._const_cont._typeref: ident = self._const_cont._typeref.called[1] else: ident = self._const_cont.TYPE self._val = (ident, self._const_cont._val) else: if bl: self._val = (bytes_to_uint(buf, bl), bl) else: # empty bit string self._val = (0, 0) def _to_per_ws(self): buf, ldet = self.__to_per_ws_buf() GEN = [] if self._const_sz: if self._const_sz._ev is not None: if not self._const_sz.in_root(ldet): # 1) size in the extension part # encoded as unconstraint integer GEN.append( Uint('E', val=1, bl=1) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 self.__to_per_ws_szunconst(buf, ldet, GEN) return self._struct else: GEN.append( Uint('E', val=0, bl=1) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 # size in the root part if self._const_sz.rdyn: # 2) defined range of possible sizes if self._const_sz.ub >= 65536: self.__to_per_ws_szunconst(buf, ldet, GEN) return self._struct else: GEN.extend( ASN1CodecPER.encode_intconst_ws(ldet, self._const_sz, name='C') ) if ASN1CodecPER.ALIGNED: # realignment if ASN1CodecPER._off[-1] % 8: GEN.extend( ASN1CodecPER.encode_pad_ws() ) ASN1CodecPER._off[-1] += ldet GEN.append( Buf('V', val=buf, bl=ldet, rep=REPR_BIN) ) self._struct = Envelope(self._name, GEN=tuple(GEN)) return self._struct elif self._const_sz.rdyn == 0: # 3) size has a single possible size if self._const_sz.ub >= 65536: self.__to_per_ws_szunconst(buf, ldet, GEN) return self._struct else: if ASN1CodecPER.ALIGNED: if ldet > 16 and ASN1CodecPER._off[-1] % 8: # realignment GEN.extend( ASN1CodecPER.encode_pad_ws() ) ASN1CodecPER._off[-1] += ldet GEN.append( Buf('V', val=buf, bl=ldet, rep=REPR_BIN) ) self._struct = Envelope(self._name, GEN=tuple(GEN)) return self._struct # 4) size is semi-constrained or has no constraint # encoded as unconstrained integer self.__to_per_ws_szunconst(buf, ldet, GEN) return self._struct def __to_per_ws_buf(self): # convert the value into a buffer and length in bits if not isinstance(self._val[0], integer_types): # 1) value is for a contained object to be encoded Cont = self._get_val_obj(self._val[0]) if Cont == self._const_cont and self._const_cont_enc is not None: # TODO: different codec to be used raise(ASN1NotSuppErr('{0}: specific CONTAINING encoder unhandled'\ .format(self.fullname()))) Cont._val = self._val[1] if ASN1CodecPER.ALIGNED: buf = Cont.to_aper_ws() else: buf = Cont.to_uper_ws() #Cont._val = None return buf, 8*len(buf) else: # 2) value is the standard (uint, bit length) if self._val[1]: return uint_to_bytes(self._val[0], self._val[1]), self._val[1] else: # empty bit string return b'', 0 def __to_per_ws_szunconst(self, buf, bl, GEN): # size is semi-constrained or unconstrained # anyway, it is encoded as unconstrained integer if ASN1CodecPER.ALIGNED and ASN1CodecPER._off[-1] % 8: GEN.extend( ASN1CodecPER.encode_pad_ws() ) if bl >= 16384: # requires fragmentation GEN.extend( ASN1CodecPER.encode_fragbytes_ws(buf, bits=bl) ) else: GEN.extend( ASN1CodecPER.encode_count_ws(bl) ) # use Buf() structure for storing the content GEN.append( Buf('V', val=buf, bl=bl, rep=REPR_BIN) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += bl self._struct = Envelope(self._name, GEN=tuple(GEN)) def _to_per(self): buf, ldet = self.__to_per_buf() GEN = [] if self._const_sz: if self._const_sz._ev is not None: if not self._const_sz.in_root(ldet): # 1) size in the extension part # encoded as unconstraint integer GEN.append( (T_UINT, 1, 1) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 self.__to_per_szunconst(buf, ldet, GEN) return GEN else: GEN.append( (T_UINT, 0, 1) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 # size in the root part if self._const_sz.rdyn: # 2) defined range of possible sizes if self._const_sz.ub >= 65536: self.__to_per_szunconst(buf, ldet, GEN) return GEN else: GEN.extend( ASN1CodecPER.encode_intconst(ldet, self._const_sz) ) if ASN1CodecPER.ALIGNED: if ASN1CodecPER._off[-1] % 8: # realignment GEN.extend( ASN1CodecPER.encode_pad() ) ASN1CodecPER._off[-1] += ldet GEN.append( (T_BYTES, buf, ldet) ) return GEN elif self._const_sz.rdyn == 0: # 3) size has a single possible size if self._const_sz.ub >= 65536: self.__to_per_szunconst(buf, ldet, GEN) return GEN else: if ASN1CodecPER.ALIGNED: if ldet > 16 and ASN1CodecPER._off[-1] % 8: # realignment GEN.extend( ASN1CodecPER.encode_pad() ) ASN1CodecPER._off[-1] += ldet GEN.append( (T_BYTES, buf, ldet) ) return GEN # 4) size is semi-constrained or has no constraint # encoded as unconstrained integer self.__to_per_szunconst(buf, ldet, GEN) return GEN def __to_per_buf(self): # convert the value into a buffer and length in bits if not isinstance(self._val[0], integer_types): # 1) value is for a contained object to be encoded Cont = self._get_val_obj(self._val[0]) if Cont == self._const_cont and self._const_cont_enc is not None: # TODO: different codec to be used raise(ASN1NotSuppErr('{0}: specific CONTAINING encoder unhandled'\ .format(self.fullname()))) Cont._val = self._val[1] if ASN1CodecPER.ALIGNED: buf = Cont.to_aper() else: buf = Cont.to_uper() #Cont._val = None return buf, 8*len(buf) else: # 2) value is the standard (uint, bit length) if self._val[1]: return uint_to_bytes(self._val[0], self._val[1]), self._val[1] else: # empty bit string return b'', 0 def __to_per_szunconst(self, buf, bl, GEN): # size is semi-constrained or unconstrained # anyway, it is encoded as unconstrained integer if ASN1CodecPER.ALIGNED and ASN1CodecPER._off[-1] % 8: GEN.extend( ASN1CodecPER.encode_pad() ) if bl >= 16384: # requires fragmentation GEN.extend( ASN1CodecPER.encode_fragbytes(buf, bits=bl) ) else: GEN.extend( ASN1CodecPER.encode_count(bl) ) # use Buf() structure for storing the content GEN.append( (T_BYTES, buf, bl) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += bl ### # conversion between internal value and ASN.1 BER encoding ### def _decode_ber_cont_ws(self, char, vs): if isinstance(vs, list): # constructed / fragmented form # vs: list of TLV Frag, bsfrag = [], [] for tlv in vs: Tag, cl, pc, tval, Len, lval = tlv[0:6] if pc != 0: # fragmenting the fragment... damned BER recursivity ! raise(ASN1NotSuppErr('{0}: BIT STRING fragments of fragments'\ .format(self.fullname()))) elif cl != 0: raise(ASN1BERDecodeErr('{0}: invalid BIT STRING fragment tag class, {1!r}'\ .format(self.fullname(), cl))) elif (tval, lval) == (0, 0): # EOC marker assert( tlv == v[-1] ) Frag.append( Envelope('EOC', GEN=(Tag, Len)) ) elif lval == 0: raise(ASN1BERDecodeErr('{0}: invalid BIT STRING fragment length'\ .format(self.fullname()))) elif tval != 3: raise(ASN1BERDecodeErr('{0}: invalid BIT STRING fragment tag value, {1!r}'\ .format(self.fullname(), tval))) else: char._cur, char._len_bit = tlv[-1][0], tlv[-1][1] Bu = Uint8('BU') Bu._from_char(char) bu = Bu() if bu > 7: raise(ASN1BERDecodeErr('{0}: invalid BIT STRING counter for unused bits'\ .format(self.fullname()))) Bs = Buf('BS', bl=tlv[-1][1]-tlv[-1][0]-8, rep=REPR_HEX) Bs._from_char(char) # concat the fragment of bit string bsfrag.append( (T_BYTES, Bs.to_bytes(), tlv[-1][1]-tlv[-1][0]-8-bu) ) # generate the fragment envelope Frag.append( Envelope('TLV', GEN=(Tag, Len, Envelope('V', GEN=(Bu, Bs)))) ) # generate the complete V envelope V = Envelope('V', GEN=tuple(Frag)) # process the defragmented bit string self.__from_ber_buf( *pack_val(*bsfrag) ) else: # primitive form # vs: value boundary 2-tuple if vs[1]-vs[0] < 8: raise(ASN1BERDecodeErr('{0}: invalid BIT STRING length'\ .format(self.fullname()))) char._cur, char._len_bit = vs[0], vs[1] Bu = Uint8('BU') Bu._from_char(char) bu = Bu() if bu > 7: raise(ASN1BERDecodeErr('{0}: invalid BIT STRING counter for unused bits'\ .format(self.fullname()))) Bs = Buf('BS', bl=vs[1]-vs[0]-8, rep=REPR_HEX) Bs._from_char(char) # generate the V envelope V = Envelope('V', GEN=(Bu, Bs)) # process the bit string self.__from_ber_buf( Bs.to_bytes(), vs[1]-vs[0]-8-bu ) return V def _decode_ber_cont(self, char, vs): if isinstance(vs, list): # constructed / fragmented form # vs: list of TLV bsfrag = [] for tlv in vs: cl, pc, tval, lval = tlv[0:4] if pc != 0: # fragmenting the fragment... damned BER recursivity ! raise(ASN1NotSuppErr('{0}: BIT STRING fragments of fragments'\ .format(self.fullname()))) elif cl != 0: raise(ASN1BERDecodeErr('{0}: invalid BIT STRING fragment tag class, {1!r}'\ .format(self.fullname(), cl))) elif (tval, lval) == (0, 0): # EOC marker assert( tlv == v[-1] ) pass elif lval == 0: raise(ASN1BERDecodeErr('{0}: invalid BIT STRING fragment length'\ .format(self.fullname()))) elif tval != 3: raise(ASN1BERDecodeErr('{0}: invalid BIT STRING fragment tag value, {1!r}'\ .format(self.fullname(), tval))) else: char._cur, char._len_bit = tlv[-1][0], tlv[-1][1] bu = char.get_uint(8) if bu > 7: raise(ASN1BERDecodeErr('{0}: invalid BIT STRING counter for unused bits'\ .format(self.fullname()))) bs = char.get_bytes(8*(lval-1)) # concat the fragment of bit string bsfrag.append( (T_BYTES, bs, tlv[-1][1]-tlv[-1][0]-8-bu) ) # process the defragmented bit string self.__from_ber_buf( *pack_val(*bsfrag) ) else: # primitive form # vs: value boundary 2-tuple if vs[1]-vs[0] < 8: raise(ASN1BERDecodeErr('{0}: invalid BIT STRING length'\ .format(self.fullname()))) char._cur, char._len_bit = vs[0], vs[1] bu = char.get_uint(8) if bu > 7: raise(ASN1BERDecodeErr('{0}: invalid BIT STRING counter for unused bits'\ .format(self.fullname()))) bs = char.get_bytes(vs[1]-vs[0]-8) # process the bit string self.__from_ber_buf( bs, vs[1]-vs[0]-8-bu ) def __from_ber_buf(self, buf, bl): # set the BIT STRING value according to buf and bit length if self._const_cont is not None: if self._const_cont_enc is not None: # TODO: different codec to be used if not self._SILENT: asnlog('BIT_STR.__from_ber_buf: %s, specific CONTAINING encoder unhandled'\ % self._name) if bl: self._val = (bytes_to_uint(buf, bl), bl) else: # empty bit string self._val = (0, 0) else: Obj, char = self._const_cont, Charpy(buf) char._len_bit = bl _const_cont_par = Obj._parent Obj._parent = self._parent try: Obj.from_ber(char, single=False) except: if not self._SILENT: asnlog('BIT_STR.__from_ber_buf: %s, CONTAINING object decoding failed'\ % self._name) if bl: self._val = (bytes_to_uint(buf, bl), bl) else: # empty bit string self._val = (0, 0) else: if Obj._typeref: ident = Obj._typeref.called[1] else: ident = Obj.TYPE self._val = (ident, self._const_cont._val) else: if bl: self._val = (bytes_to_uint(buf, bl), bl) else: # empty bit string self._val = (0, 0) def _encode_ber_cont_ws(self): buf, bl = self.__to_ber_buf() if bl % 8: bu = 8 - (bl%8) else: bu = 0 if ASN1CodecBER.ENC_BSTR_FRAG and len(buf) >= ASN1CodecBER.ENC_BSTR_FRAG: # fragmentation required Frag, lval = [], 0 for i in range(0, len(buf), ASN1CodecBER.ENC_BSTR_FRAG): frag = buf[i:i+ASN1CodecBER.ENC_BSTR_FRAG] TLV = Envelope('TLV', GEN=( ASN1CodecBER.encode_tag_ws(0, 0, 3), ASN1CodecBER.encode_len_ws(1+len(frag)), Envelope('V', GEN=(Uint8('BU', val=0), Buf('BS', val=frag, rep=REPR_HEX))))) Frag.append(TLV) lval += 2 + (TLV[1].get_bl() >> 3) + len(frag) # set the bits unused BU in the last fragment Frag[-1][2][0]._val = bu return 1, lval, Envelope('V', GEN=tuple(Frag)) else: lval = 1+len(buf) return 0, lval, Envelope('V', GEN=(Uint8('BU', val=bu), Buf('BS', val=buf, bl=8*len(buf), rep=REPR_HEX))) def _encode_ber_cont(self): buf, bl = self.__to_ber_buf() if bl % 8: bu = 8 - (bl%8) else: bu = 0 if ASN1CodecBER.ENC_BSTR_FRAG and len(buf) >= ASN1CodecBER.ENC_BSTR_FRAG: # fragmentation required TLV, lval = [], 0 for i in range(0, len(buf), ASN1CodecBER.ENC_BSTR_FRAG): frag = buf[i:i+ASN1CodecBER.ENC_BSTR_FRAG] TLV.extend( ASN1CodecBER.encode_tag(0, 0, 3) ) L = ASN1CodecBER.encode_len(1+len(frag)) TLV.extend( L ) TLV.append( (T_UINT, 0, 8) ) TLV.append( (T_BYTES, frag, 8*len(frag)) ) lval += 2 + (sum([f[2] for f in L]) >> 3) + len(frag) # set the bits unused BU in the last fragment TLV[-2] = (T_UINT, bu, 8) return 1, lval, TLV else: return 0, 1 + len(buf), [ (T_UINT, bu, 8), (T_BYTES, buf, 8*len(buf)) ] def __to_ber_buf(self): # convert the value into a buffer and length in bits if not isinstance(self._val[0], integer_types): # 1) value is for a contained object to be encoded Cont = self._get_val_obj(self._val[0]) if Cont == self._const_cont and self._const_cont_enc is not None: # TODO: different codec to be used raise(ASN1NotSuppErr('{0}: specific CONTAINING encoder unhandled'\ .format(self.fullname()))) Cont._val = self._val[1] buf = Cont.to_ber() return buf, 8*len(buf) else: # 2) value is the standard (uint, bit length) if self._val[1]: return uint_to_bytes(self._val[0], self._val[1]), self._val[1] else: # empty bit string return b'', 0 ### # conversion between internal value and ASN.1 JER encoding ### if _with_json: def _from_jval(self, val): if isinstance(val, str_types): # ensure the sz constraint is fixed if not self._const_sz or self._const_sz.ra != 1 or len(self._const_sz._rv) != 1: raise(ASN1JERDecodeErr('{0}: invalid json value, {1!r}'\ .format(self.fullname(), val))) else: try: val = int(val, 16) except ValueError: raise(ASN1JERDecodeErr('{0}: invalid json value, {1!r}'\ .format(self.fullname(), val))) bl = self._const_sz._rv[0] elif isinstance(val, dict): if 'value' in val and 'length' in val: bl = val['length'] val = int(val['value'], 16) elif self._const_cont is not None: if self._const_cont_enc is not None: # TODO: different codec to be used if not self._SILENT: raise(ASN1NotSuppErr('{0}: specific CONTAINING decoder unhandled'\ .format(self.fullname()))) Cont = self._const_cont if Cont._typeref: ident = Cont._typeref.called[1] else: ident = Cont.TYPE _par = Cont._parent Cont._parent = self try: Cont._from_jval(val[ident]) except KeyError: Cont._parent = par raise(ASN1JERDecodeErr('{0}: invalid json value, {1!r}'\ .format(self.fullname(), val))) Cont._parent = _par self._val = (ident, Cont._val) return else: raise(ASN1JERDecodeErr('{0}: invalid json value, {1!r}'\ .format(self.fullname(), val))) if bl%8: self._val = (val>>(-bl%8), bl) else: self._val = (val, bl) def _to_jval(self): if not isinstance(self._val[0], integer_types): # value is for a contained object to be encoded # using a CHOICE-like encoding Cont = self._get_val_obj(self._val[0]) if Cont == self._const_cont and self._const_cont_enc is not None: # TODO: different codec to be used raise(ASN1NotSuppErr('{0}: specific CONTAINING encoder unhandled'\ .format(self.fullname()))) Cont._val = self._val[1] _par = Cont._parent Cont._parent = self val = Cont._to_jval() Cont._parent = _par if Cont._typeref: return {Cont._typeref.called[1]: val} else: return {Cont.TYPE: val} else: val, bl = self._val bl_rnd = -bl%8 if self._const_sz and self._const_sz.ra == 1 and len(self._const_sz._rv) == 1: if bl_rnd: return uint_to_hex(val<= 3: ret = '\'%s\'H' % hexlify(self._val).decode('ascii').upper() else: ret = '\'%s\'H' % hexlify(self._val).upper() if self._ASN_WASC: # eventually add printable repr try: s = self._val.decode('ascii') if is_printable(s): return ret + ' -- %s --' % s else: return ret except: return ret else: return ret elif self._const_cont: # CHOICE-like value notation if self._const_cont._typeref: ident = self._const_cont._typeref.called[1] else: ident = self._const_cont.TYPE if self._val[0] == ident: self._const_cont._val = self._val[1] return '%s: %s' % (self._val[0], self._const_cont._to_asn1()) raise(ASN1ASNEncodeErr('{0}: non-encodable value, {1!r}'\ .format(self.fullname(), self._val))) ### # conversion between internal value and ASN.1 PER encoding ### def _from_per_ws(self, char): if self._const_sz: if self._const_sz._ev is not None: E = Uint('E', bl=1) E._from_char(char) GEN = [E] if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 if E(): # 1) size in the extension part # decoded as unconstraint self.__from_per_ws(char, GEN, unconst=True) return else: GEN = [] # size in the root part if self._const_sz.rdyn: # 2) defined range of possible sizes if self._const_sz.ub >= 65536: self.__from_per_ws(char, GEN, unconst=True) return else: self.__from_per_ws(char, GEN) return elif self._const_sz.rdyn == 0: if self._const_sz.ub >= 65536: self.__from_per_ws(char, GEN, unconst=True) return else: # 3) size has a single possible size self.__from_per_ws(char, GEN) return else: GEN = [] # 4) size is semi-constrained or has no constraint # decoded as unconstrained self.__from_per_ws(char, GEN, unconst=True) return def __from_per_ws(self, char, GEN=[], unconst=False): if self._const_cont is not None: if self._const_cont_enc is not None: # TODO: different codec to be used if not self._SILENT: asnlog('OCT_STR.__from_per_ws: %s, specific CONTAINING encoder unhandled'\ % self._name) if unconst: self._val, _gen = ASN1CodecPER.decode_unconst_open_ws(char) else: self._val, _gen = ASN1CodecPER.decode_const_open_ws(char, self._const_sz) else: Obj = self._const_cont _const_cont_par = Obj._parent Obj._parent = self._parent if unconst: val, _gen = ASN1CodecPER.decode_unconst_open_ws(char, Obj) else: val, _gen = ASN1CodecPER.decode_const_open_ws(char, self._const_sz, Obj) Obj._parent = _const_cont_par if Obj._typeref is not None: self._val = (Obj._typeref.called[1], val) else: self._val = (Obj.TYPE, val) else: if unconst: self._val, _gen = ASN1CodecPER.decode_unconst_open_ws(char) else: self._val, _gen = ASN1CodecPER.decode_const_open_ws(char, self._const_sz) self._struct = Envelope(self._name, GEN=tuple(GEN + _gen)) def _from_per(self, char): if self._const_sz: if self._const_sz._ev is not None: E = char.get_uint(1) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 if E: # 1) size in the extension part # decoded as unconstraint self.__from_per(char, unconst=True) return # size in the root part if self._const_sz.rdyn: # 2) defined range of possible sizes if self._const_sz.ub >= 65536: self.__from_per(char, unconst=True) return else: self.__from_per(char) return elif self._const_sz.rdyn == 0: if self._const_sz.ub >= 65536: self.__from_per(char, unconst=True) return else: # 3) size has a single possible size self.__from_per(char) return else: GEN = [] # 4) size is semi-constrained or has no constraint # decoded as unconstrained self.__from_per(char, unconst=True) return def __from_per(self, char, unconst=False): if self._const_cont is not None: if self._const_cont_enc is not None: # TODO: different codec to be used if not self._SILENT: asnlog('OCT_STR.__from_per: %s, specific CONTAINING encoder unhandled'\ % self._name) if unconst: self._val = ASN1CodecPER.decode_unconst_open(char) else: self._val = ASN1CodecPER.decode_const_open(char, self._const_sz) else: Obj = self._const_cont _const_cont_par = Obj._parent Obj._parent = self._parent if unconst: val = ASN1CodecPER.decode_unconst_open(char, Obj) else: val = ASN1CodecPER.decode_const_open(char, self._const_sz, Obj) Obj._parent = _const_cont_par if Obj._typeref is not None: self._val = (Obj._typeref.called[1], val) else: self._val = (Obj.TYPE, val) else: if unconst: self._val = ASN1CodecPER.decode_unconst_open(char) else: self._val = ASN1CodecPER.decode_const_open(char, self._const_sz) # TODO: _to_per_ws() does not copy the structure of a potential wrapped # object into self._struct def _to_per_ws(self): if not isinstance(self._val, bytes_types): buf, wrapped = self.__to_per_ws_buf() else: buf, wrapped = self._val, None GEN = [] if self._const_sz: if self._const_sz._ev is not None: if not self._const_sz.in_root(len(buf)): # 1) size in the extension part # encoded as unconstraint GEN.append( Uint('E', val=1, bl=1) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 GEN.extend( ASN1CodecPER.encode_unconst_buf_ws(buf) ) self._struct = Envelope(self._name, GEN=tuple(GEN)) return self._struct else: GEN.append( Uint('E', val=0, bl=1) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 # size in the root part if self._const_sz.rdyn: # 2) defined range of possible sizes if self._const_sz.ub >= 65536: GEN.extend( ASN1CodecPER.encode_unconst_buf_ws(buf) ) self._struct = Envelope(self._name, GEN=tuple(GEN)) return self._struct else: GEN.extend( ASN1CodecPER.encode_const_buf_ws(buf, self._const_sz) ) self._struct = Envelope(self._name, GEN=tuple(GEN)) return self._struct elif self._const_sz.rdyn == 0: # 3) size has a single possible size if self._const_sz.ub >= 65536: GEN.extend( ASN1CodecPER.encode_unconst_buf_ws(buf) ) self._struct = Envelope(self._name, GEN=tuple(GEN)) return self._struct else: GEN.extend( ASN1CodecPER.encode_const_buf_ws(buf, self._const_sz) ) self._struct = Envelope(self._name, GEN=tuple(GEN)) return self._struct # 4) size is semi-constrained or has no constraint # encoded as unconstrained integer GEN.extend( ASN1CodecPER.encode_unconst_buf_ws(buf) ) self._struct = Envelope(self._name, GEN=tuple(GEN)) return self._struct def __to_per_ws_buf(self): # convert the contained object value into a buffer Cont = self._get_val_obj(self._val[0]) if Cont == self._const_cont and self._const_cont_enc is not None: # TODO: different codec to be used raise(ASN1NotSuppErr('{0}: specific CONTAINING encoder unhandled'\ .format(self.fullname()))) Cont._val = self._val[1] if ASN1CodecPER.ALIGNED: buf = Cont.to_aper_ws() else: buf = Cont.to_uper_ws() #Cont._val = None return buf, Cont def _to_per(self): if not isinstance(self._val, bytes_types): buf, wrapped = self.__to_per_buf() else: buf, wrapped = self._val, None GEN = [] if self._const_sz: if self._const_sz._ev is not None: if not self._const_sz.in_root(len(buf)): # 1) size in the extension part # encoded as unconstraint GEN.append( (T_UINT, 1, 1) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 GEN.extend( ASN1CodecPER.encode_unconst_buf(buf) ) return GEN else: GEN.append( (T_UINT, 0, 1) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 # size in the root part if self._const_sz.rdyn: # 2) defined range of possible sizes if self._const_sz.ub >= 65536: GEN.extend( ASN1CodecPER.encode_unconst_buf(buf) ) return GEN else: GEN.extend( ASN1CodecPER.encode_const_buf(buf, self._const_sz) ) return GEN elif self._const_sz.rdyn == 0: # 3) size has a single possible size if self._const_sz.ub >= 65536: GEN.extend( ASN1CodecPER.encode_unconst_buf(buf) ) return GEN else: GEN.extend( ASN1CodecPER.encode_const_buf(buf, self._const_sz) ) return GEN # 4) size is semi-constrained or has no constraint # encoded as unconstrained integer GEN.extend( ASN1CodecPER.encode_unconst_buf(buf) ) return GEN def __to_per_buf(self): # convert the contained object value into a buffer Cont = self._get_val_obj(self._val[0]) if Cont == self._const_cont and self._const_cont_enc is not None: # TODO: different codec to be used raise(ASN1NotSuppErr('{0}: specific CONTAINING encoder unhandled'\ .format(self.fullname()))) Cont._val = self._val[1] if ASN1CodecPER.ALIGNED: buf = Cont.to_aper() else: buf = Cont.to_uper() #Cont._val = None return buf, Cont ### # conversion between internal value and ASN.1 BER encoding ### def _decode_ber_cont_ws(self, char, vs): if isinstance(vs, list): # constructed / fragmented form # vs: list of TLV Frag, osfrag = [], [] for tlv in vs: Tag, cl, pc, tval, Len, lval = tlv[0:6] if pc != 0: # fragmenting the fragment... damned BER recursivity ! raise(ASN1NotSuppErr('{0}: OCTET STRING fragments of fragments'\ .format(self.fullname()))) elif cl != 0: raise(ASN1BERDecodeErr('{0}: invalid OCTET STRING fragment tag class, {1!r}'\ .format(self.fullname(), cl))) elif (tval, lval) == (0, 0): # EOC marker assert( tlv == v[-1] ) Frag.append( Envelope('EOC', GEN=(Tag, Len)) ) elif tval != 4: raise(ASN1BERDecodeErr('{0}: invalid OCTET STRING fragment tag value, {1!r}'\ .format(self.fullname(), tval))) else: char._cur, char._len_bit = tlv[-1][0], tlv[-1][1] Val = Buf('V', bl=tlv[-1][1]-tlv[-1][0], rep=REPR_HEX) Val._from_char(char) osfrag.append( Val.to_bytes() ) Frag.append( Envelope('TLV', GEN=(Tag, Len, Val)) ) # generate the complete V envelope V = Envelope('V', GEN=tuple(Frag)) # process the defragmented octet string self.__from_ber_buf( b''.join(osfrag) ) else: # primitive form # vs: value boundary 2-tuple char._cur, char._len_bit = vs[0], vs[1] V = Buf('V', bl=vs[1]-vs[0], rep=REPR_HEX) V._from_char(char) # process the octet string self.__from_ber_buf( V.to_bytes() ) return V def _decode_ber_cont(self, char, vs): if isinstance(vs, list): # constructed / fragmented form # vs: list of TLV osfrag = [] for tlv in vs: cl, pc, tval, lval = tlv[0:4] if pc != 0: # fragmenting the fragment... damned BER recursivity ! raise(ASN1NotSuppErr('{0}: OCTET STRING fragments of fragments'\ .format(self.fullname()))) elif cl != 0: raise(ASN1BERDecodeErr('{0}: invalid OCTET STRING fragment tag class, {1!r}'\ .format(self.fullname(), cl))) elif (tval, lval) == (0, 0): # EOC marker assert( tlv == v[-1] ) pass elif tval != 4: raise(ASN1BERDecodeErr('{0}: invalid OCTET STRING fragment tag value, {1!r}'\ .format(self.fullname(), tval))) else: char._cur, char._len_bit = tlv[-1][0], tlv[-1][1] osfrag.append( char.get_bytes(tlv[-1][1]-tlv[-1][0]) ) # process the defragmented octet string self.__from_ber_buf( b''.join(osfrag) ) else: # primitive form # vs: value boundary 2-tuple char._cur, char._len_bit = vs[0], vs[1] self.__from_ber_buf( char.get_bytes(vs[1]-vs[0]) ) def __from_ber_buf(self, buf): # set the OCTET STRING value according to buf if self._const_cont is not None: if self._const_cont_enc is not None: # TODO: different codec to be used if not self._SILENT: asnlog('OCT_STR.__from_ber_buf: %s, specific CONTAINING encoder unhandled'\ % self._name) self._val = buf else: Obj, char = self._const_cont, Charpy(buf) _const_cont_par = Obj._parent Obj._parent = self._parent try: Obj.from_ber(char, single=False) except: if not self._SILENT: asnlog('OCT_STR.__from_ber_buf: %s, CONTAINING object decoding failed'\ % self._name) Obj._parent = _const_cont_par self._val = buf else: Obj._parent = _const_cont_par if Obj._typeref is not None: self._val = (Obj._typeref.called[1], Obj._val) else: self._val = (Obj.TYPE, Obj._val) else: self._val = buf def _encode_ber_cont_ws(self): buf = self.__to_ber_buf() if ASN1CodecBER.ENC_OSTR_FRAG and len(buf) > ASN1CodecBER.ENC_OSTR_FRAG: # fragmentation required Frag, lval = [], 0 for i in range(0, len(buf), ASN1CodecBER.ENC_OSTR_FRAG): frag = buf[i:i+ASN1CodecBER.ENC_OSTR_FRAG] TLV = Envelope('TLV', GEN=( ASN1CodecBER.encode_tag_ws(0, 0, 4), ASN1CodecBER.encode_len_ws(len(frag)), Buf('V', val=frag, bl=8*len(frag), rep=REPR_HEX))) Frag.append(TLV) lval += 1 + (TLV[1].get_bl() >> 3) + len(frag) return 1, lval, Envelope('V', GEN=tuple(Frag)) else: lval = len(buf) return 0, lval, Buf('V', val=buf, bl=8*lval, rep=REPR_HEX) def _encode_ber_cont(self): buf = self.__to_ber_buf() if ASN1CodecBER.ENC_OSTR_FRAG and len(buf) > ASN1CodecBER.ENC_OSTR_FRAG: # fragmentation required TLV, lval = [], 0 for i in range(0, len(buf), ASN1CodecBER.ENC_OSTR_FRAG): frag = buf[i:i+ASN1CodecBER.ENC_OSTR_FRAG] TLV.extend( ASN1CodecBER.encode_tag(0, 0, 4) ) L = ASN1CodecBER.encode_len(len(frag)) TLV.extend( L ) TLV.append( (T_BYTES, frag, 8*len(frag)) ) lval += 1 + (sum([f[2] for f in L]) >> 3) + len(frag) return 1, lval, TLV else: return 0, len(buf), [ (T_BYTES, buf, 8*len(buf)) ] def __to_ber_buf(self): # convert the value into a buffer and length in bits if not isinstance(self._val, bytes_types): # 1) value is for a contained object to be encoded Cont = self._get_val_obj(self._val[0]) if Cont == self._const_cont and self._const_cont_enc is not None: # TODO: different codec to be used raise(ASN1NotSuppErr('{0}: specific CONTAINING encoder unhandled'\ .format(self.fullname()))) Cont._val = self._val[1] buf = Cont.to_ber() return buf else: # 2) value is the standard (uint, bit length) return self._val ### # conversion between internal value and ASN.1 JER encoding ### # TODO: should support the alternative value based on the CONTAINING # constraint (i.e. CHOICE-like) if _with_json: def _from_jval(self, val): if isinstance(val, str_types): try: self._val = unhexlify(val) except TypeError: raise(ASN1JERDecodeErr('{0}: invalid json value, {1!r}'\ .format(self.fullname(), val))) elif self._const_cont is not None: if self._const_cont_enc is not None: # TODO: different codec to be used if not self._SILENT: raise(ASN1NotSuppErr('{0}: specific CONTAINING decoder unhandled'\ .format(self.fullname()))) Cont = self._const_cont if Cont._typeref: ident = Cont._typeref.called[1] else: ident = Cont.TYPE _par = Cont._parent Cont._parent = self try: Cont._from_jval(val[ident]) except KeyError: Cont._parent = _par raise(ASN1JERDecodeErr('{0}: invalid json value, {1!r}'\ .format(self.fullname(), val))) Cont._parent = _par self._val = (ident, Cont._val) def _to_jval(self): if isinstance(self._val, bytes_types): return hexlify(self._val).decode() else: # value is for a contained object to be encoded # using a CHOICE-like encoding Cont = self._get_val_obj(self._val[0]) if Cont == self._const_cont and self._const_cont_enc is not None: # TODO: different codec to be used raise(ASN1NotSuppErr('{0}: specific CONTAINING encoder unhandled'\ .format(self.fullname()))) Cont._val = self._val[1] _par = Cont._parent Cont._parent = self val = Cont._to_jval() Cont._parent = _par if Cont._typeref: return {Cont._typeref.called[1]: val} else: return {Cont.TYPE: val} #------------------------------------------------------------------------------# # All *String #------------------------------------------------------------------------------# _String_docstring = """ Single value: Python str it can be encoded with 'ascii', 'utf8', 'utf16' or 'utf32' depending of the ASN.1 object Specific attribute: - codec: indicates the Python codec used for encoding / decoding - clen: None or int, indicates the number of bits for a character in case it is fixed Specific constraints attributes: - const_sz: None or ASN1Set (TYPE_INT), provides the set of sizes that constraints the type - const_alpha: None or ASN1Set (*String), provides the set of alphabet character that constraints the type %s """ % ASN1Obj_docstring class _String(ASN1Obj): __doc__ = """ Virtual parent for any ASN.1 *String object %s """ % _String_docstring _const_sz = None _const_alpha = None _codec = 'utf8' _clen = None _ALPHA_RE = None def _get_char_dyn(self): # returns the number of bits required to encode a character if self._const_alpha: if self._const_alpha._ev is not None: # extensible alphabet constraint cdyn = self._clen else: cdyn = self._const_alpha.rdyn else: cdyn = self._clen if ASN1CodecPER.ALIGNED and cdyn is not None: return round_p2(cdyn) else: return cdyn def _safechk_val(self, val): if not isinstance(val, str_types): raise(ASN1ObjErr('{0}: invalid value, {1!r}'.format(self.fullname(), val))) if self._ALPHA_RE and not all([c in self._ALPHA_RE for c in val]): raise(ASN1ObjErr('{0}: invalid character in value, {1!r}'.format(self.fullname(), val))) def _safechk_bnd(self, val): # check val against potential constraints ASN1Obj._safechk_bnd(self, val) if self._const_sz and \ self._const_sz.ext is None and \ len(val) not in self._const_sz: raise(ASN1ObjErr('{0}: value out of size constraint, {1!r}'\ .format(self.fullname(), val))) if self._const_alpha and \ self._const_alpha.ext is None: for c in val: if c not in self._const_alpha: raise(ASN1ObjErr('{0}: value out of alphabet constraint, {1!r}'\ .format(self.fullname(), val))) ### # conversion between internal value and ASN.1 syntax ### def _from_asn1(self, txt): rest, val = extract_charstr(txt) if val is not None: self._val = val return rest else: raise(ASN1ASNDecodeErr('{0}: invalid text, {1!r}'.format(self.fullname(), txt))) def _to_asn1(self): # character string return '"' + self._val.replace('"', '""') + '"' ### # conversion between internal value and ASN.1 PER encoding ### # check if there is an alphabet constraint, which can reduce some more the clen parameter, # this enables to determine the number of bits required to encode each character of the string # the size constraint is to be applied on the number of character def _from_per_ws(self, char): GEN = [] if self._const_sz: if self._const_sz._ev is not None: E = Uint('E', bl=1) E._from_char(char) GEN.append(E) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 if E(): # 1) size in the extension part # decoded as unconstraint integer self.__from_per_ws_szunconst(char, GEN) return # size in the root part if self._const_sz.rdyn: # 2) defined range of possible sizes if self._const_sz.ub >= 65536: self.__from_per_ws_szunconst(char, GEN) return else: ldet, _gen = ASN1CodecPER.decode_intconst_ws(char, self._const_sz) GEN.extend(_gen) if ASN1CodecPER.ALIGNED and ASN1CodecPER._off[-1] % 8: # realignment GEN.extend( ASN1CodecPER.decode_pad_ws(char) ) self.__from_per_ws_charstr(char, ldet, GEN) return elif self._const_sz.rdyn == 0: if self._const_sz.ub >= 65536: self.__from_per_ws_szunconst(char, GEN) return else: # 3) size has a single possible size ldet = self._const_sz.lb if ASN1CodecPER.ALIGNED and ldet > 2 and ASN1CodecPER._off[-1] % 8: # realignment GEN.extend( ASN1CodecPER.decode_pad_ws(char) ) self.__from_per_ws_charstr(char, ldet, GEN) return # 4) size is semi-constrained or has no constraint # decoded as unconstrained integer self.__from_per_ws_szunconst(char, GEN) def __from_per_ws_szunconst(self, char, GEN): # size is semi-constrained or unconstrained # anyway, it is decoded as unconstrained integer if ASN1CodecPER.ALIGNED and ASN1CodecPER._off[-1] % 8: GEN.extend( ASN1CodecPER.decode_pad_ws(char) ) ldet, _gen = ASN1CodecPER.decode_count_ws(char) GEN.extend(_gen) if ldet in (65536, 49152, 32768, 16384): cdyn = self._get_char_dyn() # requires defragmentation if cdyn is None: # ldet is the number of bytes, val is bytes val, _gen = ASN1CodecPER.decode_fragbytes_ws(char, ldet) # e.g. utf-8 encoding if self._codec is None: raise(ASN1NotSuppErr('{0}: ISO 2022 codec not supported'\ .format(self.fullname()))) try: self._val = val.decode(self._codec) except Exception as err: raise(ASN1PERDecodeErr('{0}: invalid character, Python codec error, {1}'\ .format(self.fullname(), err))) elif cdyn < self._clen: # ldet is the number of characters, val is a list of uint val, _gen = ASN1CodecPER.decode_fragcharstr_ws(char, ldet, cdyn, arr=True) # character remapping required try: self._val = ''.join([self._const_alpha.root[i] for i in val]) except: raise(ASN1PERDecodeErr('{0}: character out of alphabet constraint'\ .format(self.fullname()))) elif cdyn == 4: # ldet is the number of characters, val is a list of uint val, _gen = ASN1CodecPER.decode_fragcharstr_ws(char, ldet, cdyn, arr=True) # numeric string try: self._val = ''.join([self._ALPHA_RE[i] for i in val]) except: raise(ASN1PERDecodeErr('{0}: character out of alphabet'\ .format(self.fullname()))) elif cdyn == 7: # ldet is the number of characters, val is a list of uint val, _gen = ASN1CodecPER.decode_fragcharstr_ws(char, ldet, cdyn, arr=True) # ascii encoding try: self._val = ''.join(map(chr, val)) except: raise(ASN1PERDecodeErr('{0}: character out of alphabet'\ .format(self.fullname()))) else: # ldet is the number of characters, val is bytes assert( cdyn % 8 == 0 ) val, _gen = ASN1CodecPER.decode_fragcharstr_ws(char, ldet, cdyn, arr=False) if self._codec is None: raise(ASN1NotSuppErr('{0}: ISO 2022 codec not supported'\ .format(self.fullname()))) try: self._val = val.decode(self._codec) except Exception as err: raise(ASN1PERDecodeErr('{0}: invalid character, Python codec error, {1}'\ .format(self.fullname(), err))) GEN.extend(_gen) self._struct = Envelope(self._name, GEN=tuple(GEN)) else: self.__from_per_ws_charstr(char, ldet, GEN) def __from_per_ws_charstr(self, char, ldet, GEN): cdyn = self._get_char_dyn() if cdyn is None: # ldet is the number of bytes (e.g. utf-8 encoding) V = Buf('V', bl=8*ldet) V._from_char(char) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 8*ldet if self._codec is None: raise(ASN1NotSuppErr('{0}: ISO 2022 codec not supported'\ .format(self.fullname()))) try: self._val = V().decode(self._codec) except Exception as err: raise(ASN1PERDecodeErr('{0}: invalid character, Python codec error, {1}'\ .format(self.fullname(), err))) elif cdyn < self._clen: # ldet is the number of characters V = Array('V', num=ldet, GEN=Uint('char', bl=cdyn)) V._from_char(char) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += cdyn*ldet # character remapping required try: self._val = ''.join([self._const_alpha.root[i] for i in V()]) except: raise(ASN1PERDecodeErr('{0}: character out of alphabet constraint, {1!r}'\ .format(self.fullname(), V()))) elif cdyn == 4: # ldet is the number of characters V = Array('V', num=ldet, GEN=Uint('char', bl=cdyn)) V._from_char(char) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += cdyn*ldet # numeric string try: self._val = ''.join([self._ALPHA_RE[i] for i in V()]) except: raise(ASN1PERDecodeErr('{0}: character out of alphabet, {1!r}'\ .format(self.fullname(), V()))) elif cdyn == 7: # ldet is the number of characters V = Array('V', num=ldet, GEN=Uint('char', bl=cdyn)) V._from_char(char) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += cdyn*ldet # ascii encoding try: self._val = ''.join(map(chr, V())) except: raise(ASN1PERDecodeErr('{0}: character out of alphabet, {1!r}'\ .format(self.fullname(), V()))) else: # ldet is the number of characters, val is bytes assert( cdyn % 8 == 0 ) V = Buf('V', bl=ldet*cdyn) V._from_char(char) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += cdyn*ldet if self._codec is None: raise(ASN1NotSuppErr('{0}: ISO 2022 codec not supported'\ .format(self.fullname()))) try: self._val = V().decode(self._codec) except Exception as err: raise(ASN1PERDecodeErr('{0}: invalid character, Python codec error, {1}'\ .format(self.fullname(), err))) GEN.append(V) self._struct = Envelope(self._name, GEN=tuple(GEN)) def _from_per(self, char): if self._const_sz: if self._const_sz._ev is not None: E = char.get_uint(1) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 if E: # 1) size in the extension part # decoded as unconstraint integer self.__from_per_szunconst(char) return # size in the root part if self._const_sz.rdyn: # 2) defined range of possible sizes if self._const_sz.ub >= 65536: self.__from_per_szunconst(char) return else: ldet = ASN1CodecPER.decode_intconst(char, self._const_sz) if ASN1CodecPER.ALIGNED and ASN1CodecPER._off[-1] % 8: # realignment ASN1CodecPER.decode_pad(char) self.__from_per_charstr(char, ldet) return elif self._const_sz.rdyn == 0: if self._const_sz.ub >= 65536: self.__from_per_szunconst(char) return else: # 3) size has a single possible size ldet = self._const_sz.lb if ASN1CodecPER.ALIGNED and ldet > 2 and ASN1CodecPER._off[-1] % 8: # realignment ASN1CodecPER.decode_pad(char) self.__from_per_charstr(char, ldet) return # 4) size is semi-constrained or has no constraint # decoded as unconstrained integer self.__from_per_szunconst(char) def __from_per_szunconst(self, char): # size is semi-constrained or unconstrained # anyway, it is decoded as unconstrained integer if ASN1CodecPER.ALIGNED and ASN1CodecPER._off[-1] % 8: ASN1CodecPER.decode_pad(char) ldet = ASN1CodecPER.decode_count(char) if ldet in (65536, 49152, 32768, 16384): cdyn = self._get_char_dyn() # requires defragmentation if cdyn is None: # ldet is the number of bytes, val is bytes (e.g. utf-8 encoding) val = ASN1CodecPER.decode_fragbytes(char, ldet) if self._codec is None: raise(ASN1NotSuppErr('{0}: ISO 2022 codec not supported'\ .format(self.fullname()))) try: self._val = val.decode(self._codec) except Exception as err: raise(ASN1PERDecodeErr('{0}: invalid character, Python codec error, {1}'\ .format(self.fullname(), err))) elif cdyn < self._clen: # ldet is the number of characters, val is a list of uint val = ASN1CodecPER.decode_fragcharstr(char, ldet, cdyn, arr=True) # character remapping required try: self._val = ''.join([self._const_alpha.root[i] for i in val]) except: raise(ASN1PERDecodeErr('{0}: character out of alphabet constraint'\ .format(self.fullname()))) elif cdyn == 4: # ldet is the number of characters, val is a list of uint val = ASN1CodecPER.decode_fragcharstr(char, ldet, cdyn, arr=True) # numeric string try: self._val = ''.join([self._ALPHA_RE[i] for i in val]) except: raise(ASN1PERDecodeErr('{0}: character out of alphabet'\ .format(self.fullname()))) elif cdyn == 7: # ldet is the number of characters, val is a list of uint val = ASN1CodecPER.decode_fragcharstr(char, ldet, cdyn, arr=True) # ascii encoding try: self._val = ''.join(map(chr, val)) except: raise(ASN1PERDecodeErr('{0}: character out of alphabet'\ .format(self.fullname()))) else: # ldet is the number of characters, val is bytes assert( cdyn % 8 == 0 ) val = ASN1CodecPER.decode_fragcharstr(char, ldet, cdyn, arr=False) if self._codec is None: raise(ASN1NotSuppErr('{0}: ISO 2022 codec not supported'\ .format(self.fullname()))) try: self._val = val.decode(self._codec) except Exception as err: raise(ASN1PERDecodeErr('{0}: invalid character, Python codec error, {1}'\ .format(self.fullname(), err))) else: self.__from_per_charstr(char, ldet) def __from_per_charstr(self, char, ldet): cdyn = self._get_char_dyn() if cdyn is None: # ldet is the number of bytes (e.g. utf-8 encoding) val = char.get_bytes(8*ldet) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 8*ldet if self._codec is None: raise(ASN1NotSuppErr('{0}: ISO 2022 codec not supported'\ .format(self.fullname()))) try: self._val = val.decode(self._codec) except Exception as err: raise(ASN1PERDecodeErr('{0}: invalid character, Python codec error, {1}'\ .format(self.fullname(), err))) elif cdyn < self._clen: # ldet is the number of characters val = [char.get_uint(cdyn) for i in range(ldet)] if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += cdyn*ldet # character remapping required try: self._val = ''.join([self._const_alpha.root[i] for i in val]) except: raise(ASN1PERDecodeErr('{0}: character out of alphabet constraint, {1!r}'\ .format(self.fullname(), V()))) elif cdyn == 4: # ldet is the number of characters val = [char.get_uint(cdyn) for i in range(ldet)] if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += cdyn*ldet # numeric string try: self._val = ''.join([self._ALPHA_RE[i] for i in val]) except: raise(ASN1PERDecodeErr('{0}: character out of alphabet, {1!r}'\ .format(self.fullname(), V()))) elif cdyn == 7: # ldet is the number of characters val = [char.get_uint(cdyn) for i in range(ldet)] if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += cdyn*ldet # ascii encoding try: self._val = ''.join(map(chr, val)) except: raise(ASN1PERDecodeErr('{0}: character out of alphabet, {1!r}'\ .format(self.fullname(), V()))) else: # ldet is the number of characters, val is bytes assert( cdyn % 8 == 0 ) val = char.get_bytes(ldet*cdyn) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += cdyn*ldet if self._codec is None: raise(ASN1NotSuppErr('{0}: ISO 2022 codec not supported'\ .format(self.fullname()))) try: self._val = val.decode(self._codec) except Exception as err: raise(ASN1PERDecodeErr('{0}: invalid character, Python codec error, {1}'\ .format(self.fullname(), err))) def _to_per_ws(self): GEN = [] val, cdyn, ldet = self.__to_per_val() if self._const_sz: if self._const_sz._ev is not None: if not self._const_sz.in_root(ldet): # 1) size in the extension part # encoded as unconstraint integer GEN.append( Uint('E', val=1, bl=1) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 self.__to_per_ws_szunconst(val, cdyn, ldet, GEN) return self._struct else: GEN.append( Uint('E', val=0, bl=1) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 # size in the root part if self._const_sz.rdyn: # 2) defined range of possible sizes if self._const_sz.ub >= 65536: self.__to_per_ws_szunconst(val, cdyn, ldet, GEN) return self._struct else: GEN.extend( ASN1CodecPER.encode_intconst_ws(ldet, self._const_sz, name='C') ) if ASN1CodecPER.ALIGNED and ASN1CodecPER._off[-1] % 8: # realignment GEN.extend( ASN1CodecPER.encode_pad_ws() ) self.__to_per_ws_charstr(val, cdyn, ldet, GEN) return self._struct elif self._const_sz.rdyn == 0: # 3) size has a single possible size if self._const_sz.ub >= 65536: self.__to_per_ws_szunconst(val, cdyn, ldet, GEN) return self._struct else: if ASN1CodecPER.ALIGNED and ldet > 2 and ASN1CodecPER._off[-1] % 8: # realignment GEN.extend( ASN1CodecPER.encode_pad_ws() ) self.__to_per_ws_charstr(val, cdyn, ldet, GEN) return self._struct # 4) size is semi-constrained or has no constraint # encoded as unconstrained integer self.__to_per_ws_szunconst(val, cdyn, ldet, GEN) return self._struct def __to_per_val(self): cdyn = self._get_char_dyn() if cdyn is None: # ldet is the length in bytes if self._codec is None: raise(ASN1NotSuppErr('{0}: ISO 2022 codec not supported'\ .format(self.fullname()))) try: buf = self._val.encode(self._codec) except Exception as err: raise(ASN1PEREncodeErr('{0}: invalid character, Python codec error, {1}'\ .format(self.fullname(), err))) return buf, cdyn, len(buf) else: # ldet is the length in number of chars, each encoded in $cdyn bits ldet = len(self._val) if cdyn < self._clen: # character remapping required try: val = [self._const_alpha.root.index(c) for c in self._val] except: raise(ASN1PEREncodeErr('{0}: character out of alphabet constraint, {1!r}'\ .format(self.fullname(), self._val))) elif cdyn == 4: # numeric string try: val = [self._ALPHA_RE.find(c) for c in self._val] except: raise(ASN1PEREncodeErr('{0}: character out of alphabet, {1!r}'\ .format(self.fullname(), self._val))) elif cdyn == 7: # ascii encoding try: val = list(map(ord, self._val)) except: raise(ASN1PEREncodeErr('{0}: character out of alphabet, {1!r}'\ .format(self.fullname(), self._val))) else: # builtin Python encoding, utf-16 or utf-32 assert(cdyn % 8 == 0) if self._codec is None: raise(ASN1NotSuppErr('{0}: ISO 2022 codec not supported'\ .format(self.fullname()))) try: val = self._val.encode(self._codec) except Exception as err: raise(ASN1PEREncodeErr('{0}: invalid character, Python codec error, {1}'\ .format(self.fullname(), err))) return val, cdyn, ldet def __to_per_ws_szunconst(self, val, cdyn, ldet, GEN): # size is semi-constrained or unconstrained # anyway, it is encoded as unconstrained integer if ASN1CodecPER.ALIGNED and ASN1CodecPER._off[-1] % 8: GEN.extend( ASN1CodecPER.encode_pad_ws() ) if ldet >= 16384: # requires fragmentation if cdyn is None: # bytes fragmentation GEN.extend( ASN1CodecPER.encode_fragbytes_ws(val) ) else: # int list or utf-16/32 bytes fragmentation GEN.extend( ASN1CodecPER.encode_fragcharstr_ws(val, cdyn, ldet) ) self._struct = Envelope(self._name, GEN=tuple(GEN)) else: GEN.extend( ASN1CodecPER.encode_count_ws(ldet) ) self.__to_per_ws_charstr(val, cdyn, ldet, GEN) def __to_per_ws_charstr(self, val, cdyn, ldet, GEN): if cdyn is None: # use a Buf() structure for storing the content GEN.append( Buf('V', val=val, bl=8*ldet) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 8*ldet elif isinstance(val, bytes_types): # utf-16/32 bytes buffer, use a Buf() structure GEN.append( Buf('V', val=val, bl=cdyn*ldet) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += cdyn*ldet else: # use an Array() structure GEN.append( Array('V', val=val, num=ldet, GEN=Uint('char', bl=cdyn)) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += cdyn*ldet self._struct = Envelope(self._name, GEN=tuple(GEN)) def _to_per(self): GEN = [] val, cdyn, ldet = self.__to_per_val() if self._const_sz: if self._const_sz._ev is not None: if not self._const_sz.in_root(ldet): # 1) size in the extension part # encoded as unconstraint integer GEN.append( (T_UINT, 1, 1) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 self.__to_per_szunconst(val, cdyn, ldet, GEN) return GEN else: GEN.append( (T_UINT, 0, 1) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 1 # size in the root part if self._const_sz.rdyn: # 2) defined range of possible sizes if self._const_sz.ub >= 65536: self.__to_per_szunconst(val, cdyn, ldet, GEN) return GEN else: GEN.extend( ASN1CodecPER.encode_intconst(ldet, self._const_sz) ) if ASN1CodecPER.ALIGNED and ASN1CodecPER._off[-1] % 8: # realignment GEN.extend( ASN1CodecPER.encode_pad() ) self.__to_per_charstr(val, cdyn, ldet, GEN) return GEN elif self._const_sz.rdyn == 0: # 3) size has a single possible size if self._const_sz.ub >= 65536: self.__to_per_szunconst(val, cdyn, ldet, GEN) return GEN else: if ASN1CodecPER.ALIGNED and ldet > 2 and ASN1CodecPER._off[-1] % 8: # realignment GEN.extend( ASN1CodecPER.encode_pad() ) self.__to_per_charstr(val, cdyn, ldet, GEN) return GEN # 4) size is semi-constrained or has no constraint # encoded as unconstrained integer self.__to_per_szunconst(val, cdyn, ldet, GEN) return GEN def __to_per_szunconst(self, val, cdyn, ldet, GEN): # size is semi-constrained or unconstrained # anyway, it is encoded as unconstrained integer if ASN1CodecPER.ALIGNED and ASN1CodecPER._off[-1] % 8: GEN.extend( ASN1CodecPER.encode_pad() ) if ldet >= 16384: # requires fragmentation if cdyn is None: # bytes fragmentation GEN.extend( ASN1CodecPER.encode_fragbytes(val) ) else: # int list or utf-16/32 bytes fragmentation GEN.extend( ASN1CodecPER.encode_fragcharstr(val, cdyn, ldet) ) else: GEN.extend( ASN1CodecPER.encode_count(ldet) ) self.__to_per_charstr(val, cdyn, ldet, GEN) def __to_per_charstr(self, val, cdyn, ldet, GEN): if cdyn is None: # use bytes for storing the content GEN.append( (T_BYTES, val, 8*ldet) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += 8*ldet elif isinstance(val, bytes_types): # utf-16/32 bytes buffer, use bytes again GEN.append( (T_BYTES, val, cdyn*ldet) ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += cdyn*ldet else: # use an Array() structure GEN.extend( [(T_UINT, v, cdyn) for v in val] ) if ASN1CodecPER.ALIGNED: ASN1CodecPER._off[-1] += cdyn*ldet ### # conversion between internal value and ASN.1 BER encoding ### def _decode_ber_cont_ws(self, char, vs): if isinstance(vs, list): # constructed / fragmented form # vs: list of TLV Frag, sfrag = [], [] for tlv in vs: Tag, cl, pc, tval, Len, lval = tlv[0:6] if pc != 0: # fragmenting the fragment... damned BER recursivity ! raise(ASN1NotSuppErr('{0}: String fragments of fragments'\ .format(self.fullname()))) elif cl != 0: raise(ASN1BERDecodeErr('{0}: invalid String fragment tag class, {1!r}'\ .format(self.fullname(), cl))) elif (tval, lval) == (0, 0): # EOC marker assert( tlv == v[-1] ) Frag.append( Envelope('EOC', GEN=(Tag, Len)) ) elif tval != self.TAG: raise(ASN1BERDecodeErr('{0}: invalid String fragment tag value, {1!r}'\ .format(self.fullname(), tval))) else: char._cur, char._len_bit = tlv[-1][0], tlv[-1][1] Val = Buf('V', bl=tlv[-1][1]*tlv[-1][0]) Val._from_char(char) sfrag.append( Val.to_bytes() ) Frag.append( Envelope('TLV', GEN=(Tag, Len, Val)) ) # generate the V envelope V = Envelope('V', GEN=tuple(Frag)) # process the defragmented string if self._codec is None: raise(ASN1NotSuppErr('{0}: ISO 2022 codec not supported'\ .format(self.fullname()))) try: self._val = b''.join(sfrag).decode(self._codec) except Exception as err: raise(ASN1PEREncodeErr('{0}: invalid character, Python codec error, {1}'\ .format(self.fullname(), err))) else: # primitive form # vs: value boundary 2-tuple char._cur, char._len_bit = vs[0], vs[1] V = Buf('V', bl=vs[1]-vs[0]) V._from_char(char) # process the string if self._codec is None: raise(ASN1NotSuppErr('{0}: ISO 2022 codec not supported'\ .format(self.fullname()))) try: self._val = V.to_bytes().decode(self._codec) except Exception as err: raise(ASN1PEREncodeErr('{0}: invalid character, Python codec error, {1}'\ .format(self.fullname(), err))) return V def _decode_ber_cont(self, char, vs): if isinstance(vs, list): # constructed / fragmented form # vs: list of TLV sfrag = [] for tlv in vs: cl, pc, tval, lval = tlv[0:4] if pc != 0: # fragmenting the fragment... damned BER recursivity ! raise(ASN1NotSuppErr('{0}: String fragments of fragments'\ .format(self.fullname()))) elif cl != 0: raise(ASN1BERDecodeErr('{0}: invalid String fragment tag class, {1!r}'\ .format(self.fullname(), cl))) elif (tval, lval) == (0, 0): # EOC marker assert( tlv == v[-1] ) elif tval != self.TAG: raise(ASN1BERDecodeErr('{0}: invalid String fragment tag value, {1!r}'\ .format(self.fullname(), tval))) else: char._cur, char._len_bit = tlv[-1][0], tlv[-1][1] sfrag.append( char.get_bytes(tlv[-1][1]-tlv[-1][0]) ) # process the defragmented string if self._codec is None: raise(ASN1NotSuppErr('{0}: ISO 2022 codec not supported'\ .format(self.fullname()))) try: self._val = b''.join(sfrag).decode(self._codec) except Exception as err: raise(ASN1PEREncodeErr('{0}: invalid character, Python codec error, {1}'\ .format(self.fullname(), err))) else: # primitive form if self._codec is None: raise(ASN1NotSuppErr('{0}: ISO 2022 codec not supported'\ .format(self.fullname()))) char._cur, char._len_bit = vs[0], vs[1] buf = char.get_bytes(vs[1]-vs[0]) try: self._val = buf.decode(self._codec) except Exception as err: raise(ASN1PEREncodeErr('{0}: invalid character, Python codec error, {1}'\ .format(self.fullname(), err))) def _encode_ber_cont_ws(self): if self._codec is None: raise(ASN1NotSuppErr('{0}: ISO 2022 codec not supported'\ .format(self.fullname()))) try: buf = self._val.encode(self._codec) except Exception as err: raise(ASN1PEREncodeErr('{0}: invalid character, Python codec error, {1}'\ .format(self.fullname(), err))) if ASN1CodecBER.ENC_OSTR_FRAG and len(buf) > ASN1CodecBER.ENC_OSTR_FRAG: # fragmentation required Frag, lval = [], 0 for i in range(0, len(buf), ASN1CodecBER.ENC_OSTR_FRAG): frag = buf[i:i+ASN1CodecBER.ENC_OSTR_FRAG] TLV = Envelope('TLV', GEN=( ASN1CodecBER.encode_tag_ws(0, 0, self.TAG), ASN1CodecBER.encode_len_ws(len(frag)), Buf('V', val=frag, bl=8*len(frag)))) Frag.append(TLV) lval += 1 + (TLV[1].get_bl() >> 3) + len(frag) return 1, lval, Envelope('V', GEN=tuple(Frag)) else: lval = len(buf) return 0, lval, Buf('V', val=buf, bl=8*lval) def _encode_ber_cont(self): if self._codec is None: raise(ASN1NotSuppErr('{0}: ISO 2022 codec not supported'\ .format(self.fullname()))) try: buf = self._val.encode(self._codec) except Exception as err: raise(ASN1PEREncodeErr('{0}: invalid character, Python codec error, {1}'\ .format(self.fullname(), err))) if ASN1CodecBER.ENC_OSTR_FRAG and len(buf) > ASN1CodecBER.ENC_OSTR_FRAG: # fragmentation required TLV, lval = [], 0 for i in range(0, len(buf), ASN1CodecBER.ENC_OSTR_FRAG): frag = buf[i:i+ASN1CodecBER.ENC_OSTR_FRAG] TLV.extend( ASN1CodecBER.encode_tag(0, 0, self.TAG) ) L = ASN1CodecBER.encode_len(len(frag)) TLV.extend( L ) TLV.append( (T_BYTES, frag, 8*len(frag)) ) lval += 1 + (sum([f[2] for f in L]) >> 3) + len(frag) return 1, lval, TLV else: return 0, len(buf), [ (T_BYTES, buf, 8*len(buf)) ] ### # conversion between internal value and ASN.1 JER encoding ### if _with_json: def _from_jval(self, val): if isinstance(val, str_types): self._val = val else: raise(ASN1JERDecodeErr('{0}: invalid json value, {1!r}'\ .format(self.fullname(), val))) def _to_jval(self): return self._val # Python does not provide a complete support for ISO2022 encoding # so here, we use iso2022_jp_2004 _ISO2022_CODEC = 'iso2022_jp_2004' # If you want to disable support for ISO2022 entirely, just set this to None #_ISO2022_CODEC = None class OBJ_DESC(_String): __doc__ = """ ASN.1 basic type OBJECT DESCRIPTOR object %s """ % _String_docstring # OBJECT DESCRIPTOR is a subtype of GraphicString _codec = _ISO2022_CODEC TYPE = TYPE_OBJ_DESC TAG = 7 class STR_UTF8(_String): __doc__ = """ ASN.1 basic type UTF8String object %s """ % _String_docstring TYPE = TYPE_STR_UTF8 TAG = 12 class STR_NUM(_String): __doc__ = """ ASN.1 basic type NumericString object %s """ % _String_docstring _codec = 'ascii' _clen = 4 _ALPHA_RE = ' 0123456789' TYPE = TYPE_STR_NUM TAG = 18 class STR_PRINT(_String): __doc__ = """ ASN.1 basic type PrintableString object %s """ % _String_docstring _codec = 'ascii' _clen = 7 _ALPHA_RE = '0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\'()+,-./:=?' TYPE = TYPE_STR_PRINT TAG = 19 class STR_TELE(_String): __doc__ = """ ASN.1 basic type TeletexString object %s """ % _String_docstring _codec = _ISO2022_CODEC TYPE = TYPE_STR_TELE TAG = 20 if _with_json: _from_jval = OCT_STR._from_jval _to_jval = OCT_STR._to_jval class STR_T61(_String): __doc__ = """ ASN.1 basic type T61String object %s """ % _String_docstring _codec = _ISO2022_CODEC TYPE = TYPE_STR_T61 TAG = 20 if _with_json: _from_jval = OCT_STR._from_jval _to_jval = OCT_STR._to_jval class STR_VID(_String): __doc__ = """ ASN.1 basic type VideotextString object %s """ % _String_docstring _codec = _ISO2022_CODEC TYPE = TYPE_STR_VID TAG = 21 if _with_json: _from_jval = OCT_STR._from_jval _to_jval = OCT_STR._to_jval class STR_IA5(_String): __doc__ = """ ASN.1 basic type IA5String object %s """ % _String_docstring _codec = 'ascii' _clen = 7 _ALPHA_RE = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~' TYPE = TYPE_STR_IA5 TAG = 22 class STR_GRAPH(_String): __doc__ = """ ASN.1 basic type GraphicString object %s """ % _String_docstring _codec = _ISO2022_CODEC TYPE = TYPE_STR_GRAPH TAG = 25 if _with_json: _from_jval = OCT_STR._from_jval _to_jval = OCT_STR._to_jval class STR_VIS(_String): __doc__ = """ ASN.1 basic type VisibleString object %s """ % _String_docstring _codec = 'ascii' _clen = 7 _ALPHA_RE = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}' TYPE = TYPE_STR_VIS TAG = 26 class STR_ISO646(_String): __doc__ = """ ASN.1 basic type ISO646String object %s """ % _String_docstring _codec = 'ascii' _clen = STR_VIS._clen _ALPHA_RE = STR_VIS._ALPHA_RE TYPE = TYPE_STR_ISO646 TAG = 26 class STR_GENE(_String): __doc__ = """ ASN.1 basic type GenericString object %s """ % _String_docstring _codec = _ISO2022_CODEC TYPE = TYPE_STR_GENE TAG = 27 if _with_json: _from_jval = OCT_STR._from_jval _to_jval = OCT_STR._to_jval class STR_UNIV(_String): __doc__ = """ ASN.1 basic type UniversalString object %s """ % _String_docstring _codec = 'utf-32-be' _clen = 32 TYPE = TYPE_STR_UNIV TAG = 28 class STR_BMP(_String): __doc__ = """ ASN.1 basic type BMPString object %s """ % _String_docstring _codec = 'utf-16-be' _clen = 16 TYPE = TYPE_STR_BMP TAG = 30 #------------------------------------------------------------------------------# # UTCTime and GeneralizedTime #------------------------------------------------------------------------------# class _Time(STR_VIS): __doc__ = """ Virtual parent for UTCTime and GeneralizedTime, both being actually subtype of VisibleString """ ### # convert the internal tuple value to a string, which then gets # encoded / decoded like a VisibleString ### def _from_per_ws(self, char): _String._from_per_ws(self, char) self._decode_cont(self._val) def _from_per(self, char): _String._from_per(self, char) self._decode_cont(self._val) def _to_per_ws(self): val = self._val self._val = self._encode_cont(canon=True) ret = _String._to_per_ws(self) self._val = val return ret def _to_per(self): val = self._val self._val = self._encode_cont(canon=True) ret = _String._to_per(self) self._val = val return ret def _decode_ber_cont_ws(self, char, vs): ret = _String._decode_ber_cont_ws(self, char, vs) self._decode_cont(self._val) return ret def _decode_ber_cont(self, char, vs): _String._decode_ber_cont(self, char, vs) self._decode_cont(self._val) def _encode_ber_cont_ws(self): val = self._val if ASN1CodecBER.ENC_TIME_CANON: self._val = self._encode_cont(canon=True) else: self._val = self._encode_cont(canon=False) ret = _String._encode_ber_cont_ws(self) self._val = val return ret def _encode_ber_cont(self): val = self._val if ASN1CodecBER.ENC_TIME_CANON: self._val = self._encode_cont(canon=True) else: self._val = self._encode_cont(canon=False) ret = _String._encode_ber_cont(self) self._val = val return ret ### # conversion between internal value and ASN.1 JER encoding ### if _with_json: def _from_jval(self, val): _String._from_jval(self, val) self._decode_cont(self._val) def _to_jval(self): val = self._val self._val = self._encode_cont(canon=True) ret = _String._to_jval(self) self._val = val return ret class TIME_UTC(_Time): __doc__ = """ ASN.1 basic type UTCTime object Single value: Python 7-tuple of str or None (YY, MM, DD, HH, MM, [SS,] Z) SS is optional, hence 6th element can be None Z corresponds to the UTC decay and can be Z or {+-}HHMM %s """ % ASN1Obj_docstring TYPE = TYPE_TIME_UTC TAG = 23 _ASN_RE = re.compile( '"([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2}){0,1}((?:Z)|(?:[+-]{1}[0-9]{4}))"') def _safechk_val(self, val): if not isinstance(val, tuple) or len(val) != 7 \ or not all([isinstance(v, str_types + (NoneType,)) for v in val]): # TODO: more conditions are required to test for the exact format raise(ASN1ObjErr('{0}: invalid value, {1!r}'.format(self.fullname(), val))) ### # conversion between internal value and ASN.1 syntax ### def _from_asn1(self, txt): m = self._ASN_RE.match(txt) if m is not None: self._val = m.groups() return txt[m.end():].strip() else: raise(ASN1ASNDecodeErr('{0}: invalid text, {1!r}'.format(self.fullname(), txt))) def _to_asn1(self): if self._val[5] is None: s = '"' + ''.join(self._val[:5] + self._val[-1:]) + '"' if _with_time: try: if s[-2] == 'Z': i = ' -- %s --' % asctime(strptime(s[1:-2], '%y%m%d%H%M')) else: i = ' -- %s --' % asctime(strptime(s[1:-1], '%y%m%d%H%M%Z')) except: i = '' return s + i else: return s else: s = '"' + ''.join(self._val) + '"' if _with_time: try: if s[-2] == 'Z': i = ' -- %s --' % asctime(strptime(s[1:-2], '%y%m%d%H%M%S')) else: i = ' -- %s --' % asctime(strptime(s[1:-1], '%y%m%d%H%M%S%Z')) except: i = '' return s + i else: return s ### # ascii encoding of the time string for BER and PER, in the canonical way ### def _decode_cont(self, asc): try: self._from_asn1('"' + asc + '"') except Exception as err: raise(ASN1BERDecodeErr('{0}: invalid UTCTime ascii encoding'\ .format(self.fullname()))) def _encode_cont(self, canon=True): if self._val[5] is None: if canon: asc = ''.join(self._val[:5]) + '00' + self._val[-1] else: asc = ''.join(self._val[:5]) + self._val[-1] else: asc = ''.join(self._val) return asc class TIME_GEN(_Time): __doc__ = """ ASN.1 basic type GeneralizedTime object Single value: Python 8-tuple of str or None (YYYY, MM, DD, HH, [MM, [SS,]] [{.,}F*,] [Z]), MM and SS are optional F* is optional and provides a fraction of seconds, minutes or hours Hence 5th, 6th and 7th element can be None Z corresponds to the UTC decay, can be Z, {+-}HH or {+-}HHMM and is optional, hence 8th element can be None too %s """ % ASN1Obj_docstring TYPE = TYPE_TIME_GEN TAG = 24 _ASN_RE = re.compile( '"([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})' \ '(?:([0-9]{2})([0-9]{2}){0,1}){0,1}' \ '(?:(?:\.|,)([0-9]{1,})){0,1}' \ '((?:Z)|(?:[+-](?:[0-9]{2}){0,2})){0,1}"') def _safechk_val(self, val): if not isinstance(val, tuple) or len(val) != 8 \ or not all([isinstance(v, str_types + (NoneType,)) for v in val]): # TODO: more conditions are required to test for the exact format raise(ASN1ObjErr('{0}: invalid value, {1!r}'.format(self.fullname(), val))) ### # conversion between internal value and ASN.1 syntax ### def _from_asn1(self, txt): m = self._ASN_RE.match(txt) if m is not None: self._val = m.groups() return txt[m.end():].strip() else: raise(ASN1ASNDecodeErr('{0}: invalid text, {1!r}'.format(self.fullname(), txt))) def _to_asn1(self): if self._val[4] is None: num = list(self._val[:4]) elif self._val[5] is None: num = list(self._val[:5]) else: num = list(self._val[:6]) if self._val[7] is not None: # fractionnal part if self._val[-1] is not None: # UTC decay num.extend( ['.'] + list(self._val[7:]) ) else: num.extend( ['.', self._val[7]] ) elif self._val[-1] is not None: # UTC decay num.append( self._val[-1] ) return '"' + ''.join(num) + '"' ### # ascii encoding of the time string for BER and PER, in the canonical way ### def _decode_cont(self, asc): try: self._from_asn1('"' + asc + '"') except Exception as err: raise(ASN1BERDecodeErr('{0}: invalid GeneralizedTime ascii encoding'\ .format(self.fullname()))) def _encode_cont(self, canon=True): if canon: if self._val[4] is None: asc = list(self._val[:4]) + ['00', '00'] elif self._val[5] is None: asc = list(self._val[:5]) + ['00'] else: asc = list(self._val[:6]) if self._val[6] is not None and int(self._val[6]) != 0: # fractionnal part, removing trailing 0 asc.append( '.' + self._val[6].rstrip('0') ) if self._val[-1] in (None, 'Z'): # UTC time (or considered so...) asc.append( 'Z' ) else: # apply the UTC decay compensation if not _with_datetime: raise(ASN1NotSuppErr('{0}: UTC decay compensation requires module datetime'\ .format(self.fullname()))) else: # keep precision up to minutes dt = datetime(*map(int, asc[:5])) if len(self._val[-1]) == 3: delta = timedelta(hours=int(self._val[-1][1:3]), minutes=0) else: delta = timedelta(hours=int(self._val[-1][1:3]), minutes=int(self._val[-1][3:5])) if self._val[-1][0:1] == '-': dt += delta else: dt -= delta asc = [dt.strftime('%Y%m%d%H%M'), asc[5], asc[6], 'Z'] else: if self._val[4] is None: asc = list(self._val[:4]) elif self._val[5] is None: asc = list(self._val[:5]) else: asc = list(self._val[:6]) if self._val[6] is not None: # fractionnal part asc.append( '.' + self._val[6] ) if self._val[-1] is not None: # UTC decay asc.append( self._val[-1] ) return ''.join(asc)