| # $Id: asn1.py 23 2006-11-08 15:45:33Z dugsong $ |
| # -*- coding: utf-8 -*- |
| """Abstract Syntax Notation #1.""" |
| from __future__ import absolute_import |
| from __future__ import print_function |
| |
| import struct |
| from calendar import timegm |
| |
| from . import dpkt |
| from .compat import compat_ord |
| |
| # Type class |
| CLASSMASK = 0xc0 |
| UNIVERSAL = 0x00 |
| APPLICATION = 0x40 |
| CONTEXT = 0x80 |
| PRIVATE = 0xc0 |
| |
| # Constructed (vs. primitive) |
| CONSTRUCTED = 0x20 |
| |
| # Universal-class tags |
| TAGMASK = 0x1f |
| INTEGER = 2 |
| BIT_STRING = 3 # arbitrary bit string |
| OCTET_STRING = 4 # arbitrary octet string |
| NULL = 5 |
| OID = 6 # object identifier |
| SEQUENCE = 16 # ordered collection of types |
| SET = 17 # unordered collection of types |
| PRINT_STRING = 19 # printable string |
| T61_STRING = 20 # T.61 (8-bit) character string |
| IA5_STRING = 22 # ASCII |
| UTC_TIME = 23 |
| |
| |
| def utctime(buf): |
| """Convert ASN.1 UTCTime string to UTC float. |
| |
| TODO: Long description here. |
| |
| Args: |
| buf: A buffer with format "yymnddhhmm" |
| |
| Returns: |
| A floating point number, indicates seconds since the Epoch. |
| """ |
| yy = int(buf[:2]) |
| mn = int(buf[2:4]) |
| dd = int(buf[4:6]) |
| hh = int(buf[6:8]) |
| mm = int(buf[8:10]) |
| try: |
| ss = int(buf[10:12]) |
| buf = buf[12:] |
| except TypeError: |
| ss = 0 |
| buf = buf[10:] |
| |
| if buf[0] == '+': |
| hh -= int(buf[1:3]) |
| mm -= int(buf[3:5]) |
| elif buf[0] == '-': |
| hh += int(buf[1:3]) |
| mm += int(buf[3:5]) |
| return timegm((2000 + yy, mn, dd, hh, mm, ss, 0, 0, 0)) |
| |
| |
| def decode(buf): |
| """Sleazy ASN.1 decoder. |
| |
| TODO: Long description here. |
| |
| Args: |
| buf: A buffer with Sleazy ASN.1 data. |
| |
| Returns: |
| A list of (id, value) tuples from ASN.1 BER/DER encoded buffer. |
| |
| Raises: |
| UnpackError: An error occurred the ASN.1 length exceed. |
| """ |
| msg = [] |
| while buf: |
| t = compat_ord(buf[0]) |
| constructed = t & CONSTRUCTED |
| tag = t & TAGMASK |
| l_ = compat_ord(buf[1]) |
| c = 0 |
| if constructed and l_ == 128: |
| # XXX - constructed, indefinite length |
| msg.append((t, decode(buf[2:]))) |
| elif l_ >= 128: |
| c = l_ & 127 |
| if c == 1: |
| l_ = compat_ord(buf[2]) |
| elif c == 2: |
| l_ = struct.unpack('>H', buf[2:4])[0] |
| elif c == 3: |
| l_ = struct.unpack('>I', buf[1:5])[0] & 0xfff |
| c = 2 |
| elif c == 4: |
| l_ = struct.unpack('>I', buf[2:6])[0] |
| else: |
| # XXX - can be up to 127 bytes, but... |
| raise dpkt.UnpackError('excessive long-form ASN.1 length %d' % l_) |
| |
| # Skip type, length |
| buf = buf[2 + c:] |
| |
| # Parse content |
| if constructed: |
| msg.append((t, decode(buf))) |
| elif tag == INTEGER: |
| if l_ == 0: |
| n = 0 |
| elif l_ == 1: |
| n = compat_ord(buf[0]) |
| elif l_ == 2: |
| n = struct.unpack('>H', buf[:2])[0] |
| elif l_ == 3: |
| n = struct.unpack('>I', buf[:4])[0] >> 8 |
| elif l_ == 4: |
| n = struct.unpack('>I', buf[:4])[0] |
| else: |
| raise dpkt.UnpackError('excessive integer length > %d bytes' % l_) |
| msg.append((t, n)) |
| elif tag == UTC_TIME: |
| msg.append((t, utctime(buf[:l_]))) |
| else: |
| msg.append((t, buf[:l_])) |
| |
| # Skip content |
| buf = buf[l_:] |
| return msg |
| |
| |
| def test_asn1(): |
| s = ( |
| b'0\x82\x02Q\x02\x01\x0bc\x82\x02J\x04xcn=Douglas J Song 1, ou=Information Technology Division,' |
| b' ou=Faculty and Staff, ou=People, o=University of Michigan, c=US\n\x01\x00\n\x01\x03\x02\x01' |
| b'\x00\x02\x01\x00\x01\x01\x00\x87\x0bobjectclass0\x82\x01\xb0\x04\rmemberOfGroup\x04\x03acl' |
| b'\x04\x02cn\x04\x05title\x04\rpostalAddress\x04\x0ftelephoneNumber\x04\x04mail\x04\x06member' |
| b'\x04\thomePhone\x04\x11homePostalAddress\x04\x0bobjectClass\x04\x0bdescription\x04\x18' |
| b'facsimileTelephoneNumber\x04\x05pager\x04\x03uid\x04\x0cuserPassword\x04\x08joinable\x04\x10' |
| b'associatedDomain\x04\x05owner\x04\x0erfc822ErrorsTo\x04\x08ErrorsTo\x04\x10rfc822RequestsTo\x04\n' |
| b'RequestsTo\x04\tmoderator\x04\nlabeledURL\x04\nonVacation\x04\x0fvacationMessage\x04\x05drink\x04\x0e' |
| b'lastModifiedBy\x04\x10lastModifiedTime\x04\rmodifiersname\x04\x0fmodifytimestamp\x04\x0ccreatorsname' |
| b'\x04\x0fcreatetimestamp' |
| ) |
| assert decode(s) == [ |
| (48, [ |
| (2, 11), |
| (99, [ |
| (4, ( |
| b'cn=Douglas J Song 1, ' |
| b'ou=Information Technology Division, ' |
| b'ou=Faculty and Staff, ' |
| b'ou=People, ' |
| b'o=University of Michigan, ' |
| b'c=US' |
| )), |
| (10, b'\x00'), |
| (10, b'\x03'), |
| (2, 0), |
| (2, 0), |
| (1, b'\x00'), |
| (135, b'objectclass'), |
| (48, [ |
| (4, b'memberOfGroup'), |
| (4, b'acl'), |
| (4, b'cn'), |
| (4, b'title'), |
| (4, b'postalAddress'), |
| (4, b'telephoneNumber'), |
| (4, b'mail'), |
| (4, b'member'), |
| (4, b'homePhone'), |
| (4, b'homePostalAddress'), |
| (4, b'objectClass'), |
| (4, b'description'), |
| (4, b'facsimileTelephoneNumber'), |
| (4, b'pager'), |
| (4, b'uid'), |
| (4, b'userPassword'), |
| (4, b'joinable'), |
| (4, b'associatedDomain'), |
| (4, b'owner'), |
| (4, b'rfc822ErrorsTo'), |
| (4, b'ErrorsTo'), |
| (4, b'rfc822RequestsTo'), |
| (4, b'RequestsTo'), |
| (4, b'moderator'), |
| (4, b'labeledURL'), |
| (4, b'onVacation'), |
| (4, b'vacationMessage'), |
| (4, b'drink'), |
| (4, b'lastModifiedBy'), |
| (4, b'lastModifiedTime'), |
| (4, b'modifiersname'), |
| (4, b'modifytimestamp'), |
| (4, b'creatorsname'), |
| (4, b'createtimestamp'), |
| ]) |
| ]) |
| ]) |
| ] |
| |
| |
| def test_utctime(): |
| buf = ( |
| '201005' # yymndd |
| '012345' # hhmmss |
| '+1234' # +hhmm |
| ) |
| assert utctime(buf) == 1601815785.0 |
| |
| buf = ( |
| '201005' # yymndd |
| '012345' # hhmmss |
| '-1234' # -hhmm |
| ) |
| assert utctime(buf) == 1601906265.0 |
| |
| |
| def test_decode(): |
| import pytest |
| from binascii import unhexlify |
| buf = unhexlify( |
| '20' # CONSTRUCTED |
| '80' # 128 | 0 |
| ) |
| assert decode(buf) == [(32, []), (32, [])] |
| |
| # unpacking UTC_TIME |
| buf = unhexlify( |
| '17' # t: code: UTC_TIME |
| '81' # l_: code: 128 | 1 (constructed |
| '22' # data len |
| '3230313030353031323334352b30303030' |
| |
| ) |
| assert decode(buf) == [(23, 1601861025.0)] |
| |
| # unpacking 2-byte size; zero-length integer |
| buf = unhexlify( |
| '02' # t: INTEGER |
| '82' # l_: 128 | 2 |
| '0000' # new l_ |
| ) |
| assert decode(buf) == [(2, 0)] |
| |
| # unpacking 3-byte size |
| buf = unhexlify( |
| '02' # t: INTEGER |
| '83' # l_: 128 | 3 |
| '000001' # new l_ |
| ) |
| assert decode(buf) == [(2, 1)] |
| |
| # unpacking 4-byte size |
| buf = unhexlify( |
| '02' # t: INTEGER |
| '84' # l_: 128 | 4 |
| '00000002' # new l_ |
| 'abcd' |
| ) |
| assert decode(buf) == [(2, 43981)] |
| |
| # unpacking 4-byte size |
| buf = unhexlify( |
| '02' # t: INTEGER |
| '85' # l_: 128 | 5 |
| ) |
| with pytest.raises(dpkt.UnpackError, match="excessive long-form ASN.1 length 133"): |
| decode(buf) |
| |
| # unpacking 1-byte size; 4-byte integer |
| buf = unhexlify( |
| '02' # t: INTEGER |
| '81' # l_: 128 | 1 |
| '04' # new l_ |
| '12345678' # integer |
| ) |
| assert decode(buf) == [(2, 305419896)] |
| |
| # unpacking 1-byte size; 4-byte integer |
| buf = unhexlify( |
| '02' # t: INTEGER |
| '81' # l_: 128 | 1 |
| '05' # new l_ |
| ) |
| with pytest.raises(dpkt.UnpackError, match="excessive integer length > 5 bytes"): |
| decode(buf) |
| |
| # unpacking 1-byte size; 3-byte integer |
| buf = unhexlify( |
| '02' # t: INTEGER |
| '81' # l_: 128 | 1 |
| '03' # new l_ |
| '123456' # integer |
| |
| '02' # t: INTEGER |
| '81' # l_: 128 | 1 |
| '00' # new l_ |
| ) |
| assert decode(buf) == [ |
| (2, 1193046), |
| (2, 0), |
| ] |