454 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Simple encoder primitives for ASN.1 BER/DER/CER4 *5 * Copyright (C) 2019 James.Bottomley@HansenPartnership.com6 */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.115 * @data: pointer to the pointer to the data16 * @end_data: end of data pointer, points one beyond last usable byte in @data17 * @integer: integer to be encoded18 *19 * This is a simplified encoder: it only currently does20 * positive integers, but it should be simple enough to add the21 * 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 bit60 * 7 clear in two's complement (otherwise it's a61 * negative number) so prepend a leading zero if62 * that's not the case63 */64 if (!found && (byte & 0x80)) {65 /*66 * no check needed here, we already know we67 * have len >= 168 */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.1133 * @data: position to begin encoding at134 * @end_data: end of data pointer, points one beyond last usable byte in @data135 * @oid: array of oids136 * @oid_len: length of oid array137 *138 * this encodes an OID up to ASN.1 when presented as an array of OID values139 */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 for (i = 2; i < oid_len; i++) {168 ret = asn1_encode_oid_digit(&d, &data_len, oid[i]);169 if (ret < 0)170 return ERR_PTR(ret);171 }172 173 data[1] = d - data - 2;174 175 return d;176}177EXPORT_SYMBOL_GPL(asn1_encode_oid);178 179/**180 * asn1_encode_length() - encode a length to follow an ASN.1 tag181 * @data: pointer to encode at182 * @data_len: pointer to remaining length (adjusted by routine)183 * @len: length to encode184 *185 * This routine can encode lengths up to 65535 using the ASN.1 rules.186 * It will accept a negative length and place a zero length tag187 * instead (to keep the ASN.1 valid). This convention allows other188 * encoder primitives to accept negative lengths as singalling the189 * sequence will be re-encoded when the length is known.190 */191static int asn1_encode_length(unsigned char **data, int *data_len, int len)192{193 if (*data_len < 1)194 return -EINVAL;195 196 if (len < 0) {197 *((*data)++) = 0;198 (*data_len)--;199 return 0;200 }201 202 if (len <= 0x7f) {203 *((*data)++) = len;204 (*data_len)--;205 return 0;206 }207 208 if (*data_len < 2)209 return -EINVAL;210 211 if (len <= 0xff) {212 *((*data)++) = 0x81;213 *((*data)++) = len & 0xff;214 *data_len -= 2;215 return 0;216 }217 218 if (*data_len < 3)219 return -EINVAL;220 221 if (len <= 0xffff) {222 *((*data)++) = 0x82;223 *((*data)++) = (len >> 8) & 0xff;224 *((*data)++) = len & 0xff;225 *data_len -= 3;226 return 0;227 }228 229 if (WARN(len > 0xffffff, "ASN.1 length can't be > 0xffffff"))230 return -EINVAL;231 232 if (*data_len < 4)233 return -EINVAL;234 *((*data)++) = 0x83;235 *((*data)++) = (len >> 16) & 0xff;236 *((*data)++) = (len >> 8) & 0xff;237 *((*data)++) = len & 0xff;238 *data_len -= 4;239 240 return 0;241}242 243/**244 * asn1_encode_tag() - add a tag for optional or explicit value245 * @data: pointer to place tag at246 * @end_data: end of data pointer, points one beyond last usable byte in @data247 * @tag: tag to be placed248 * @string: the data to be tagged249 * @len: the length of the data to be tagged250 *251 * Note this currently only handles short form tags < 31.252 *253 * Standard usage is to pass in a @tag, @string and @length and the254 * @string will be ASN.1 encoded with @tag and placed into @data. If255 * the encoding would put data past @end_data then an error is256 * returned, otherwise a pointer to a position one beyond the encoding257 * is returned.258 *259 * To encode in place pass a NULL @string and -1 for @len and the260 * maximum allowable beginning and end of the data; all this will do261 * is add the current maximum length and update the data pointer to262 * the place where the tag contents should be placed is returned. The263 * data should be copied in by the calling routine which should then264 * repeat the prior statement but now with the known length. In order265 * to avoid having to keep both before and after pointers, the repeat266 * expects to be called with @data pointing to where the first encode267 * returned it and still NULL for @string but the real length in @len.268 */269unsigned char *270asn1_encode_tag(unsigned char *data, const unsigned char *end_data,271 u32 tag, const unsigned char *string, int len)272{273 int data_len = end_data - data;274 int ret;275 276 if (WARN(tag > 30, "ASN.1 tag can't be > 30"))277 return ERR_PTR(-EINVAL);278 279 if (!string && WARN(len > 127,280 "BUG: recode tag is too big (>127)"))281 return ERR_PTR(-EINVAL);282 283 if (IS_ERR(data))284 return data;285 286 if (!string && len > 0) {287 /*288 * we're recoding, so move back to the start of the289 * tag and install a dummy length because the real290 * data_len should be NULL291 */292 data -= 2;293 data_len = 2;294 }295 296 if (data_len < 2)297 return ERR_PTR(-EINVAL);298 299 *(data++) = _tagn(CONT, CONS, tag);300 data_len--;301 ret = asn1_encode_length(&data, &data_len, len);302 if (ret < 0)303 return ERR_PTR(ret);304 305 if (!string)306 return data;307 308 if (data_len < len)309 return ERR_PTR(-EINVAL);310 311 memcpy(data, string, len);312 data += len;313 314 return data;315}316EXPORT_SYMBOL_GPL(asn1_encode_tag);317 318/**319 * asn1_encode_octet_string() - encode an ASN.1 OCTET STRING320 * @data: pointer to encode at321 * @end_data: end of data pointer, points one beyond last usable byte in @data322 * @string: string to be encoded323 * @len: length of string324 *325 * Note ASN.1 octet strings may contain zeros, so the length is obligatory.326 */327unsigned char *328asn1_encode_octet_string(unsigned char *data,329 const unsigned char *end_data,330 const unsigned char *string, u32 len)331{332 int data_len = end_data - data;333 int ret;334 335 if (IS_ERR(data))336 return data;337 338 /* need minimum of 2 bytes for tag and length of zero length string */339 if (data_len < 2)340 return ERR_PTR(-EINVAL);341 342 *(data++) = _tag(UNIV, PRIM, OTS);343 data_len--;344 345 ret = asn1_encode_length(&data, &data_len, len);346 if (ret)347 return ERR_PTR(ret);348 349 if (data_len < len)350 return ERR_PTR(-EINVAL);351 352 memcpy(data, string, len);353 data += len;354 355 return data;356}357EXPORT_SYMBOL_GPL(asn1_encode_octet_string);358 359/**360 * asn1_encode_sequence() - wrap a byte stream in an ASN.1 SEQUENCE361 * @data: pointer to encode at362 * @end_data: end of data pointer, points one beyond last usable byte in @data363 * @seq: data to be encoded as a sequence364 * @len: length of the data to be encoded as a sequence365 *366 * Fill in a sequence. To encode in place, pass NULL for @seq and -1367 * for @len; then call again once the length is known (still with NULL368 * for @seq). In order to avoid having to keep both before and after369 * pointers, the repeat expects to be called with @data pointing to370 * where the first encode placed it.371 */372unsigned char *373asn1_encode_sequence(unsigned char *data, const unsigned char *end_data,374 const unsigned char *seq, int len)375{376 int data_len = end_data - data;377 int ret;378 379 if (!seq && WARN(len > 127,380 "BUG: recode sequence is too big (>127)"))381 return ERR_PTR(-EINVAL);382 383 if (IS_ERR(data))384 return data;385 386 if (!seq && len >= 0) {387 /*388 * we're recoding, so move back to the start of the389 * sequence and install a dummy length because the390 * real length should be NULL391 */392 data -= 2;393 data_len = 2;394 }395 396 if (data_len < 2)397 return ERR_PTR(-EINVAL);398 399 *(data++) = _tag(UNIV, CONS, SEQ);400 data_len--;401 402 ret = asn1_encode_length(&data, &data_len, len);403 if (ret)404 return ERR_PTR(ret);405 406 if (!seq)407 return data;408 409 if (data_len < len)410 return ERR_PTR(-EINVAL);411 412 memcpy(data, seq, len);413 data += len;414 415 return data;416}417EXPORT_SYMBOL_GPL(asn1_encode_sequence);418 419/**420 * asn1_encode_boolean() - encode a boolean value to ASN.1421 * @data: pointer to encode at422 * @end_data: end of data pointer, points one beyond last usable byte in @data423 * @val: the boolean true/false value424 */425unsigned char *426asn1_encode_boolean(unsigned char *data, const unsigned char *end_data,427 bool val)428{429 int data_len = end_data - data;430 431 if (IS_ERR(data))432 return data;433 434 /* booleans are 3 bytes: tag, length == 1 and value == 0 or 1 */435 if (data_len < 3)436 return ERR_PTR(-EINVAL);437 438 *(data++) = _tag(UNIV, PRIM, BOOL);439 data_len--;440 441 asn1_encode_length(&data, &data_len, 1);442 443 if (val)444 *(data++) = 1;445 else446 *(data++) = 0;447 448 return data;449}450EXPORT_SYMBOL_GPL(asn1_encode_boolean);451 452MODULE_DESCRIPTION("Simple encoder primitives for ASN.1 BER/DER/CER");453MODULE_LICENSE("GPL");454