From 9a10effcec5ce8f732978e29990eb94ca50711a7 Mon Sep 17 00:00:00 2001 From: jsing Date: Sat, 25 Dec 2021 07:04:03 +0000 Subject: [PATCH] Rewrite ASN.1 identifier/length parsing in CBS. Provide internal asn1_get_identifier_cbs() and asn1_get_length_cbs() functions that are called from asn1_get_object_cbs(). Convert the existing ASN1_get_object() function so that it calls asn1_get_object_cbs(), before mapping the result into the API that it implements. ok tb@ --- lib/libcrypto/Makefile | 4 +- lib/libcrypto/asn1/asn1_lib.c | 171 ++++++++++++++++++++++++++++++ lib/libcrypto/asn1/asn1_locl.h | 8 +- lib/libcrypto/asn1/asn1_old_lib.c | 129 +++++++--------------- 4 files changed, 220 insertions(+), 92 deletions(-) create mode 100644 lib/libcrypto/asn1/asn1_lib.c diff --git a/lib/libcrypto/Makefile b/lib/libcrypto/Makefile index 7279d5a1815..cc53d8e6fef 100644 --- a/lib/libcrypto/Makefile +++ b/lib/libcrypto/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.59 2021/12/17 11:28:05 tb Exp $ +# $OpenBSD: Makefile,v 1.60 2021/12/25 07:04:03 jsing Exp $ LIB= crypto LIBREBUILD=y @@ -65,7 +65,7 @@ SRCS+= n_pkey.c SRCS+= x_pkey.c x_exten.c bio_asn1.c bio_ndef.c asn_mime.c SRCS+= asn1_gen.c asn1_par.c asn1_old_lib.c asn1_err.c a_strnid.c SRCS+= evp_asn1.c asn_pack.c p5_pbe.c p5_pbev2.c p8_pkey.c asn_moid.c -SRCS+= a_time_tm.c asn1_types.c +SRCS+= a_time_tm.c asn1_types.c asn1_lib.c # bf/ SRCS+= bf_skey.c bf_ecb.c bf_cfb64.c bf_ofb64.c diff --git a/lib/libcrypto/asn1/asn1_lib.c b/lib/libcrypto/asn1/asn1_lib.c new file mode 100644 index 00000000000..542a72f6f15 --- /dev/null +++ b/lib/libcrypto/asn1/asn1_lib.c @@ -0,0 +1,171 @@ +/* $OpenBSD: asn1_lib.c,v 1.51 2021/12/25 07:04:03 jsing Exp $ */ +/* + * Copyright (c) 2021 Joel Sing + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "bytestring.h" + +static int +asn1_get_identifier_cbs(CBS *cbs, int der_mode, uint8_t *out_class, + int *out_constructed, uint32_t *out_tag_number) +{ + uint8_t tag_class, tag_val; + int tag_constructed; + uint32_t tag_number; + + /* + * Decode ASN.1 identifier octets - see ITU-T X.690 section 8.1.2. + */ + + *out_class = 0; + *out_constructed = 0; + *out_tag_number = 0; + + if (!CBS_get_u8(cbs, &tag_val)) + return 0; + + /* + * ASN.1 tag class, encoding (primitive or constructed) and tag number + * are encoded in one or more identifier octets - the first octet + * contains the 2 bit tag class, the 1 bit encoding type and 5 bits + * of tag number. + * + * For tag numbers larger than 30 (0x1e) the 5 bit tag number in the + * first octet is set to all ones (0x1f) - the tag number is then + * encoded in subsequent octets - each of which have a one bit + * continuation flag and 7 bits of tag number in big-endian form. + * The encoding should not contain leading zeros but can for BER. + */ + tag_class = (tag_val >> 6) & 0x3; + tag_constructed = (tag_val >> 5) & 0x1; + tag_number = tag_val & 0x1f; + + /* Long form. */ + if (tag_number == 0x1f) { + tag_number = 0; + do { + if (!CBS_get_u8(cbs, &tag_val)) + return 0; + if (der_mode && tag_number == 0 && tag_val == 0x80) + return 0; + if (tag_number > (UINT32_MAX >> 7)) + return 0; + tag_number = tag_number << 7 | (tag_val & 0x7f); + } while ((tag_val & 0x80) != 0); + } + + *out_class = tag_class; + *out_constructed = tag_constructed; + *out_tag_number = tag_number; + + return 1; +} + +static int +asn1_get_length_cbs(CBS *cbs, int der_mode, int *out_indefinite, + uint32_t *out_length) +{ + uint8_t len_bytes; + uint32_t length; + uint8_t val; + + /* + * Decode ASN.1 length octets - see ITU-T X.690 section 8.1.3. + */ + + *out_length = 0; + *out_indefinite = 0; + + if (!CBS_get_u8(cbs, &val)) + return 0; + + /* + * Short form - length is encoded in the lower 7 bits of a single byte. + */ + if (val < 0x80) { + *out_length = val; + return 1; + } + + /* + * Indefinite length - content continues until an End of Content (EOC) + * marker is reached. Must be used with constructed encoding. + */ + if (val == 0x80) { + *out_indefinite = 1; + return 1; + } + + /* + * Long form - the lower 7 bits of the first byte specifies the number + * of bytes used to encode the length, the following bytes specify the + * length in big-endian form. The encoding should not contain leading + * zeros but can for BER. A length value of 0x7f is invalid. + */ + if ((len_bytes = val & 0x7f) == 0x7f) + return 0; + + length = 0; + + while (len_bytes-- > 0) { + if (!CBS_get_u8(cbs, &val)) + return 0; + if (der_mode && length == 0 && val == 0) + return 0; + if (length > (UINT32_MAX >> 8)) + return 0; + length = (length << 8) | val; + } + + *out_length = length; + + return 1; +} + +int +asn1_get_object_cbs(CBS *cbs, int der_mode, uint8_t *out_tag_class, + int *out_constructed, uint32_t *out_tag_number, int *out_indefinite, + uint32_t *out_length) +{ + int constructed, indefinite; + uint32_t tag_number, length; + uint8_t tag_class; + + *out_tag_class = 0; + *out_constructed = 0; + *out_tag_number = 0; + *out_indefinite = 0; + *out_length = 0; + + if (!asn1_get_identifier_cbs(cbs, der_mode, &tag_class, &constructed, + &tag_number)) + return 0; + if (!asn1_get_length_cbs(cbs, der_mode, &indefinite, &length)) + return 0; + + /* Indefinite length can only be used with constructed encoding. */ + if (indefinite && !constructed) + return 0; + + *out_tag_class = tag_class; + *out_constructed = constructed; + *out_tag_number = tag_number; + *out_indefinite = indefinite; + *out_length = length; + + return 1; +} diff --git a/lib/libcrypto/asn1/asn1_locl.h b/lib/libcrypto/asn1/asn1_locl.h index f7731ec5dd3..851c6e3400e 100644 --- a/lib/libcrypto/asn1/asn1_locl.h +++ b/lib/libcrypto/asn1/asn1_locl.h @@ -1,4 +1,4 @@ -/* $OpenBSD: asn1_locl.h,v 1.13 2021/12/14 17:35:21 jsing Exp $ */ +/* $OpenBSD: asn1_locl.h,v 1.14 2021/12/25 07:04:03 jsing Exp $ */ /* Written by Dr Stephen N Henson (steve@openssl.org) for the OpenSSL * project 2006. */ @@ -56,6 +56,8 @@ * */ +#include "bytestring.h" + __BEGIN_HIDDEN_DECLS /* Internal ASN1 structures and functions: not for application use */ @@ -155,6 +157,10 @@ struct x509_crl_method_st { int UTF8_getc(const unsigned char *str, int len, unsigned long *val); int UTF8_putc(unsigned char *str, int len, unsigned long value); +int asn1_get_object_cbs(CBS *cbs, int der_mode, uint8_t *out_class, + int *out_constructed, uint32_t *out_tag_number, int *out_indefinite, + uint32_t *out_length); + int asn1_tag2charwidth(int tag); __END_HIDDEN_DECLS diff --git a/lib/libcrypto/asn1/asn1_old_lib.c b/lib/libcrypto/asn1/asn1_old_lib.c index cc9a48f74ca..958c15b30cd 100644 --- a/lib/libcrypto/asn1/asn1_old_lib.c +++ b/lib/libcrypto/asn1/asn1_old_lib.c @@ -1,4 +1,4 @@ -/* $OpenBSD: asn1_old_lib.c,v 1.1 2021/12/15 18:12:10 jsing Exp $ */ +/* $OpenBSD: asn1_old_lib.c,v 1.2 2021/12/25 07:04:03 jsing Exp $ */ /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) * All rights reserved. * @@ -63,7 +63,8 @@ #include #include -static int asn1_get_length(const unsigned char **pp, int *inf, long *rl, int max); +#include "asn1_locl.h" + static void asn1_put_length(unsigned char **pp, int length); static int @@ -96,101 +97,51 @@ int ASN1_get_object(const unsigned char **pp, long *plength, int *ptag, int *pclass, long omax) { - int i, ret; - long l; - const unsigned char *p = *pp; - int tag, xclass, inf; - long max = omax; - - if (!max) - goto err; - ret = (*p & V_ASN1_CONSTRUCTED); - xclass = (*p & V_ASN1_PRIVATE); - i = *p & V_ASN1_PRIMITIVE_TAG; - if (i == V_ASN1_PRIMITIVE_TAG) { /* high-tag */ - p++; - if (--max == 0) - goto err; - l = 0; - while (*p & 0x80) { - l <<= 7L; - l |= *(p++) & 0x7f; - if (--max == 0) - goto err; - if (l > (INT_MAX >> 7L)) - goto err; - } - l <<= 7L; - l |= *(p++) & 0x7f; - tag = (int)l; - if (--max == 0) - goto err; - } else { - tag = i; - p++; - if (--max == 0) - goto err; + int constructed, indefinite; + uint32_t tag_number, length; + uint8_t tag_class; + CBS cbs; + int ret = 0; + + *pclass = 0; + *ptag = 0; + *plength = 0; + + CBS_init(&cbs, *pp, omax); + + if (!asn1_get_object_cbs(&cbs, 0, &tag_class, &constructed, &tag_number, + &indefinite, &length)) { + ASN1error(ASN1_R_HEADER_TOO_LONG); + return 0x80; } - *ptag = tag; - *pclass = xclass; - if (!asn1_get_length(&p, &inf, plength, (int)max)) - goto err; - if (inf && !(ret & V_ASN1_CONSTRUCTED)) - goto err; + if (tag_number > INT_MAX) { + ASN1error(ASN1_R_HEADER_TOO_LONG); + return 0x80; + } - if (*plength > (omax - (p - *pp))) { + /* + * API insanity ahead... in this case we add an error to the stack and + * signal an error by setting the 8th bit in the return value... but we + * still provide all of the decoded data. + */ + if (length > CBS_len(&cbs)) { ASN1error(ASN1_R_TOO_LONG); - /* Set this so that even if things are not long enough - * the values are set correctly */ - ret |= 0x80; + ret = 0x80; } - *pp = p; - return (ret | inf); -err: - ASN1error(ASN1_R_HEADER_TOO_LONG); - return (0x80); -} + *pclass = tag_class << 6; + *ptag = tag_number; + *plength = length; -static int -asn1_get_length(const unsigned char **pp, int *inf, long *rl, int max) -{ - const unsigned char *p = *pp; - unsigned long ret = 0; - unsigned int i; + *pp = CBS_data(&cbs); - if (max-- < 1) - return (0); - if (*p == 0x80) { - *inf = 1; - ret = 0; - p++; - } else { - *inf = 0; - i = *p & 0x7f; - if (*(p++) & 0x80) { - if (max < (int)i) - return (0); - /* skip leading zeroes */ - while (i && *p == 0) { - p++; - i--; - } - if (i > sizeof(long)) - return 0; - while (i-- > 0) { - ret <<= 8L; - ret |= *(p++); - } - } else - ret = i; - } - if (ret > LONG_MAX) - return 0; - *pp = p; - *rl = (long)ret; - return (1); + if (constructed) + ret |= 1 << 5; + if (indefinite) + ret |= 1; + + return ret; } /* class 0 is constructed -- 2.20.1