Rewrite ASN.1 identifier/length parsing in CBS.
authorjsing <jsing@openbsd.org>
Sat, 25 Dec 2021 07:04:03 +0000 (07:04 +0000)
committerjsing <jsing@openbsd.org>
Sat, 25 Dec 2021 07:04:03 +0000 (07:04 +0000)
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
lib/libcrypto/asn1/asn1_lib.c [new file with mode: 0644]
lib/libcrypto/asn1/asn1_locl.h
lib/libcrypto/asn1/asn1_old_lib.c

index 7279d5a..cc53d8e 100644 (file)
@@ -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 (file)
index 0000000..542a72f
--- /dev/null
@@ -0,0 +1,171 @@
+/* $OpenBSD: asn1_lib.c,v 1.51 2021/12/25 07:04:03 jsing Exp $ */
+/*
+ * Copyright (c) 2021 Joel Sing <jsing@openbsd.org>
+ *
+ * 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 <limits.h>
+
+#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;
+}
index f7731ec..851c6e3 100644 (file)
@@ -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
index cc9a48f..958c15b 100644 (file)
@@ -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 <openssl/asn1.h>
 #include <openssl/err.h>
 
-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