Line | Count | Source |
1 | | /*- |
2 | | * Copyright (c) 2016 Christos Zoulas |
3 | | * All rights reserved. |
4 | | * |
5 | | * Redistribution and use in source and binary forms, with or without |
6 | | * modification, are permitted provided that the following conditions |
7 | | * are met: |
8 | | * 1. Redistributions of source code must retain the above copyright |
9 | | * notice, this list of conditions and the following disclaimer. |
10 | | * 2. Redistributions in binary form must reproduce the above copyright |
11 | | * notice, this list of conditions and the following disclaimer in the |
12 | | * documentation and/or other materials provided with the distribution. |
13 | | * |
14 | | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
15 | | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
16 | | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
17 | | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
18 | | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
19 | | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
20 | | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
21 | | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
22 | | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
23 | | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
24 | | * POSSIBILITY OF SUCH DAMAGE. |
25 | | */ |
26 | | /* |
27 | | * DER (Distinguished Encoding Rules) Parser |
28 | | * |
29 | | * Sources: |
30 | | * https://en.wikipedia.org/wiki/X.690 |
31 | | * http://fm4dd.com/openssl/certexamples.htm |
32 | | * http://blog.engelke.com/2014/10/17/parsing-ber-and-der-encoded-asn-1-objects/ |
33 | | */ |
34 | | #ifndef TEST_DER |
35 | | #include "file.h" |
36 | | |
37 | | #ifndef lint |
38 | | FILE_RCSID("@(#)$File: der.c,v 1.28 2024/11/25 22:31:53 christos Exp $") |
39 | | #endif |
40 | | #else |
41 | | #define SIZE_T_FORMAT "z" |
42 | | #define CAST(a, b) ((a)(b)) |
43 | | #endif |
44 | | |
45 | | #include <sys/types.h> |
46 | | |
47 | | #include <stdio.h> |
48 | | #include <fcntl.h> |
49 | | #include <stdlib.h> |
50 | | #include <string.h> |
51 | | #include <ctype.h> |
52 | | |
53 | | #ifndef TEST_DER |
54 | | #include "magic.h" |
55 | | #include "der.h" |
56 | | #else |
57 | | #include <sys/mman.h> |
58 | | #include <sys/stat.h> |
59 | | #include <err.h> |
60 | | #endif |
61 | | |
62 | 1.01M | #define DER_BAD CAST(uint32_t, -1) |
63 | | |
64 | | #define DER_CLASS_UNIVERSAL 0 |
65 | | #define DER_CLASS_APPLICATION 1 |
66 | | #define DER_CLASS_CONTEXT 2 |
67 | | #define DER_CLASS_PRIVATE 3 |
68 | | #if defined(DEBUG_DER) || defined(TEST_DER) |
69 | | static const char der_class[] = "UACP"; |
70 | | #endif |
71 | | |
72 | | #define DER_TYPE_PRIMITIVE 0 |
73 | | #define DER_TYPE_CONSTRUCTED 1 |
74 | | #if defined(DEBUG_DER) || defined(TEST_DER) |
75 | | static const char der_type[] = "PC"; |
76 | | #endif |
77 | | |
78 | | #define DER_TAG_EOC 0x00 |
79 | | #define DER_TAG_BOOLEAN 0x01 |
80 | | #define DER_TAG_INTEGER 0x02 |
81 | | #define DER_TAG_BIT STRING 0x03 |
82 | | #define DER_TAG_OCTET_STRING 0x04 |
83 | | #define DER_TAG_NULL 0x05 |
84 | | #define DER_TAG_OBJECT_IDENTIFIER 0x06 |
85 | | #define DER_TAG_OBJECT_DESCRIPTOR 0x07 |
86 | | #define DER_TAG_EXTERNAL 0x08 |
87 | | #define DER_TAG_REAL 0x09 |
88 | | #define DER_TAG_ENUMERATED 0x0a |
89 | | #define DER_TAG_EMBEDDED_PDV 0x0b |
90 | 168 | #define DER_TAG_UTF8_STRING 0x0c |
91 | | #define DER_TAG_RELATIVE_OID 0x0d |
92 | | #define DER_TAG_TIME 0x0e |
93 | | #define DER_TAG_RESERVED_2 0x0f |
94 | | #define DER_TAG_SEQUENCE 0x10 |
95 | | #define DER_TAG_SET 0x11 |
96 | | #define DER_TAG_NUMERIC_STRING 0x12 |
97 | 78 | #define DER_TAG_PRINTABLE_STRING 0x13 |
98 | | #define DER_TAG_T61_STRING 0x14 |
99 | | #define DER_TAG_VIDEOTEX_STRING 0x15 |
100 | 168 | #define DER_TAG_IA5_STRING 0x16 |
101 | 0 | #define DER_TAG_UTCTIME 0x17 |
102 | | #define DER_TAG_GENERALIZED_TIME 0x18 |
103 | | #define DER_TAG_GRAPHIC_STRING 0x19 |
104 | | #define DER_TAG_VISIBLE_STRING 0x1a |
105 | | #define DER_TAG_GENERAL_STRING 0x1b |
106 | | #define DER_TAG_UNIVERSAL_STRING 0x1c |
107 | | #define DER_TAG_CHARACTER_STRING 0x1d |
108 | | #define DER_TAG_BMP_STRING 0x1e |
109 | | #define DER_TAG_DATE 0x1f |
110 | | #define DER_TAG_TIME_OF_DAY 0x20 |
111 | | #define DER_TAG_DATE_TIME 0x21 |
112 | | #define DER_TAG_DURATION 0x22 |
113 | | #define DER_TAG_OID_IRI 0x23 |
114 | | #define DER_TAG_RELATIVE_OID_IRI 0x24 |
115 | 267k | #define DER_TAG_LAST 0x25 |
116 | | |
117 | | static const char *der__tag[] = { |
118 | | "eoc", "bool", "int", "bit_str", "octet_str", |
119 | | "null", "obj_id", "obj_desc", "ext", "real", |
120 | | "enum", "embed", "utf8_str", "rel_oid", "time", |
121 | | "res2", "seq", "set", "num_str", "prt_str", |
122 | | "t61_str", "vid_str", "ia5_str", "utc_time", "gen_time", |
123 | | "gr_str", "vis_str", "gen_str", "univ_str", "char_str", |
124 | | "bmp_str", "date", "tod", "datetime", "duration", |
125 | | "oid-iri", "rel-oid-iri", |
126 | | }; |
127 | | |
128 | | #ifdef DEBUG_DER |
129 | | #define DPRINTF(a) printf a |
130 | | #else |
131 | | #define DPRINTF(a) |
132 | | #endif |
133 | | |
134 | | #ifdef TEST_DER |
135 | | file_private uint8_t |
136 | | getclass(uint8_t c) |
137 | | { |
138 | | return c >> 6; |
139 | | } |
140 | | |
141 | | file_private uint8_t |
142 | | gettype(uint8_t c) |
143 | | { |
144 | | return (c >> 5) & 1; |
145 | | } |
146 | | #endif |
147 | | |
148 | | file_private uint32_t |
149 | | gettag(const uint8_t *c, size_t *p, size_t l) |
150 | 443k | { |
151 | 443k | uint32_t tag; |
152 | | |
153 | 443k | if (*p >= l) |
154 | 4.20k | return DER_BAD; |
155 | | |
156 | 439k | tag = c[(*p)++] & 0x1f; |
157 | | |
158 | 439k | if (tag != 0x1f) |
159 | 263k | return tag; |
160 | | |
161 | 176k | if (*p >= l) |
162 | 1.43k | return DER_BAD; |
163 | | |
164 | 1.00G | while (c[*p] >= 0x80) { |
165 | 1.00G | tag = tag * 128 + c[(*p)++] - 0x80; |
166 | 1.00G | if (*p >= l) |
167 | 2.82k | return DER_BAD; |
168 | 1.00G | } |
169 | 172k | return tag; |
170 | 174k | } |
171 | | |
172 | | /* |
173 | | * Read the length of a DER tag from the input. |
174 | | * |
175 | | * `c` is the input, `p` is an output parameter that specifies how much of the |
176 | | * input we consumed, and `l` is the maximum input length. |
177 | | * |
178 | | * Returns the length, or DER_BAD if the end of the input is reached or the |
179 | | * length exceeds the remaining input. |
180 | | */ |
181 | | file_private uint32_t |
182 | | getlength(const uint8_t *c, size_t *p, size_t l) |
183 | 431k | { |
184 | 431k | uint8_t digits, i; |
185 | 431k | size_t len; |
186 | 431k | int is_onebyte_result; |
187 | | |
188 | 431k | if (*p >= l) { |
189 | 4.02k | DPRINTF(("%s:[1] %zu >= %zu\n", __func__, *p, l)); |
190 | 4.02k | return DER_BAD; |
191 | 4.02k | } |
192 | | |
193 | | /* |
194 | | * Digits can either be 0b0 followed by the result, or 0b1 |
195 | | * followed by the number of digits of the result. In either case, |
196 | | * we verify that we can read so many bytes from the input. |
197 | | */ |
198 | 427k | is_onebyte_result = (c[*p] & 0x80) == 0; |
199 | 427k | digits = c[(*p)++] & 0x7f; |
200 | 427k | if (*p + digits >= l) { |
201 | 115k | DPRINTF(("%s:[2] %zu + %u >= %zu\n", __func__, *p, digits, l)); |
202 | 115k | return DER_BAD; |
203 | 115k | } |
204 | | |
205 | 312k | if (is_onebyte_result) |
206 | 258k | return digits; |
207 | | |
208 | | /* |
209 | | * Decode len. We've already verified that we're allowed to read |
210 | | * `digits` bytes. |
211 | | */ |
212 | 54.0k | len = 0; |
213 | 3.65M | for (i = 0; i < digits; i++) |
214 | 3.59M | len = (len << 8) | c[(*p)++]; |
215 | | |
216 | 54.0k | if (len > UINT32_MAX - *p || *p + len > l) { |
217 | 15.5k | DPRINTF(("%s:[3] bad len %zu + %zu >= %zu\n", |
218 | 15.5k | __func__, *p, len, l)); |
219 | 15.5k | return DER_BAD; |
220 | 15.5k | } |
221 | 38.4k | return CAST(uint32_t, len); |
222 | 54.0k | } |
223 | | |
224 | | file_private const char * |
225 | | der_tag(char *buf, size_t len, uint32_t tag) |
226 | 267k | { |
227 | 267k | if (tag < DER_TAG_LAST) |
228 | 171k | strlcpy(buf, der__tag[tag], len); |
229 | 96.1k | else |
230 | 96.1k | snprintf(buf, len, "%#x", tag); |
231 | 267k | return buf; |
232 | 267k | } |
233 | | |
234 | | #ifndef TEST_DER |
235 | | file_private int |
236 | | der_data(char *buf, size_t blen, uint32_t tag, const void *q, uint32_t len) |
237 | 1.42k | { |
238 | 1.42k | uint32_t i; |
239 | 1.42k | const uint8_t *d = CAST(const uint8_t *, q); |
240 | 1.42k | switch (tag) { |
241 | 78 | case DER_TAG_PRINTABLE_STRING: |
242 | 168 | case DER_TAG_UTF8_STRING: |
243 | 168 | case DER_TAG_IA5_STRING: |
244 | 168 | return snprintf(buf, blen, "%.*s", len, RCAST(const char *, q)); |
245 | 0 | case DER_TAG_UTCTIME: |
246 | 0 | if (len < 12) |
247 | 0 | break; |
248 | 0 | return snprintf(buf, blen, |
249 | 0 | "20%c%c-%c%c-%c%c %c%c:%c%c:%c%c GMT", d[0], d[1], d[2], |
250 | 0 | d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], d[11]); |
251 | 1.26k | default: |
252 | 1.26k | break; |
253 | 1.42k | } |
254 | | |
255 | 7.03k | for (i = 0; i < len; i++) { |
256 | 5.77k | uint32_t z = i << 1; |
257 | 5.77k | if (z < blen - 2) |
258 | 3.69k | snprintf(buf + z, blen - z, "%.2x", d[i]); |
259 | 5.77k | } |
260 | 1.26k | return len * 2; |
261 | 1.42k | } |
262 | | |
263 | | file_protected int32_t |
264 | | der_offs(struct magic_set *ms, struct magic *m, size_t nbytes) |
265 | 29.4k | { |
266 | 29.4k | const uint8_t *b = RCAST(const uint8_t *, ms->search.s); |
267 | 29.4k | size_t offs = 0, len = ms->search.s_len ? ms->search.s_len : nbytes; |
268 | | |
269 | 29.4k | if (gettag(b, &offs, len) == DER_BAD) { |
270 | 0 | DPRINTF(("%s: bad tag 1\n", __func__)); |
271 | 0 | return -1; |
272 | 0 | } |
273 | 29.4k | DPRINTF(("%s1: %u %" SIZE_T_FORMAT "u %d\n", __func__, ms->offset, |
274 | 29.4k | offs, m->offset)); |
275 | | |
276 | 29.4k | uint32_t tlen = getlength(b, &offs, len); |
277 | 29.4k | if (tlen == DER_BAD) { |
278 | 0 | DPRINTF(("%s: bad tag 2\n", __func__)); |
279 | 0 | return -1; |
280 | 0 | } |
281 | 29.4k | DPRINTF(("%s2: %u %" SIZE_T_FORMAT "u %u\n", __func__, ms->offset, |
282 | 29.4k | offs, tlen)); |
283 | | |
284 | 29.4k | offs += ms->offset + m->offset; |
285 | 29.4k | DPRINTF(("cont_level = %d\n", m->cont_level)); |
286 | | #ifdef DEBUG_DER |
287 | | size_t i; |
288 | | for (i = 0; i < m->cont_level; i++) |
289 | | printf("cont_level[%" SIZE_T_FORMAT "u] = %d\n", i, |
290 | | ms->c.li[i].off); |
291 | | #endif |
292 | 29.4k | if (m->cont_level != 0) { |
293 | 3.66k | if (offs + tlen > nbytes) |
294 | 0 | return -1; |
295 | 3.66k | ms->c.li[m->cont_level - 1].off = CAST(int, offs + tlen); |
296 | 3.66k | DPRINTF(("cont_level[%u] = %d\n", m->cont_level - 1, |
297 | 3.66k | ms->c.li[m->cont_level - 1].off)); |
298 | 3.66k | } |
299 | 29.4k | return CAST(int32_t, offs); |
300 | 29.4k | } |
301 | | |
302 | | file_protected int |
303 | | der_cmp(struct magic_set *ms, struct magic *m) |
304 | 414k | { |
305 | 414k | const uint8_t *b = RCAST(const uint8_t *, ms->search.s); |
306 | 414k | const char *s = m->value.s; |
307 | 414k | size_t offs = 0, len = ms->search.s_len; |
308 | 414k | uint32_t tag, tlen; |
309 | 414k | char buf[128]; |
310 | | |
311 | 414k | DPRINTF(("%s: compare %zu bytes\n", __func__, len)); |
312 | | |
313 | 414k | tag = gettag(b, &offs, len); |
314 | 414k | if (tag == DER_BAD) { |
315 | 12.0k | DPRINTF(("%s: bad tag 1\n", __func__)); |
316 | 12.0k | return -1; |
317 | 12.0k | } |
318 | | |
319 | 402k | DPRINTF(("%s1: %d %" SIZE_T_FORMAT "u %d\n", __func__, ms->offset, |
320 | 402k | offs, m->offset)); |
321 | | |
322 | 402k | tlen = getlength(b, &offs, len); |
323 | 402k | if (tlen == DER_BAD) { |
324 | 134k | DPRINTF(("%s: bad tag 2\n", __func__)); |
325 | 134k | return -1; |
326 | 134k | } |
327 | | |
328 | 267k | der_tag(buf, sizeof(buf), tag); |
329 | 267k | if ((ms->flags & MAGIC_DEBUG) != 0) |
330 | 0 | fprintf(stderr, "%s: tag %p got=%s exp=%s\n", __func__, b, |
331 | 0 | buf, s); |
332 | 267k | size_t slen = strlen(buf); |
333 | | |
334 | 267k | if (strncmp(buf, s, slen) != 0) { |
335 | 235k | DPRINTF(("%s: no string match %s != %s\n", __func__, buf, s)); |
336 | 235k | return 0; |
337 | 235k | } |
338 | | |
339 | 32.2k | s += slen; |
340 | | |
341 | 33.5k | again: |
342 | 33.5k | switch (*s) { |
343 | 28.8k | case '\0': |
344 | 28.8k | DPRINTF(("%s: EOF match\n", __func__)); |
345 | 28.8k | return 1; |
346 | 1.42k | case '=': |
347 | 1.42k | s++; |
348 | 1.42k | goto val; |
349 | 3.24k | default: |
350 | 3.24k | if (!isdigit(CAST(unsigned char, *s))) { |
351 | 0 | DPRINTF(("%s: no digit %c\n", __func__, *s)); |
352 | 0 | return 0; |
353 | 0 | } |
354 | | |
355 | 3.24k | slen = 0; |
356 | 3.24k | do |
357 | 4.84k | slen = slen * 10 + *s - '0'; |
358 | 3.24k | while (isdigit(CAST(unsigned char, *++s))); |
359 | 3.24k | if ((ms->flags & MAGIC_DEBUG) != 0) |
360 | 0 | fprintf(stderr, "%s: len %" SIZE_T_FORMAT "u %u\n", |
361 | 0 | __func__, slen, tlen); |
362 | 3.24k | if (tlen != slen) { |
363 | 1.98k | DPRINTF(("%s: len %u != %zu\n", __func__, tlen, slen)); |
364 | 1.98k | return 0; |
365 | 1.98k | } |
366 | 1.26k | goto again; |
367 | 33.5k | } |
368 | 1.42k | val: |
369 | 1.42k | DPRINTF(("%s: before data %" SIZE_T_FORMAT "u %u\n", __func__, offs, |
370 | 1.42k | tlen)); |
371 | 1.42k | der_data(buf, sizeof(buf), tag, b + offs, tlen); |
372 | 1.42k | if ((ms->flags & MAGIC_DEBUG) != 0) |
373 | 0 | fprintf(stderr, "%s: data %s %s\n", __func__, buf, s); |
374 | 1.42k | if (strcmp(buf, s) != 0 && strcmp("x", s) != 0) { |
375 | 858 | DPRINTF(("%s: no string match %s != %s\n", __func__, buf, s)); |
376 | 858 | return 0; |
377 | 858 | } |
378 | 571 | strlcpy(ms->ms_value.s, buf, sizeof(ms->ms_value.s)); |
379 | 571 | DPRINTF(("%s: complete match\n", __func__)); |
380 | 571 | return 1; |
381 | 1.42k | } |
382 | | #endif |
383 | | |
384 | | #ifdef TEST_DER |
385 | | file_private void |
386 | | printtag(uint32_t tag, const void *q, uint32_t len) |
387 | | { |
388 | | const uint8_t *d = q; |
389 | | switch (tag) { |
390 | | case DER_TAG_PRINTABLE_STRING: |
391 | | case DER_TAG_UTF8_STRING: |
392 | | case DER_TAG_IA5_STRING: |
393 | | case DER_TAG_UTCTIME: |
394 | | printf("%.*s\n", len, (const char *)q); |
395 | | return; |
396 | | default: |
397 | | break; |
398 | | } |
399 | | |
400 | | for (uint32_t i = 0; i < len; i++) |
401 | | printf("%.2x", d[i]); |
402 | | printf("\n"); |
403 | | } |
404 | | |
405 | | file_private void |
406 | | printdata(size_t level, const void *v, size_t x, size_t l) |
407 | | { |
408 | | const uint8_t *p = v, *ep = p + l; |
409 | | size_t ox; |
410 | | char buf[128]; |
411 | | |
412 | | while (p + x < ep) { |
413 | | const uint8_t *q; |
414 | | uint8_t c = getclass(p[x]); |
415 | | uint8_t t = gettype(p[x]); |
416 | | ox = x; |
417 | | // if (x != 0) |
418 | | // printf("%.2x %.2x %.2x\n", p[x - 1], p[x], p[x + 1]); |
419 | | uint32_t tag = gettag(p, &x, ep - p + x); |
420 | | if (p + x >= ep) |
421 | | break; |
422 | | uint32_t len = getlength(p, &x, ep - p + x); |
423 | | |
424 | | printf("%" SIZE_T_FORMAT "u %" SIZE_T_FORMAT "u-%" |
425 | | SIZE_T_FORMAT "u %c,%c,%s,%u:", level, ox, x, |
426 | | der_class[c], der_type[t], |
427 | | der_tag(buf, sizeof(buf), tag), len); |
428 | | q = p + x; |
429 | | if (p + len > ep) |
430 | | errx(EXIT_FAILURE, "corrupt der"); |
431 | | printtag(tag, q, len); |
432 | | if (t != DER_TYPE_PRIMITIVE) |
433 | | printdata(level + 1, p, x, len + x); |
434 | | x += len; |
435 | | } |
436 | | } |
437 | | |
438 | | int |
439 | | main(int argc, char *argv[]) |
440 | | { |
441 | | int fd; |
442 | | struct stat st; |
443 | | size_t l; |
444 | | void *p; |
445 | | |
446 | | if ((fd = open(argv[1], O_RDONLY)) == -1) |
447 | | err(EXIT_FAILURE, "open `%s'", argv[1]); |
448 | | if (fstat(fd, &st) == -1) |
449 | | err(EXIT_FAILURE, "stat `%s'", argv[1]); |
450 | | l = (size_t)st.st_size; |
451 | | if ((p = mmap(NULL, l, PROT_READ, MAP_FILE, fd, 0)) == MAP_FAILED) |
452 | | err(EXIT_FAILURE, "mmap `%s'", argv[1]); |
453 | | |
454 | | printdata(0, p, 0, l); |
455 | | munmap(p, l); |
456 | | return 0; |
457 | | } |
458 | | #endif |