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