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