Also add a decoupled OBJ_obj2txt.
We need it in both directions. Also I missed that in OBJ_obj2txt we
allowed uint64_t components, but in my new OBJ_txt2obj we only allowed
uint32_t. For consistency, upgrade that to uint64_t.
Bug: chromium:706445
Change-Id: I38cfeea8ff64b9acf7998e552727c6c3b2cc600f
Reviewed-on: https://boringssl-review.googlesource.com/23544
Commit-Queue: Steven Valdez <svaldez@google.com>
Reviewed-by: Steven Valdez <svaldez@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/crypto/bytestring/bytestring_test.cc b/crypto/bytestring/bytestring_test.cc
index 6ce7035..1969e73 100644
--- a/crypto/bytestring/bytestring_test.cc
+++ b/crypto/bytestring/bytestring_test.cc
@@ -889,54 +889,95 @@
TEST(CBBTest, AddOIDFromText) {
const struct {
- const char *in;
- bool ok;
- std::vector<uint8_t> out;
- } kTests[] = {
+ const char *text;
+ std::vector<uint8_t> der;
+ } kValidOIDs[] = {
// Some valid values.
- {"1.2.3.4", true, {0x2a, 0x3, 0x4}},
+ {"0.0", {0x00}},
+ {"0.2.3.4", {0x2, 0x3, 0x4}},
+ {"1.2.3.4", {0x2a, 0x3, 0x4}},
+ {"2.2.3.4", {0x52, 0x3, 0x4}},
{"1.2.840.113554.4.1.72585",
- true,
{0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09}},
// Test edge cases around the first component.
- {"0.39", true, {0x27}},
- {"0.40", false, {}},
- {"1.0", true, {0x28}},
- {"1.39", true, {0x4f}},
- {"1.40", false, {}},
- {"2.0", true, {0x50}},
- {"2.1", true, {0x51}},
- {"2.40", true, {0x78}},
- // The empty string is not an OID.
- {"", false, {}},
- // No empty components.
- {".1.2.3.4.5", false, {}},
- {"1..2.3.4.5", false, {}},
- {"1.2.3.4.5.", false, {}},
- // There must be at least two components.
- {"1", false, {}},
- // No extra leading zeros.
- {"00.1.2.3.4", false, {}},
- {"01.1.2.3.4", false, {}},
- // Check for overflow.
- {"1.2.4294967295", true, {0x2a, 0x8f, 0xff, 0xff, 0xff, 0x7f}},
- {"1.2.4294967296", false, {}},
- // 40*A + B overflows.
- {"2.4294967215", true, {0x8f, 0xff, 0xff, 0xff, 0x7f}},
- {"2.4294967216", false, {}},
+ {"0.39", {0x27}},
+ {"1.0", {0x28}},
+ {"1.39", {0x4f}},
+ {"2.0", {0x50}},
+ {"2.1", {0x51}},
+ {"2.40", {0x78}},
+ // Edge cases near an overflow.
+ {"1.2.18446744073709551615",
+ {0x2a, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}},
+ {"2.18446744073709551535",
+ {0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}},
};
- for (const auto &t : kTests) {
- SCOPED_TRACE(t.in);
+
+ const char *kInvalidTexts[] = {
+ // Invalid second component.
+ "0.40",
+ "1.40",
+ // Invalid first component.
+ "3.1",
+ // The empty string is not an OID.
+ "",
+ // No empty components.
+ ".1.2.3.4.5",
+ "1..2.3.4.5",
+ "1.2.3.4.5.",
+ // There must be at least two components.
+ "1",
+ // No extra leading zeros.
+ "00.1.2.3.4",
+ "01.1.2.3.4",
+ // Overflow for both components or 40*A + B.
+ "1.2.18446744073709551616",
+ "2.18446744073709551536",
+ };
+
+ const std::vector<uint8_t> kInvalidDER[] = {
+ // The empty string is not an OID.
+ {},
+ // Non-minimal representation.
+ {0x80, 0x01},
+ // Overflow. This is the DER representation of
+ // 1.2.840.113554.4.1.72585.18446744073709551616. (The final value is
+ // 2^64.)
+ {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09,
+ 0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00},
+ };
+
+ for (const auto &t : kValidOIDs) {
+ SCOPED_TRACE(t.text);
+
bssl::ScopedCBB cbb;
ASSERT_TRUE(CBB_init(cbb.get(), 0));
- int ok = CBB_add_asn1_oid_from_text(cbb.get(), t.in, strlen(t.in));
- EXPECT_EQ(t.ok, static_cast<bool>(ok));
- if (ok) {
- uint8_t *out;
- size_t len;
- ASSERT_TRUE(CBB_finish(cbb.get(), &out, &len));
- bssl::UniquePtr<uint8_t> free_out(out);
- EXPECT_EQ(Bytes(t.out), Bytes(out, len));
- }
+ ASSERT_TRUE(CBB_add_asn1_oid_from_text(cbb.get(), t.text, strlen(t.text)));
+ uint8_t *out;
+ size_t len;
+ ASSERT_TRUE(CBB_finish(cbb.get(), &out, &len));
+ bssl::UniquePtr<uint8_t> free_out(out);
+ EXPECT_EQ(Bytes(t.der), Bytes(out, len));
+
+ CBS cbs;
+ CBS_init(&cbs, t.der.data(), t.der.size());
+ bssl::UniquePtr<char> text(CBS_asn1_oid_to_text(&cbs));
+ ASSERT_TRUE(text.get());
+ EXPECT_STREQ(t.text, text.get());
+ }
+
+ for (const char *t : kInvalidTexts) {
+ SCOPED_TRACE(t);
+ bssl::ScopedCBB cbb;
+ ASSERT_TRUE(CBB_init(cbb.get(), 0));
+ EXPECT_FALSE(CBB_add_asn1_oid_from_text(cbb.get(), t, strlen(t)));
+ }
+
+ for (const auto &t : kInvalidDER) {
+ SCOPED_TRACE(Bytes(t));
+ CBS cbs;
+ CBS_init(&cbs, t.data(), t.size());
+ bssl::UniquePtr<char> text(CBS_asn1_oid_to_text(&cbs));
+ EXPECT_FALSE(text);
}
}
diff --git a/crypto/bytestring/cbb.c b/crypto/bytestring/cbb.c
index 5a1c45b..b12a66c 100644
--- a/crypto/bytestring/cbb.c
+++ b/crypto/bytestring/cbb.c
@@ -332,9 +332,9 @@
// add_base128_integer encodes |v| as a big-endian base-128 integer where the
// high bit of each byte indicates where there is more data. This is the
// encoding used in DER for both high tag number form and OID components.
-static int add_base128_integer(CBB *cbb, uint32_t v) {
+static int add_base128_integer(CBB *cbb, uint64_t v) {
unsigned len_len = 0;
- unsigned copy = v;
+ uint64_t copy = v;
while (copy > 0) {
len_len++;
copy >>= 7;
@@ -508,7 +508,7 @@
// an OID literal, e.g., "1.2.840.113554.4.1.72585". It consumes both the
// component and the dot, so |cbs| may be passed into the function again for the
// next value.
-static int parse_dotted_decimal(CBS *cbs, uint32_t *out) {
+static int parse_dotted_decimal(CBS *cbs, uint64_t *out) {
*out = 0;
int seen_digit = 0;
for (;;) {
@@ -524,8 +524,8 @@
// Forbid stray leading zeros.
(seen_digit && *out == 0) ||
// Check for overflow.
- *out > UINT32_MAX / 10 ||
- *out * 10 > UINT32_MAX - (u - '0')) {
+ *out > UINT64_MAX / 10 ||
+ *out * 10 > UINT64_MAX - (u - '0')) {
return 0;
}
*out = *out * 10 + (u - '0');
@@ -544,7 +544,7 @@
CBS_init(&cbs, (const uint8_t *)text, len);
// OIDs must have at least two components.
- uint32_t a, b;
+ uint64_t a, b;
if (!parse_dotted_decimal(&cbs, &a) ||
!parse_dotted_decimal(&cbs, &b)) {
return 0;
@@ -554,8 +554,8 @@
// 0, 1, or 2 and that, when it is 0 or 1, |b| is at most 39.
if (a > 2 ||
(a < 2 && b > 39) ||
- b > UINT32_MAX - 80 ||
- !add_base128_integer(cbb, 40 * a + b)) {
+ b > UINT64_MAX - 80 ||
+ !add_base128_integer(cbb, 40u * a + b)) {
return 0;
}
diff --git a/crypto/bytestring/cbs.c b/crypto/bytestring/cbs.c
index d96371c..e456330 100644
--- a/crypto/bytestring/cbs.c
+++ b/crypto/bytestring/cbs.c
@@ -12,11 +12,16 @@
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+#if !defined(__STDC_FORMAT_MACROS)
+#define __STDC_FORMAT_MACROS
+#endif
+
#include <openssl/buf.h>
#include <openssl/mem.h>
#include <openssl/bytestring.h>
#include <assert.h>
+#include <inttypes.h>
#include <string.h>
#include "internal.h"
@@ -175,6 +180,33 @@
return cbs_get_length_prefixed(cbs, out, 3);
}
+// parse_base128_integer reads a big-endian base-128 integer from |cbs| and sets
+// |*out| to the result. This is the encoding used in DER for both high tag
+// number form and OID components.
+static int parse_base128_integer(CBS *cbs, uint64_t *out) {
+ uint64_t v = 0;
+ uint8_t b;
+ do {
+ if (!CBS_get_u8(cbs, &b)) {
+ return 0;
+ }
+ if ((v >> (64 - 7)) != 0) {
+ // The value is too large.
+ return 0;
+ }
+ if (v == 0 && b == 0x80) {
+ // The value must be minimally encoded.
+ return 0;
+ }
+ v = (v << 7) | (b & 0x7f);
+
+ // Values end at an octet with the high bit cleared.
+ } while (b & 0x80);
+
+ *out = v;
+ return 1;
+}
+
static int parse_asn1_tag(CBS *cbs, unsigned *out) {
uint8_t tag_byte;
if (!CBS_get_u8(cbs, &tag_byte)) {
@@ -191,27 +223,15 @@
unsigned tag = ((unsigned)tag_byte & 0xe0) << CBS_ASN1_TAG_SHIFT;
unsigned tag_number = tag_byte & 0x1f;
if (tag_number == 0x1f) {
- tag_number = 0;
- for (;;) {
- if (!CBS_get_u8(cbs, &tag_byte) ||
- ((tag_number << 7) >> 7) != tag_number) {
- return 0;
- }
- tag_number = (tag_number << 7) | (tag_byte & 0x7f);
- // The tag must be represented in the minimal number of bytes.
- if (tag_number == 0) {
- return 0;
- }
- if ((tag_byte & 0x80) == 0) {
- break;
- }
- }
- if (// Check the tag number is within our supported bounds.
- tag_number > CBS_ASN1_TAG_NUMBER_MASK ||
+ uint64_t v;
+ if (!parse_base128_integer(cbs, &v) ||
+ // Check the tag number is within our supported bounds.
+ v > CBS_ASN1_TAG_NUMBER_MASK ||
// Small tag numbers should have used low tag number form.
- tag_number < 0x1f) {
+ v < 0x1f) {
return 0;
}
+ tag_number = (unsigned)v;
}
tag |= tag_number;
@@ -527,3 +547,55 @@
return byte_num < CBS_len(cbs) &&
(CBS_data(cbs)[byte_num] & (1 << bit_num)) != 0;
}
+
+static int add_decimal(CBB *out, uint64_t v) {
+ char buf[DECIMAL_SIZE(uint64_t) + 1];
+ BIO_snprintf(buf, sizeof(buf), "%" PRIu64, v);
+ return CBB_add_bytes(out, (const uint8_t *)buf, strlen(buf));
+}
+
+char *CBS_asn1_oid_to_text(const CBS *cbs) {
+ CBB cbb;
+ if (!CBB_init(&cbb, 32)) {
+ goto err;
+ }
+
+ CBS copy = *cbs;
+ // The first component is 40 * value1 + value2, where value1 is 0, 1, or 2.
+ uint64_t v;
+ if (!parse_base128_integer(©, &v)) {
+ goto err;
+ }
+
+ if (v >= 80) {
+ if (!CBB_add_bytes(&cbb, (const uint8_t *)"2.", 2) ||
+ !add_decimal(&cbb, v - 80)) {
+ goto err;
+ }
+ } else if (!add_decimal(&cbb, v / 40) ||
+ !CBB_add_u8(&cbb, '.') ||
+ !add_decimal(&cbb, v % 40)) {
+ goto err;
+ }
+
+ while (CBS_len(©) != 0) {
+ if (!parse_base128_integer(©, &v) ||
+ !CBB_add_u8(&cbb, '.') ||
+ !add_decimal(&cbb, v)) {
+ goto err;
+ }
+ }
+
+ uint8_t *txt;
+ size_t txt_len;
+ if (!CBB_add_u8(&cbb, '\0') ||
+ !CBB_finish(&cbb, &txt, &txt_len)) {
+ goto err;
+ }
+
+ return (char *)txt;
+
+err:
+ CBB_cleanup(&cbb);
+ return NULL;
+}
diff --git a/crypto/obj/obj.c b/crypto/obj/obj.c
index 8e1e6b1..a34d6dc 100644
--- a/crypto/obj/obj.c
+++ b/crypto/obj/obj.c
@@ -434,36 +434,6 @@
return (int)ret;
}
-static int parse_oid_component(CBS *cbs, uint64_t *out) {
- uint64_t v = 0;
- uint8_t b;
- do {
- if (!CBS_get_u8(cbs, &b)) {
- return 0;
- }
- if ((v >> (64 - 7)) != 0) {
- // The component is too large.
- return 0;
- }
- if (v == 0 && b == 0x80) {
- // The component must be minimally encoded.
- return 0;
- }
- v = (v << 7) | (b & 0x7f);
-
- // Components end at an octet with the high bit cleared.
- } while (b & 0x80);
-
- *out = v;
- return 1;
-}
-
-static int add_decimal(CBB *out, uint64_t v) {
- char buf[DECIMAL_SIZE(uint64_t) + 1];
- BIO_snprintf(buf, sizeof(buf), "%" PRIu64, v);
- return CBB_add_bytes(out, (const uint8_t *)buf, strlen(buf));
-}
-
int OBJ_obj2txt(char *out, int out_len, const ASN1_OBJECT *obj,
int always_return_oid) {
// Python depends on the empty OID successfully encoding as the empty
@@ -485,56 +455,19 @@
}
}
- CBB cbb;
- if (!CBB_init(&cbb, 32)) {
- goto err;
- }
-
CBS cbs;
CBS_init(&cbs, obj->data, obj->length);
-
- // The first component is 40 * value1 + value2, where value1 is 0, 1, or 2.
- uint64_t v;
- if (!parse_oid_component(&cbs, &v)) {
- goto err;
- }
-
- if (v >= 80) {
- if (!CBB_add_bytes(&cbb, (const uint8_t *)"2.", 2) ||
- !add_decimal(&cbb, v - 80)) {
- goto err;
+ char *txt = CBS_asn1_oid_to_text(&cbs);
+ if (txt == NULL) {
+ if (out_len > 0) {
+ out[0] = '\0';
}
- } else if (!add_decimal(&cbb, v / 40) ||
- !CBB_add_u8(&cbb, '.') ||
- !add_decimal(&cbb, v % 40)) {
- goto err;
+ return -1;
}
- while (CBS_len(&cbs) != 0) {
- if (!parse_oid_component(&cbs, &v) ||
- !CBB_add_u8(&cbb, '.') ||
- !add_decimal(&cbb, v)) {
- goto err;
- }
- }
-
- uint8_t *txt;
- size_t txt_len;
- if (!CBB_add_u8(&cbb, '\0') ||
- !CBB_finish(&cbb, &txt, &txt_len)) {
- goto err;
- }
-
- int ret = strlcpy_int(out, (const char *)txt, out_len);
+ int ret = strlcpy_int(out, txt, out_len);
OPENSSL_free(txt);
return ret;
-
-err:
- CBB_cleanup(&cbb);
- if (out_len > 0) {
- out[0] = '\0';
- }
- return -1;
}
static uint32_t hash_nid(const ASN1_OBJECT *obj) {
diff --git a/include/openssl/bytestring.h b/include/openssl/bytestring.h
index abf8885..43349e9 100644
--- a/include/openssl/bytestring.h
+++ b/include/openssl/bytestring.h
@@ -279,6 +279,13 @@
// is indexed starting from zero.
OPENSSL_EXPORT int CBS_asn1_bitstring_has_bit(const CBS *cbs, unsigned bit);
+// CBS_asn1_oid_to_text interprets |cbs| as DER-encoded ASN.1 OBJECT IDENTIFIER
+// contents (not including the element framing) and returns the ASCII
+// representation (e.g., "1.2.840.113554.4.1.72585") in a newly-allocated
+// string, or NULL on failure. The caller must release the result with
+// |OPENSSL_free|.
+OPENSSL_EXPORT char *CBS_asn1_oid_to_text(const CBS *cbs);
+
// CRYPTO ByteBuilder.
//
@@ -450,7 +457,7 @@
// the element's contents.
//
// This function considers OID strings with components which do not fit in a
-// |uint32_t| to be invalid.
+// |uint64_t| to be invalid.
OPENSSL_EXPORT int CBB_add_asn1_oid_from_text(CBB *cbb, const char *text,
size_t len);