Rewrite ASN1_PRINTABLE_type and add tests.
The old loop read one byte past the length. It also stopped the loop
too early on interior NUL. See also upstream's
https://github.com/openssl/openssl/pull/16433, though I've opted to
rewrite the function entirely rather than use their fix.
Also deduplicate the PrintableString check.
Change-Id: Ia8bd282047c2a2ed1d5e71a68a3947c7c108df95
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/49066
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/asn1/a_mbstr.c b/crypto/asn1/a_mbstr.c
index b004665..5853b72 100644
--- a/crypto/asn1/a_mbstr.c
+++ b/crypto/asn1/a_mbstr.c
@@ -66,8 +66,6 @@
#include "internal.h"
#include "../bytestring/internal.h"
-static int is_printable(uint32_t value);
-
/*
* These functions take a string in UTF8, ASCII or multibyte form and a mask
* of permissible ASN1 string types. It then works out the minimal type
@@ -153,7 +151,7 @@
}
/* Update which output formats are still possible. */
- if ((mask & B_ASN1_PRINTABLESTRING) && !is_printable(c)) {
+ if ((mask & B_ASN1_PRINTABLESTRING) && !asn1_is_printable(c)) {
mask &= ~B_ASN1_PRINTABLESTRING;
}
if ((mask & B_ASN1_IA5STRING) && (c > 127)) {
@@ -285,8 +283,7 @@
return -1;
}
-/* Return 1 if the character is permitted in a PrintableString */
-static int is_printable(uint32_t value)
+int asn1_is_printable(uint32_t value)
{
if (value > 0x7f) {
return 0;
diff --git a/crypto/asn1/a_print.c b/crypto/asn1/a_print.c
index 2104521..c7efede 100644
--- a/crypto/asn1/a_print.c
+++ b/crypto/asn1/a_print.c
@@ -56,38 +56,28 @@
#include <openssl/asn1.h>
-#include <openssl/err.h>
-#include <openssl/mem.h>
+#include <string.h>
+
+#include "internal.h"
+
int ASN1_PRINTABLE_type(const unsigned char *s, int len)
{
- int c;
- int ia5 = 0;
- int t61 = 0;
-
- if (len <= 0)
- len = -1;
- if (s == NULL)
- return (V_ASN1_PRINTABLESTRING);
-
- while ((*s) && (len-- != 0)) {
- c = *(s++);
- if (!(((c >= 'a') && (c <= 'z')) ||
- ((c >= 'A') && (c <= 'Z')) ||
- (c == ' ') ||
- ((c >= '0') && (c <= '9')) ||
- (c == ' ') || (c == '\'') ||
- (c == '(') || (c == ')') ||
- (c == '+') || (c == ',') ||
- (c == '-') || (c == '.') ||
- (c == '/') || (c == ':') || (c == '=') || (c == '?')))
- ia5 = 1;
- if (c & 0x80)
- t61 = 1;
+ if (len < 0) {
+ len = strlen((const char *)s);
}
- if (t61)
- return (V_ASN1_T61STRING);
- if (ia5)
- return (V_ASN1_IA5STRING);
- return (V_ASN1_PRINTABLESTRING);
+
+ int printable = 1;
+ for (int i = 0; i < len; i++) {
+ unsigned char c = s[i];
+ if (c & 0x80) {
+ /* No need to continue iterating. */
+ return V_ASN1_T61STRING;
+ }
+ if (!asn1_is_printable(c)) {
+ printable = 0;
+ }
+ }
+
+ return printable ? V_ASN1_PRINTABLESTRING : V_ASN1_IA5STRING;
}
diff --git a/crypto/asn1/asn1_test.cc b/crypto/asn1/asn1_test.cc
index 3e835db..28a5998 100644
--- a/crypto/asn1/asn1_test.cc
+++ b/crypto/asn1/asn1_test.cc
@@ -1046,6 +1046,27 @@
TestSerialize(str.get(), i2d_ASN1_PRINTABLE, kMinusOne);
}
+TEST(ASN1Test, PrintableType) {
+ const struct {
+ std::vector<uint8_t> in;
+ int result;
+ } kTests[] = {
+ {{}, V_ASN1_PRINTABLESTRING},
+ {{'a', 'A', '0', '\'', '(', ')', '+', ',', '-', '.', '/', ':', '=', '?'},
+ V_ASN1_PRINTABLESTRING},
+ {{'*'}, V_ASN1_IA5STRING},
+ {{'\0'}, V_ASN1_IA5STRING},
+ {{'\0', 'a'}, V_ASN1_IA5STRING},
+ {{0, 1, 2, 3, 125, 126, 127}, V_ASN1_IA5STRING},
+ {{0, 1, 2, 3, 125, 126, 127, 128}, V_ASN1_T61STRING},
+ {{128, 0, 1, 2, 3, 125, 126, 127}, V_ASN1_T61STRING},
+ };
+ for (const auto &t : kTests) {
+ SCOPED_TRACE(Bytes(t.in));
+ EXPECT_EQ(t.result, ASN1_PRINTABLE_type(t.in.data(), t.in.size()));
+ }
+}
+
// The ASN.1 macros do not work on Windows shared library builds, where usage of
// |OPENSSL_EXPORT| is a bit stricter.
#if !defined(OPENSSL_WINDOWS) || !defined(BORINGSSL_SHARED_LIBRARY)
diff --git a/crypto/asn1/internal.h b/crypto/asn1/internal.h
index 4e42c70..f40aa86 100644
--- a/crypto/asn1/internal.h
+++ b/crypto/asn1/internal.h
@@ -150,6 +150,10 @@
* a pointer. */
const void *asn1_type_value_as_pointer(const ASN1_TYPE *a);
+/* asn1_is_printable returns one if |value| is a valid Unicode codepoint for an
+ * ASN.1 PrintableString, and zero otherwise. */
+int asn1_is_printable(uint32_t value);
+
#if defined(__cplusplus)
} /* extern C */
diff --git a/include/openssl/asn1.h b/include/openssl/asn1.h
index 45a9794..497ad7c 100644
--- a/include/openssl/asn1.h
+++ b/include/openssl/asn1.h
@@ -1118,9 +1118,11 @@
int len, const char *sn,
const char *ln);
-// General
-// given a string, return the correct type, max is the maximum length
-OPENSSL_EXPORT int ASN1_PRINTABLE_type(const unsigned char *s, int max);
+// ASN1_PRINTABLE_type interprets |len| bytes from |s| as a Latin-1 string. It
+// returns the first of |V_ASN1_PRINTABLESTRING|, |V_ASN1_IA5STRING|, or
+// |V_ASN1_T61STRING| that can represent every character. If |len| is negative,
+// |strlen(s)| is used instead.
+OPENSSL_EXPORT int ASN1_PRINTABLE_type(const unsigned char *s, int len);
OPENSSL_EXPORT unsigned long ASN1_tag2bit(int tag);