Add helpers for working with relative OIDs.
Adds:
CBB_add_asn1_oid_component
As well as adding the following functions which are relative-OID
versions of the existing OID functions:
CBB_add_asn1_relative_oid_from_text
CBS_is_valid_asn1_relative_oid
CBS_asn1_relative_oid_to_text
Change-Id: I72509a155eb06cfa39f610099aa82a19415533f8
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/84207
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: Matt Mueller <mattm@google.com>
diff --git a/crypto/bytestring/bytestring_test.cc b/crypto/bytestring/bytestring_test.cc
index 9f6a2c4..7005970 100644
--- a/crypto/bytestring/bytestring_test.cc
+++ b/crypto/bytestring/bytestring_test.cc
@@ -1483,6 +1483,126 @@
}
}
+TEST(CBBTest, AddRelativeOIDFromText) {
+ const struct {
+ const char *text;
+ std::vector<uint8_t> der;
+ } kValidOIDs[] = {
+ // Some valid values.
+ {"0", {0x00}},
+ {"128", {0x81, 0x00}},
+ {"128.129", {0x81, 0x00, 0x81, 0x01}},
+ {"0.2.3.4", {0x0, 0x2, 0x3, 0x4}},
+ {"1.2.3.4", {0x1, 0x2, 0x3, 0x4}},
+ {"127.2.3.4", {0x7f, 0x2, 0x3, 0x4}},
+ {"1.2.840.113554.4.1.72585",
+ {0x1, 0x2, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09}},
+ // Edge cases near an overflow.
+ {"1.2.18446744073709551615",
+ {0x1, 0x2, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}},
+ };
+
+ const char *kInvalidTexts[] = {
+ // The empty string is not a relative OID.
+ "",
+ // No empty components.
+ ".",
+ ".1.2.3.4.5",
+ "1..2.3.4.5",
+ "1.2.3.4.5.",
+ // No extra leading zeros.
+ "00.1.2.3.4",
+ "01.1.2.3.4",
+ // Overflow
+ "1.2.18446744073709551616",
+ };
+
+ const struct {
+ std::vector<uint8_t> der;
+ // If true, |der| is valid but has a component that exceeds 2^64-1.
+ bool overflow;
+ } kInvalidDER[] = {
+ // The empty string is not a relative OID.
+ {{}, false},
+ // Non-minimal representation.
+ {{0x80, 0x01}, false},
+ // Unterminated integer.
+ {{0x83}, false},
+ // Overflow. This is the DER representation of
+ // 840.113554.4.1.72585.18446744073709551616. (The final value is
+ // 2^64.)
+ {{0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09,
+ 0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00},
+ true},
+ };
+
+ for (const auto &t : kValidOIDs) {
+ SCOPED_TRACE(t.text);
+
+ bssl::ScopedCBB cbb;
+ ASSERT_TRUE(CBB_init(cbb.get(), 0));
+ ASSERT_TRUE(CBB_add_asn1_relative_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_relative_oid_to_text(&cbs));
+ ASSERT_TRUE(text.get());
+ EXPECT_STREQ(t.text, text.get());
+
+ EXPECT_TRUE(CBS_is_valid_asn1_relative_oid(&cbs));
+ }
+
+ for (const char *t : kInvalidTexts) {
+ SCOPED_TRACE(t);
+ bssl::ScopedCBB cbb;
+ ASSERT_TRUE(CBB_init(cbb.get(), 0));
+ EXPECT_FALSE(CBB_add_asn1_relative_oid_from_text(cbb.get(), t, strlen(t)));
+ }
+
+ for (const auto &t : kInvalidDER) {
+ SCOPED_TRACE(Bytes(t.der));
+ CBS cbs;
+ CBS_init(&cbs, t.der.data(), t.der.size());
+ bssl::UniquePtr<char> text(CBS_asn1_relative_oid_to_text(&cbs));
+ EXPECT_FALSE(text);
+ EXPECT_EQ(t.overflow ? 1 : 0, CBS_is_valid_asn1_relative_oid(&cbs));
+ }
+}
+
+TEST(CBBTest, AddOIDComponent) {
+ const struct {
+ uint64_t component;
+ std::vector<uint8_t> der;
+ } kValidOIDs[] = {
+ // Some valid values.
+ {0, {0x00}},
+ {127, {0x7f}},
+ {128, {0x81, 0x00}},
+ {129, {0x81, 0x01}},
+ {113554, {0x86, 0xf7, 0x12}},
+ // Edge cases near an overflow.
+ {18446744073709551615u,
+ {0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}},
+ };
+ for (const auto &t : kValidOIDs) {
+ SCOPED_TRACE(t.component);
+ bssl::ScopedCBB cbb;
+ ASSERT_TRUE(CBB_init(cbb.get(), 0));
+ ASSERT_TRUE(CBB_add_asn1_oid_component(cbb.get(), t.component));
+ 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));
+ }
+}
+
TEST(CBBTest, FlushASN1SetOf) {
const struct {
std::vector<uint8_t> in, out;
diff --git a/crypto/bytestring/cbb.cc b/crypto/bytestring/cbb.cc
index 2d004bf..c8f5a51 100644
--- a/crypto/bytestring/cbb.cc
+++ b/crypto/bytestring/cbb.cc
@@ -638,6 +638,38 @@
return 1;
}
+int CBB_add_asn1_relative_oid_from_text(CBB *cbb, const char *text,
+ size_t len) {
+ if (!CBB_flush(cbb)) {
+ return 0;
+ }
+
+ // Relative OIDs must have at least one component.
+ if (!len) {
+ return 0;
+ }
+
+ CBS cbs;
+ CBS_init(&cbs, reinterpret_cast<const uint8_t *>(text), len);
+
+ while (CBS_len(&cbs) > 0) {
+ uint64_t a;
+ if (!parse_dotted_decimal(&cbs, &a) || !add_base128_integer(cbb, a)) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+int CBB_add_asn1_oid_component(CBB *cbb, uint64_t value) {
+ if (!CBB_flush(cbb)) {
+ return 0;
+ }
+
+ return add_base128_integer(cbb, value);
+}
+
static int compare_set_of_element(const void *a_ptr, const void *b_ptr) {
// See X.690, section 11.6 for the ordering. They are sorted in ascending
// order by their DER encoding.
diff --git a/crypto/bytestring/cbs.cc b/crypto/bytestring/cbs.cc
index 55bb149..671914d 100644
--- a/crypto/bytestring/cbs.cc
+++ b/crypto/bytestring/cbs.cc
@@ -754,6 +754,39 @@
return nullptr;
}
+int CBS_is_valid_asn1_relative_oid(const CBS *cbs) {
+ return CBS_is_valid_asn1_oid(cbs);
+}
+
+char *CBS_asn1_relative_oid_to_text(const CBS *cbs) {
+ CBS copy = *cbs;
+ bssl::ScopedCBB cbb;
+ if (!CBB_init(cbb.get(), 32)) {
+ return nullptr;
+ }
+
+ // Relative OIDs must have at least one component.
+ uint64_t v;
+ if (!parse_base128_integer(©, &v) || !add_decimal(cbb.get(), v)) {
+ return nullptr;
+ }
+
+ while (CBS_len(©) != 0) {
+ if (!parse_base128_integer(©, &v) || !CBB_add_u8(cbb.get(), '.') ||
+ !add_decimal(cbb.get(), v)) {
+ return nullptr;
+ }
+ }
+
+ uint8_t *txt;
+ size_t txt_len;
+ if (!CBB_add_u8(cbb.get(), '\0') || !CBB_finish(cbb.get(), &txt, &txt_len)) {
+ return nullptr;
+ }
+
+ return reinterpret_cast<char *>(txt);
+}
+
static int cbs_get_two_digits(CBS *cbs, int *out) {
uint8_t first_digit, second_digit;
if (!CBS_get_u8(cbs, &first_digit)) {
diff --git a/include/openssl/bytestring.h b/include/openssl/bytestring.h
index 6dc5c24..25877c2 100644
--- a/include/openssl/bytestring.h
+++ b/include/openssl/bytestring.h
@@ -389,6 +389,22 @@
// OID components are too large.
OPENSSL_EXPORT char *CBS_asn1_oid_to_text(const CBS *cbs);
+// CBS_is_valid_asn1_relative_oid returns one if |cbs| is a valid DER-encoded
+// ASN.1 RELATIVE-OID contents (not including the element framing) and zero
+// otherwise. This function tolerates arbitrarily large OID components.
+//
+// (This is actually the same as |CBS_is_valid_asn1_oid|, but is also exposed
+// under the relative_oid name for API symmetry.)
+OPENSSL_EXPORT int CBS_is_valid_asn1_relative_oid(const CBS *cbs);
+
+// CBS_asn1_relative_oid_to_text interprets |cbs| as DER-encoded ASN.1
+// RELATIVE-OID contents (not including the element framing) and returns the
+// ASCII representation (e.g., "32473.1") in a newly-allocated string, or NULL
+// on failure. The caller must release the result with |OPENSSL_free|.
+//
+// This function may fail if |cbs| is an invalid RELATIVE-OID, or if any
+// OID components are too large.
+OPENSSL_EXPORT char *CBS_asn1_relative_oid_to_text(const CBS *cbs);
// CBS_parse_generalized_time returns one if |cbs| is a valid DER-encoded, ASN.1
// GeneralizedTime body within the limitations imposed by RFC 5280, or zero
@@ -653,6 +669,22 @@
OPENSSL_EXPORT int CBB_add_asn1_oid_from_text(CBB *cbb, const char *text,
size_t len);
+// CBB_add_asn1_relative_oid_from_text decodes |len| bytes from |text| as an
+// ASCII RELATIVE-OID representation, e.g. "32473.1", and writes the
+// DER-encoded contents to |cbb|. It returns one on success and zero on malloc
+// failure or if |text| was invalid. It does not include any framing, only the
+// element's contents.
+//
+// This function considers OID strings with components which do not fit in a
+// |uint64_t| to be invalid.
+OPENSSL_EXPORT int CBB_add_asn1_relative_oid_from_text(CBB *cbb,
+ const char *text,
+ size_t len);
+
+// CBB_add_asn1_oid_component appends a single OID component to |cbb|.
+// It returns one on success and zero on error.
+OPENSSL_EXPORT int CBB_add_asn1_oid_component(CBB *cbb, uint64_t value);
+
// CBB_flush_asn1_set_of calls |CBB_flush| on |cbb| and then reorders the
// contents for a DER-encoded ASN.1 SET OF type. It returns one on success and
// zero on failure. DER canonicalizes SET OF contents by sorting