1 // SPDX-License-Identifier: GPL-2.0-only 1 2 /* 3 * Simple encoder primitives for ASN.1 BER/DER 4 * 5 * Copyright (C) 2019 James.Bottomley@HansenPa 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 int 15 * @data: pointer to the pointer to the 16 * @end_data: end of data pointer, points on 17 * @integer: integer to be encoded 18 * 19 * This is a simplified encoder: it only curre 20 * positive integers, but it should be simple 21 * negative case if a use comes along. 22 */ 23 unsigned char * 24 asn1_encode_integer(unsigned char *data, const 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 sup 34 return ERR_PTR(-EINVAL); 35 36 if (IS_ERR(data)) 37 return data; 38 39 /* need at least 3 bytes for tag, leng 40 if (data_len < 3) 41 return ERR_PTR(-EINVAL); 42 43 /* remaining length where at d (the st 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 54 55 if (!found && byte == 0) 56 continue; 57 58 /* 59 * for a positive number the f 60 * 7 clear in two's complement 61 * negative number) so prepend 62 * that's not the case 63 */ 64 if (!found && (byte & 0x80)) { 65 /* 66 * no check needed her 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 } 86 EXPORT_SYMBOL_GPL(asn1_encode_integer); 87 88 /* calculate the base 128 digit values setting 89 static int asn1_encode_oid_digit(unsigned char 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 on 135 * @oid: array of oids 136 * @oid_len: length of oid array 137 * 138 * this encodes an OID up to ASN.1 when presen 139 */ 140 unsigned char * 141 asn1_encode_oid(unsigned char *data, const uns 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 a 149 return ERR_PTR(-EINVAL); 150 151 if (WARN(oid_len > 32, "OID is too lar 152 return ERR_PTR(-EINVAL); 153 154 if (IS_ERR(data)) 155 return data; 156 157 158 /* need at least 3 bytes for tag, leng 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 169 if (ret < 0) 170 return ERR_PTR(ret); 171 } 172 173 data[1] = d - data - 2; 174 175 return d; 176 } 177 EXPORT_SYMBOL_GPL(asn1_encode_oid); 178 179 /** 180 * asn1_encode_length() - encode a length to f 181 * @data: pointer to encode at 182 * @data_len: pointer to remaining length (adj 183 * @len: length to encode 184 * 185 * This routine can encode lengths up to 65535 186 * It will accept a negative length and place 187 * instead (to keep the ASN.1 valid). This co 188 * encoder primitives to accept negative lengt 189 * sequence will be re-encoded when the length 190 */ 191 static int asn1_encode_length(unsigned char ** 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) & 0x 224 *((*data)++) = len & 0xff; 225 *data_len -= 3; 226 return 0; 227 } 228 229 if (WARN(len > 0xffffff, "ASN.1 length 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 245 * @data: pointer to place tag at 246 * @end_data: end of data pointer, points on 247 * @tag: tag to be placed 248 * @string: the data to be tagged 249 * @len: the length of the data to be t 250 * 251 * Note this currently only handles short form 252 * 253 * Standard usage is to pass in a @tag, @strin 254 * @string will be ASN.1 encoded with @tag and 255 * the encoding would put data past @end_data 256 * returned, otherwise a pointer to a position 257 * is returned. 258 * 259 * To encode in place pass a NULL @string and 260 * maximum allowable beginning and end of the 261 * is add the current maximum length and updat 262 * the place where the tag contents should be 263 * data should be copied in by the calling rou 264 * repeat the prior statement but now with the 265 * to avoid having to keep both before and aft 266 * expects to be called with @data pointing to 267 * returned it and still NULL for @string but 268 */ 269 unsigned char * 270 asn1_encode_tag(unsigned char *data, const uns 271 u32 tag, const unsigned char * 272 { 273 int data_len = end_data - data; 274 int ret; 275 276 if (WARN(tag > 30, "ASN.1 tag can't be 277 return ERR_PTR(-EINVAL); 278 279 if (!string && WARN(len > 127, 280 "BUG: recode tag i 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 bac 289 * tag and install a dummy len 290 * data_len should be NULL 291 */ 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_ 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 } 316 EXPORT_SYMBOL_GPL(asn1_encode_tag); 317 318 /** 319 * asn1_encode_octet_string() - encode an ASN. 320 * @data: pointer to encode at 321 * @end_data: end of data pointer, points on 322 * @string: string to be encoded 323 * @len: length of string 324 * 325 * Note ASN.1 octet strings may contain zeros, 326 */ 327 unsigned char * 328 asn1_encode_octet_string(unsigned char *data, 329 const unsigned char * 330 const unsigned char * 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 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_ 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 } 357 EXPORT_SYMBOL_GPL(asn1_encode_octet_string); 358 359 /** 360 * asn1_encode_sequence() - wrap a byte stream 361 * @data: pointer to encode at 362 * @end_data: end of data pointer, points on 363 * @seq: data to be encoded as a sequen 364 * @len: length of the data to be encod 365 * 366 * Fill in a sequence. To encode in place, pa 367 * for @len; then call again once the length i 368 * for @seq). In order to avoid having to keep 369 * pointers, the repeat expects to be called w 370 * where the first encode placed it. 371 */ 372 unsigned char * 373 asn1_encode_sequence(unsigned char *data, cons 374 const unsigned char *seq, 375 { 376 int data_len = end_data - data; 377 int ret; 378 379 if (!seq && WARN(len > 127, 380 "BUG: recode sequence 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 bac 389 * sequence and install a dumm 390 * real length should be NULL 391 */ 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_ 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 } 417 EXPORT_SYMBOL_GPL(asn1_encode_sequence); 418 419 /** 420 * asn1_encode_boolean() - encode a boolean va 421 * @data: pointer to encode at 422 * @end_data: end of data pointer, points on 423 * @val: the boolean true/false value 424 */ 425 unsigned char * 426 asn1_encode_boolean(unsigned char *data, const 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 = 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 else 446 *(data++) = 0; 447 448 return data; 449 } 450 EXPORT_SYMBOL_GPL(asn1_encode_boolean); 451 452 MODULE_DESCRIPTION("Simple encoder primitives 453 MODULE_LICENSE("GPL"); 454
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.