blob: 27bbe891714f914cadcec73d9407687c7ef2a405 [file] [log] [blame]
James Bottomleyb0706762021-01-27 11:06:13 -08001// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Simple encoder primitives for ASN.1 BER/DER/CER
4 *
5 * Copyright (C) 2019 James.Bottomley@HansenPartnership.com
6 */
7
8#include <linux/asn1_encoder.h>
9#include <linux/bug.h>
10#include <linux/string.h>
11#include <linux/module.h>
12
13/**
14 * asn1_encode_integer() - encode positive integer to ASN.1
15 * @data: pointer to the pointer to the data
16 * @end_data: end of data pointer, points one beyond last usable byte in @data
17 * @integer: integer to be encoded
18 *
19 * This is a simplified encoder: it only currently does
20 * positive integers, but it should be simple enough to add the
21 * negative case if a use comes along.
22 */
23unsigned char *
24asn1_encode_integer(unsigned char *data, const unsigned char *end_data,
25 s64 integer)
26{
27 int data_len = end_data - data;
28 unsigned char *d = &data[2];
29 bool found = false;
30 int i;
31
32 if (WARN(integer < 0,
33 "BUG: integer encode only supports positive integers"))
34 return ERR_PTR(-EINVAL);
35
36 if (IS_ERR(data))
37 return data;
38
39 /* need at least 3 bytes for tag, length and integer encoding */
40 if (data_len < 3)
41 return ERR_PTR(-EINVAL);
42
43 /* remaining length where at d (the start of the integer encoding) */
44 data_len -= 2;
45
46 data[0] = _tag(UNIV, PRIM, INT);
47 if (integer == 0) {
48 *d++ = 0;
49 goto out;
50 }
51
52 for (i = sizeof(integer); i > 0 ; i--) {
53 int byte = integer >> (8 * (i - 1));
54
55 if (!found && byte == 0)
56 continue;
57
58 /*
59 * for a positive number the first byte must have bit
60 * 7 clear in two's complement (otherwise it's a
61 * negative number) so prepend a leading zero if
62 * that's not the case
63 */
64 if (!found && (byte & 0x80)) {
65 /*
66 * no check needed here, we already know we
67 * have len >= 1
68 */
69 *d++ = 0;
70 data_len--;
71 }
72
73 found = true;
74 if (data_len == 0)
75 return ERR_PTR(-EINVAL);
76
77 *d++ = byte;
78 data_len--;
79 }
80
81 out:
82 data[1] = d - data - 2;
83
84 return d;
85}
86EXPORT_SYMBOL_GPL(asn1_encode_integer);
87
88/* calculate the base 128 digit values setting the top bit of the first octet */
89static int asn1_encode_oid_digit(unsigned char **_data, int *data_len, u32 oid)
90{
91 unsigned char *data = *_data;
92 int start = 7 + 7 + 7 + 7;
93 int ret = 0;
94
95 if (*data_len < 1)
96 return -EINVAL;
97
98 /* quick case */
99 if (oid == 0) {
100 *data++ = 0x80;
101 (*data_len)--;
102 goto out;
103 }
104
105 while (oid >> start == 0)
106 start -= 7;
107
108 while (start > 0 && *data_len > 0) {
109 u8 byte;
110
111 byte = oid >> start;
112 oid = oid - (byte << start);
113 start -= 7;
114 byte |= 0x80;
115 *data++ = byte;
116 (*data_len)--;
117 }
118
119 if (*data_len > 0) {
120 *data++ = oid;
121 (*data_len)--;
122 } else {
123 ret = -EINVAL;
124 }
125
126 out:
127 *_data = data;
128 return ret;
129}
130
131/**
132 * asn1_encode_oid() - encode an oid to ASN.1
133 * @data: position to begin encoding at
134 * @end_data: end of data pointer, points one beyond last usable byte in @data
135 * @oid: array of oids
136 * @oid_len: length of oid array
137 *
138 * this encodes an OID up to ASN.1 when presented as an array of OID values
139 */
140unsigned char *
141asn1_encode_oid(unsigned char *data, const unsigned char *end_data,
142 u32 oid[], int oid_len)
143{
144 int data_len = end_data - data;
145 unsigned char *d = data + 2;
146 int i, ret;
147
148 if (WARN(oid_len < 2, "OID must have at least two elements"))
149 return ERR_PTR(-EINVAL);
150
151 if (WARN(oid_len > 32, "OID is too large"))
152 return ERR_PTR(-EINVAL);
153
154 if (IS_ERR(data))
155 return data;
156
157
158 /* need at least 3 bytes for tag, length and OID encoding */
159 if (data_len < 3)
160 return ERR_PTR(-EINVAL);
161
162 data[0] = _tag(UNIV, PRIM, OID);
163 *d++ = oid[0] * 40 + oid[1];
164
165 data_len -= 3;
166
167 ret = 0;
168
169 for (i = 2; i < oid_len; i++) {
170 ret = asn1_encode_oid_digit(&d, &data_len, oid[i]);
171 if (ret < 0)
172 return ERR_PTR(ret);
173 }
174
175 data[1] = d - data - 2;
176
177 return d;
178}
179EXPORT_SYMBOL_GPL(asn1_encode_oid);
180
181/**
182 * asn1_encode_length() - encode a length to follow an ASN.1 tag
183 * @data: pointer to encode at
Zhen Lei9dbbc3b2021-07-07 18:07:31 -0700184 * @data_len: pointer to remaining length (adjusted by routine)
James Bottomleyb0706762021-01-27 11:06:13 -0800185 * @len: length to encode
186 *
187 * This routine can encode lengths up to 65535 using the ASN.1 rules.
188 * It will accept a negative length and place a zero length tag
189 * instead (to keep the ASN.1 valid). This convention allows other
190 * encoder primitives to accept negative lengths as singalling the
191 * sequence will be re-encoded when the length is known.
192 */
193static int asn1_encode_length(unsigned char **data, int *data_len, int len)
194{
195 if (*data_len < 1)
196 return -EINVAL;
197
198 if (len < 0) {
199 *((*data)++) = 0;
200 (*data_len)--;
201 return 0;
202 }
203
204 if (len <= 0x7f) {
205 *((*data)++) = len;
206 (*data_len)--;
207 return 0;
208 }
209
210 if (*data_len < 2)
211 return -EINVAL;
212
213 if (len <= 0xff) {
214 *((*data)++) = 0x81;
215 *((*data)++) = len & 0xff;
216 *data_len -= 2;
217 return 0;
218 }
219
220 if (*data_len < 3)
221 return -EINVAL;
222
223 if (len <= 0xffff) {
224 *((*data)++) = 0x82;
225 *((*data)++) = (len >> 8) & 0xff;
226 *((*data)++) = len & 0xff;
227 *data_len -= 3;
228 return 0;
229 }
230
231 if (WARN(len > 0xffffff, "ASN.1 length can't be > 0xffffff"))
232 return -EINVAL;
233
234 if (*data_len < 4)
235 return -EINVAL;
236 *((*data)++) = 0x83;
237 *((*data)++) = (len >> 16) & 0xff;
238 *((*data)++) = (len >> 8) & 0xff;
239 *((*data)++) = len & 0xff;
240 *data_len -= 4;
241
242 return 0;
243}
244
245/**
246 * asn1_encode_tag() - add a tag for optional or explicit value
247 * @data: pointer to place tag at
248 * @end_data: end of data pointer, points one beyond last usable byte in @data
249 * @tag: tag to be placed
250 * @string: the data to be tagged
251 * @len: the length of the data to be tagged
252 *
253 * Note this currently only handles short form tags < 31.
254 *
255 * Standard usage is to pass in a @tag, @string and @length and the
256 * @string will be ASN.1 encoded with @tag and placed into @data. If
257 * the encoding would put data past @end_data then an error is
258 * returned, otherwise a pointer to a position one beyond the encoding
259 * is returned.
260 *
261 * To encode in place pass a NULL @string and -1 for @len and the
262 * maximum allowable beginning and end of the data; all this will do
263 * is add the current maximum length and update the data pointer to
264 * the place where the tag contents should be placed is returned. The
265 * data should be copied in by the calling routine which should then
266 * repeat the prior statement but now with the known length. In order
267 * to avoid having to keep both before and after pointers, the repeat
268 * expects to be called with @data pointing to where the first encode
269 * returned it and still NULL for @string but the real length in @len.
270 */
271unsigned char *
272asn1_encode_tag(unsigned char *data, const unsigned char *end_data,
273 u32 tag, const unsigned char *string, int len)
274{
275 int data_len = end_data - data;
276 int ret;
277
278 if (WARN(tag > 30, "ASN.1 tag can't be > 30"))
279 return ERR_PTR(-EINVAL);
280
281 if (!string && WARN(len > 127,
282 "BUG: recode tag is too big (>127)"))
283 return ERR_PTR(-EINVAL);
284
285 if (IS_ERR(data))
286 return data;
287
288 if (!string && len > 0) {
289 /*
290 * we're recoding, so move back to the start of the
291 * tag and install a dummy length because the real
292 * data_len should be NULL
293 */
294 data -= 2;
295 data_len = 2;
296 }
297
298 if (data_len < 2)
299 return ERR_PTR(-EINVAL);
300
301 *(data++) = _tagn(CONT, CONS, tag);
302 data_len--;
303 ret = asn1_encode_length(&data, &data_len, len);
304 if (ret < 0)
305 return ERR_PTR(ret);
306
307 if (!string)
308 return data;
309
310 if (data_len < len)
311 return ERR_PTR(-EINVAL);
312
313 memcpy(data, string, len);
314 data += len;
315
316 return data;
317}
318EXPORT_SYMBOL_GPL(asn1_encode_tag);
319
320/**
321 * asn1_encode_octet_string() - encode an ASN.1 OCTET STRING
322 * @data: pointer to encode at
323 * @end_data: end of data pointer, points one beyond last usable byte in @data
324 * @string: string to be encoded
325 * @len: length of string
326 *
327 * Note ASN.1 octet strings may contain zeros, so the length is obligatory.
328 */
329unsigned char *
330asn1_encode_octet_string(unsigned char *data,
331 const unsigned char *end_data,
332 const unsigned char *string, u32 len)
333{
334 int data_len = end_data - data;
335 int ret;
336
337 if (IS_ERR(data))
338 return data;
339
340 /* need minimum of 2 bytes for tag and length of zero length string */
341 if (data_len < 2)
342 return ERR_PTR(-EINVAL);
343
344 *(data++) = _tag(UNIV, PRIM, OTS);
345 data_len--;
346
347 ret = asn1_encode_length(&data, &data_len, len);
348 if (ret)
349 return ERR_PTR(ret);
350
351 if (data_len < len)
352 return ERR_PTR(-EINVAL);
353
354 memcpy(data, string, len);
355 data += len;
356
357 return data;
358}
359EXPORT_SYMBOL_GPL(asn1_encode_octet_string);
360
361/**
362 * asn1_encode_sequence() - wrap a byte stream in an ASN.1 SEQUENCE
363 * @data: pointer to encode at
364 * @end_data: end of data pointer, points one beyond last usable byte in @data
365 * @seq: data to be encoded as a sequence
366 * @len: length of the data to be encoded as a sequence
367 *
368 * Fill in a sequence. To encode in place, pass NULL for @seq and -1
369 * for @len; then call again once the length is known (still with NULL
370 * for @seq). In order to avoid having to keep both before and after
371 * pointers, the repeat expects to be called with @data pointing to
372 * where the first encode placed it.
373 */
374unsigned char *
375asn1_encode_sequence(unsigned char *data, const unsigned char *end_data,
376 const unsigned char *seq, int len)
377{
378 int data_len = end_data - data;
379 int ret;
380
381 if (!seq && WARN(len > 127,
382 "BUG: recode sequence is too big (>127)"))
383 return ERR_PTR(-EINVAL);
384
385 if (IS_ERR(data))
386 return data;
387
388 if (!seq && len >= 0) {
389 /*
390 * we're recoding, so move back to the start of the
391 * sequence and install a dummy length because the
392 * real length should be NULL
393 */
394 data -= 2;
395 data_len = 2;
396 }
397
398 if (data_len < 2)
399 return ERR_PTR(-EINVAL);
400
401 *(data++) = _tag(UNIV, CONS, SEQ);
402 data_len--;
403
404 ret = asn1_encode_length(&data, &data_len, len);
405 if (ret)
406 return ERR_PTR(ret);
407
408 if (!seq)
409 return data;
410
411 if (data_len < len)
412 return ERR_PTR(-EINVAL);
413
414 memcpy(data, seq, len);
415 data += len;
416
417 return data;
418}
419EXPORT_SYMBOL_GPL(asn1_encode_sequence);
420
421/**
422 * asn1_encode_boolean() - encode a boolean value to ASN.1
423 * @data: pointer to encode at
424 * @end_data: end of data pointer, points one beyond last usable byte in @data
425 * @val: the boolean true/false value
426 */
427unsigned char *
428asn1_encode_boolean(unsigned char *data, const unsigned char *end_data,
429 bool val)
430{
431 int data_len = end_data - data;
432
433 if (IS_ERR(data))
434 return data;
435
436 /* booleans are 3 bytes: tag, length == 1 and value == 0 or 1 */
437 if (data_len < 3)
438 return ERR_PTR(-EINVAL);
439
440 *(data++) = _tag(UNIV, PRIM, BOOL);
441 data_len--;
442
443 asn1_encode_length(&data, &data_len, 1);
444
445 if (val)
446 *(data++) = 1;
447 else
448 *(data++) = 0;
449
450 return data;
451}
452EXPORT_SYMBOL_GPL(asn1_encode_boolean);
453
454MODULE_LICENSE("GPL");