1# coding: utf-8
2
3"""
4Functions for parsing and dumping using the ASN.1 DER encoding. Exports the
5following items:
6
7 - emit()
8 - parse()
9 - peek()
10
11Other type classes are defined that help compose the types listed above.
12"""
13
14from __future__ import unicode_literals, division, absolute_import, print_function
15
16import sys
17
18from ._types import byte_cls, chr_cls, type_name
19from .util import int_from_bytes, int_to_bytes
20
21_PY2 = sys.version_info <= (3,)
22_INSUFFICIENT_DATA_MESSAGE = 'Insufficient data - %s bytes requested but only %s available'
23
24
25def emit(class_, method, tag, contents):
26    """
27    Constructs a byte string of an ASN.1 DER-encoded value
28
29    This is typically not useful. Instead, use one of the standard classes from
30    asn1crypto.core, or construct a new class with specific fields, and call the
31    .dump() method.
32
33    :param class_:
34        An integer ASN.1 class value: 0 (universal), 1 (application),
35        2 (context), 3 (private)
36
37    :param method:
38        An integer ASN.1 method value: 0 (primitive), 1 (constructed)
39
40    :param tag:
41        An integer ASN.1 tag value
42
43    :param contents:
44        A byte string of the encoded byte contents
45
46    :return:
47        A byte string of the ASN.1 DER value (header and contents)
48    """
49
50    if not isinstance(class_, int):
51        raise TypeError('class_ must be an integer, not %s' % type_name(class_))
52
53    if class_ < 0 or class_ > 3:
54        raise ValueError('class_ must be one of 0, 1, 2 or 3, not %s' % class_)
55
56    if not isinstance(method, int):
57        raise TypeError('method must be an integer, not %s' % type_name(method))
58
59    if method < 0 or method > 1:
60        raise ValueError('method must be 0 or 1, not %s' % method)
61
62    if not isinstance(tag, int):
63        raise TypeError('tag must be an integer, not %s' % type_name(tag))
64
65    if tag < 0:
66        raise ValueError('tag must be greater than zero, not %s' % tag)
67
68    if not isinstance(contents, byte_cls):
69        raise TypeError('contents must be a byte string, not %s' % type_name(contents))
70
71    return _dump_header(class_, method, tag, contents) + contents
72
73
74def parse(contents, strict=False):
75    """
76    Parses a byte string of ASN.1 BER/DER-encoded data.
77
78    This is typically not useful. Instead, use one of the standard classes from
79    asn1crypto.core, or construct a new class with specific fields, and call the
80    .load() class method.
81
82    :param contents:
83        A byte string of BER/DER-encoded data
84
85    :param strict:
86        A boolean indicating if trailing data should be forbidden - if so, a
87        ValueError will be raised when trailing data exists
88
89    :raises:
90        ValueError - when the contents do not contain an ASN.1 header or are truncated in some way
91        TypeError - when contents is not a byte string
92
93    :return:
94        A 6-element tuple:
95         - 0: integer class (0 to 3)
96         - 1: integer method
97         - 2: integer tag
98         - 3: byte string header
99         - 4: byte string content
100         - 5: byte string trailer
101    """
102
103    if not isinstance(contents, byte_cls):
104        raise TypeError('contents must be a byte string, not %s' % type_name(contents))
105
106    contents_len = len(contents)
107    info, consumed = _parse(contents, contents_len)
108    if strict and consumed != contents_len:
109        raise ValueError('Extra data - %d bytes of trailing data were provided' % (contents_len - consumed))
110    return info
111
112
113def peek(contents):
114    """
115    Parses a byte string of ASN.1 BER/DER-encoded data to find the length
116
117    This is typically used to look into an encoded value to see how long the
118    next chunk of ASN.1-encoded data is. Primarily it is useful when a
119    value is a concatenation of multiple values.
120
121    :param contents:
122        A byte string of BER/DER-encoded data
123
124    :raises:
125        ValueError - when the contents do not contain an ASN.1 header or are truncated in some way
126        TypeError - when contents is not a byte string
127
128    :return:
129        An integer with the number of bytes occupied by the ASN.1 value
130    """
131
132    if not isinstance(contents, byte_cls):
133        raise TypeError('contents must be a byte string, not %s' % type_name(contents))
134
135    info, consumed = _parse(contents, len(contents))
136    return consumed
137
138
139def _parse(encoded_data, data_len, pointer=0, lengths_only=False):
140    """
141    Parses a byte string into component parts
142
143    :param encoded_data:
144        A byte string that contains BER-encoded data
145
146    :param data_len:
147        The integer length of the encoded data
148
149    :param pointer:
150        The index in the byte string to parse from
151
152    :param lengths_only:
153        A boolean to cause the call to return a 2-element tuple of the integer
154        number of bytes in the header and the integer number of bytes in the
155        contents. Internal use only.
156
157    :return:
158        A 2-element tuple:
159         - 0: A tuple of (class_, method, tag, header, content, trailer)
160         - 1: An integer indicating how many bytes were consumed
161    """
162
163    if data_len < pointer + 2:
164        raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (2, data_len - pointer))
165
166    start = pointer
167    first_octet = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
168    pointer += 1
169
170    tag = first_octet & 31
171    # Base 128 length using 8th bit as continuation indicator
172    if tag == 31:
173        tag = 0
174        while True:
175            num = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
176            pointer += 1
177            tag *= 128
178            tag += num & 127
179            if num >> 7 == 0:
180                break
181
182    length_octet = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
183    pointer += 1
184
185    if length_octet >> 7 == 0:
186        if lengths_only:
187            return (pointer, pointer + (length_octet & 127))
188        contents_end = pointer + (length_octet & 127)
189
190    else:
191        length_octets = length_octet & 127
192        if length_octets:
193            pointer += length_octets
194            contents_end = pointer + int_from_bytes(encoded_data[pointer - length_octets:pointer], signed=False)
195            if lengths_only:
196                return (pointer, contents_end)
197
198        else:
199            # To properly parse indefinite length values, we need to scan forward
200            # parsing headers until we find a value with a length of zero. If we
201            # just scanned looking for \x00\x00, nested indefinite length values
202            # would not work.
203            contents_end = pointer
204            while contents_end < data_len:
205                sub_header_end, contents_end = _parse(encoded_data, data_len, contents_end, lengths_only=True)
206                if contents_end == sub_header_end and encoded_data[contents_end - 2:contents_end] == b'\x00\x00':
207                    break
208            if lengths_only:
209                return (pointer, contents_end)
210            if contents_end > data_len:
211                raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (contents_end, data_len))
212            return (
213                (
214                    first_octet >> 6,
215                    (first_octet >> 5) & 1,
216                    tag,
217                    encoded_data[start:pointer],
218                    encoded_data[pointer:contents_end - 2],
219                    b'\x00\x00'
220                ),
221                contents_end
222            )
223
224    if contents_end > data_len:
225        raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (contents_end, data_len))
226    return (
227        (
228            first_octet >> 6,
229            (first_octet >> 5) & 1,
230            tag,
231            encoded_data[start:pointer],
232            encoded_data[pointer:contents_end],
233            b''
234        ),
235        contents_end
236    )
237
238
239def _dump_header(class_, method, tag, contents):
240    """
241    Constructs the header bytes for an ASN.1 object
242
243    :param class_:
244        An integer ASN.1 class value: 0 (universal), 1 (application),
245        2 (context), 3 (private)
246
247    :param method:
248        An integer ASN.1 method value: 0 (primitive), 1 (constructed)
249
250    :param tag:
251        An integer ASN.1 tag value
252
253    :param contents:
254        A byte string of the encoded byte contents
255
256    :return:
257        A byte string of the ASN.1 DER header
258    """
259
260    header = b''
261
262    id_num = 0
263    id_num |= class_ << 6
264    id_num |= method << 5
265
266    if tag >= 31:
267        cont_bit = 0
268        while tag > 0:
269            header = chr_cls(cont_bit | (tag & 0x7f)) + header
270            if not cont_bit:
271                cont_bit = 0x80
272            tag = tag >> 7
273        header = chr_cls(id_num | 31) + header
274    else:
275        header += chr_cls(id_num | tag)
276
277    length = len(contents)
278    if length <= 127:
279        header += chr_cls(length)
280    else:
281        length_bytes = int_to_bytes(length)
282        header += chr_cls(0x80 | len(length_bytes))
283        header += length_bytes
284
285    return header
286