Fix X509_ATTRIBUTE_set1_data with negative attributes
One of the WARNINGs in this function is unambiguously a bug. Just fix
it. In doing so, add a helper for the ASN1_STRING -> ASN1_TYPE
conversion and make this function easier to follow.
Also document the attrtype == 0 case. I missed that one when enumerating
this overcomplicated calling convention. Move that check earlier so we
don't try to process the input. It's ignored anyway.
Change-Id: Ia6e2dcb7c69488b6e2e58a68c3b701040ed3d4ef
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/64827
Reviewed-by: Bob Beck <bbe@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/asn1/a_strex.c b/crypto/asn1/a_strex.c
index 7e9afad..516f7df 100644
--- a/crypto/asn1/a_strex.c
+++ b/crypto/asn1/a_strex.c
@@ -68,6 +68,7 @@
#include <openssl/mem.h>
#include "../bytestring/internal.h"
+#include "../internal.h"
#include "internal.h"
@@ -238,22 +239,8 @@
// Placing the ASN1_STRING in a temporary ASN1_TYPE allows the DER encoding
// to readily obtained.
ASN1_TYPE t;
- t.type = str->type;
- // Negative INTEGER and ENUMERATED values are the only case where
- // |ASN1_STRING| and |ASN1_TYPE| types do not match.
- //
- // TODO(davidben): There are also some type fields which, in |ASN1_TYPE|, do
- // not correspond to |ASN1_STRING|. It is unclear whether those are allowed
- // in |ASN1_STRING| at all, or what the space of allowed types is.
- // |ASN1_item_ex_d2i| will never produce such a value so, for now, we say
- // this is an invalid input. But this corner of the library in general
- // should be more robust.
- if (t.type == V_ASN1_NEG_INTEGER) {
- t.type = V_ASN1_INTEGER;
- } else if (t.type == V_ASN1_NEG_ENUMERATED) {
- t.type = V_ASN1_ENUMERATED;
- }
- t.value.asn1_string = (ASN1_STRING *)str;
+ OPENSSL_memset(&t, 0, sizeof(ASN1_TYPE));
+ asn1_type_set0_string(&t, (ASN1_STRING *)str);
unsigned char *der_buf = NULL;
int der_len = i2d_ASN1_TYPE(&t, &der_buf);
if (der_len < 0) {
diff --git a/crypto/asn1/a_type.c b/crypto/asn1/a_type.c
index 0c2fd13..af603a3 100644
--- a/crypto/asn1/a_type.c
+++ b/crypto/asn1/a_type.c
@@ -56,7 +56,8 @@
#include <openssl/asn1.h>
-#include <openssl/asn1t.h>
+#include <assert.h>
+
#include <openssl/err.h>
#include <openssl/mem.h>
#include <openssl/obj.h>
@@ -89,6 +90,23 @@
}
}
+void asn1_type_set0_string(ASN1_TYPE *a, ASN1_STRING *str) {
+ // |ASN1_STRING| types are almost the same as |ASN1_TYPE| types, except that
+ // the negative flag is not reflected into |ASN1_TYPE|.
+ int type = str->type;
+ if (type == V_ASN1_NEG_INTEGER) {
+ type = V_ASN1_INTEGER;
+ } else if (type == V_ASN1_NEG_ENUMERATED) {
+ type = V_ASN1_ENUMERATED;
+ }
+
+ // These types are not |ASN1_STRING| types and use a different
+ // representation when stored in |ASN1_TYPE|.
+ assert(type != V_ASN1_NULL && type != V_ASN1_OBJECT &&
+ type != V_ASN1_BOOLEAN);
+ ASN1_TYPE_set(a, type, str);
+}
+
void asn1_type_cleanup(ASN1_TYPE *a) {
switch (a->type) {
case V_ASN1_NULL:
diff --git a/crypto/asn1/internal.h b/crypto/asn1/internal.h
index 414b5a9..57e6966 100644
--- a/crypto/asn1/internal.h
+++ b/crypto/asn1/internal.h
@@ -210,6 +210,10 @@
// a pointer.
const void *asn1_type_value_as_pointer(const ASN1_TYPE *a);
+// asn1_type_set0_string sets |a|'s value to the object represented by |str| and
+// takes ownership of |str|.
+void asn1_type_set0_string(ASN1_TYPE *a, ASN1_STRING *str);
+
// asn1_type_cleanup releases memory associated with |a|'s value, without
// freeing |a| itself.
void asn1_type_cleanup(ASN1_TYPE *a);
diff --git a/crypto/x509/x509_att.c b/crypto/x509/x509_att.c
index 062168e..e3d0c6a 100644
--- a/crypto/x509/x509_att.c
+++ b/crypto/x509/x509_att.c
@@ -137,54 +137,57 @@
int X509_ATTRIBUTE_set1_data(X509_ATTRIBUTE *attr, int attrtype,
const void *data, int len) {
- ASN1_TYPE *ttmp = NULL;
- ASN1_STRING *stmp = NULL;
- int atype = 0;
if (!attr) {
return 0;
}
- if (attrtype & MBSTRING_FLAG) {
- stmp = ASN1_STRING_set_by_NID(NULL, data, len, attrtype,
- OBJ_obj2nid(attr->object));
- if (!stmp) {
- OPENSSL_PUT_ERROR(X509, ERR_R_ASN1_LIB);
- return 0;
- }
- atype = stmp->type;
- } else if (len != -1) {
- if (!(stmp = ASN1_STRING_type_new(attrtype))) {
- goto err;
- }
- if (!ASN1_STRING_set(stmp, data, len)) {
- goto err;
- }
- atype = attrtype;
- }
- // This is a bit naughty because the attribute should really have at
- // least one value but some types use and zero length SET and require
- // this.
+
if (attrtype == 0) {
- ASN1_STRING_free(stmp);
+ // Do nothing. This is used to create an empty value set in
+ // |X509_ATTRIBUTE_create_by_*|. This is invalid, but supported by OpenSSL.
return 1;
}
- if (!(ttmp = ASN1_TYPE_new())) {
- goto err;
+
+ ASN1_TYPE *typ = ASN1_TYPE_new();
+ if (typ == NULL) {
+ return 0;
}
- if ((len == -1) && !(attrtype & MBSTRING_FLAG)) {
- if (!ASN1_TYPE_set1(ttmp, attrtype, data)) {
+
+ // This function is several functions in one.
+ if (attrtype & MBSTRING_FLAG) {
+ // |data| is an encoded string. We must decode and re-encode it to |attr|'s
+ // preferred ASN.1 type. Note |len| may be -1, in which case
+ // |ASN1_STRING_set_by_NID| calls |strlen| automatically.
+ ASN1_STRING *str = ASN1_STRING_set_by_NID(NULL, data, len, attrtype,
+ OBJ_obj2nid(attr->object));
+ if (str == NULL) {
+ OPENSSL_PUT_ERROR(X509, ERR_R_ASN1_LIB);
goto err;
}
+ asn1_type_set0_string(typ, str);
+ } else if (len != -1) {
+ // |attrtype| must be a valid |ASN1_STRING| type. |data| and |len| is a
+ // value in the corresponding |ASN1_STRING| representation.
+ ASN1_STRING *str = ASN1_STRING_type_new(attrtype);
+ if (str == NULL || !ASN1_STRING_set(str, data, len)) {
+ ASN1_STRING_free(str);
+ goto err;
+ }
+ asn1_type_set0_string(typ, str);
} else {
- ASN1_TYPE_set(ttmp, atype, stmp);
- stmp = NULL;
+ // |attrtype| must be a valid |ASN1_TYPE| type. |data| is a pointer to an
+ // object of the corresponding type.
+ if (!ASN1_TYPE_set1(typ, attrtype, data)) {
+ goto err;
+ }
}
- if (!sk_ASN1_TYPE_push(attr->set, ttmp)) {
+
+ if (!sk_ASN1_TYPE_push(attr->set, typ)) {
goto err;
}
return 1;
+
err:
- ASN1_TYPE_free(ttmp);
- ASN1_STRING_free(stmp);
+ ASN1_TYPE_free(typ);
return 0;
}
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index f8c930f..a590e08 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -3832,46 +3832,62 @@
// Test the various |X509_ATTRIBUTE| creation functions.
TEST(X509Test, Attribute) {
- // The friendlyName attribute has a BMPString value. See RFC 2985,
- // section 5.5.1.
+ // The expected attribute values are:
+ // 1. BMPString U+2603
+ // 2. BMPString "test"
+ // 3. INTEGER -1 (not valid for friendlyName)
static const uint8_t kTest1[] = {0x26, 0x03}; // U+2603 SNOWMAN
static const uint8_t kTest1UTF8[] = {0xe2, 0x98, 0x83};
static const uint8_t kTest2[] = {0, 't', 0, 'e', 0, 's', 0, 't'};
- auto check_attribute = [&](X509_ATTRIBUTE *attr, bool has_test2) {
+ constexpr uint32_t kTest1Mask = 1 << 0;
+ constexpr uint32_t kTest2Mask = 1 << 1;
+ constexpr uint32_t kTest3Mask = 1 << 2;
+ auto check_attribute = [&](X509_ATTRIBUTE *attr, uint32_t mask) {
EXPECT_EQ(NID_friendlyName, OBJ_obj2nid(X509_ATTRIBUTE_get0_object(attr)));
- EXPECT_EQ(has_test2 ? 2 : 1, X509_ATTRIBUTE_count(attr));
+ int idx = 0;
+ if (mask & kTest1Mask) {
+ // The first attribute should contain |kTest1|.
+ const ASN1_TYPE *value = X509_ATTRIBUTE_get0_type(attr, idx);
+ ASSERT_TRUE(value);
+ EXPECT_EQ(V_ASN1_BMPSTRING, value->type);
+ EXPECT_EQ(Bytes(kTest1),
+ Bytes(ASN1_STRING_get0_data(value->value.bmpstring),
+ ASN1_STRING_length(value->value.bmpstring)));
- // The first attribute should contain |kTest1|.
- const ASN1_TYPE *value = X509_ATTRIBUTE_get0_type(attr, 0);
- ASSERT_TRUE(value);
- EXPECT_EQ(V_ASN1_BMPSTRING, value->type);
- EXPECT_EQ(Bytes(kTest1),
- Bytes(ASN1_STRING_get0_data(value->value.bmpstring),
- ASN1_STRING_length(value->value.bmpstring)));
+ // |X509_ATTRIBUTE_get0_data| requires the type match.
+ EXPECT_FALSE(
+ X509_ATTRIBUTE_get0_data(attr, idx, V_ASN1_OCTET_STRING, nullptr));
+ const ASN1_BMPSTRING *bmpstring = static_cast<const ASN1_BMPSTRING *>(
+ X509_ATTRIBUTE_get0_data(attr, idx, V_ASN1_BMPSTRING, nullptr));
+ ASSERT_TRUE(bmpstring);
+ EXPECT_EQ(Bytes(kTest1), Bytes(ASN1_STRING_get0_data(bmpstring),
+ ASN1_STRING_length(bmpstring)));
+ idx++;
+ }
- // |X509_ATTRIBUTE_get0_data| requires the type match.
- EXPECT_FALSE(
- X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_OCTET_STRING, nullptr));
- const ASN1_BMPSTRING *bmpstring = static_cast<const ASN1_BMPSTRING *>(
- X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_BMPSTRING, nullptr));
- ASSERT_TRUE(bmpstring);
- EXPECT_EQ(Bytes(kTest1), Bytes(ASN1_STRING_get0_data(bmpstring),
- ASN1_STRING_length(bmpstring)));
-
- if (has_test2) {
- value = X509_ATTRIBUTE_get0_type(attr, 1);
+ if (mask & kTest2Mask) {
+ const ASN1_TYPE *value = X509_ATTRIBUTE_get0_type(attr, idx);
ASSERT_TRUE(value);
EXPECT_EQ(V_ASN1_BMPSTRING, value->type);
EXPECT_EQ(Bytes(kTest2),
Bytes(ASN1_STRING_get0_data(value->value.bmpstring),
ASN1_STRING_length(value->value.bmpstring)));
- } else {
- EXPECT_FALSE(X509_ATTRIBUTE_get0_type(attr, 1));
+ idx++;
}
- EXPECT_FALSE(X509_ATTRIBUTE_get0_type(attr, 2));
+ if (mask & kTest3Mask) {
+ const ASN1_TYPE *value = X509_ATTRIBUTE_get0_type(attr, idx);
+ ASSERT_TRUE(value);
+ EXPECT_EQ(V_ASN1_INTEGER, value->type);
+ int64_t v;
+ ASSERT_TRUE(ASN1_INTEGER_get_int64(&v, value->value.integer));
+ EXPECT_EQ(v, -1);
+ idx++;
+ }
+
+ EXPECT_FALSE(X509_ATTRIBUTE_get0_type(attr, idx));
};
bssl::UniquePtr<ASN1_STRING> str(ASN1_STRING_type_new(V_ASN1_BMPSTRING));
@@ -3883,7 +3899,7 @@
X509_ATTRIBUTE_create(NID_friendlyName, V_ASN1_BMPSTRING, str.get()));
ASSERT_TRUE(attr);
str.release(); // |X509_ATTRIBUTE_create| takes ownership on success.
- check_attribute(attr.get(), /*has_test2=*/false);
+ check_attribute(attr.get(), kTest1Mask);
// Test the |MBSTRING_*| form of |X509_ATTRIBUTE_set1_data|.
attr.reset(X509_ATTRIBUTE_new());
@@ -3892,12 +3908,19 @@
X509_ATTRIBUTE_set1_object(attr.get(), OBJ_nid2obj(NID_friendlyName)));
ASSERT_TRUE(X509_ATTRIBUTE_set1_data(attr.get(), MBSTRING_UTF8, kTest1UTF8,
sizeof(kTest1UTF8)));
- check_attribute(attr.get(), /*has_test2=*/false);
+ check_attribute(attr.get(), kTest1Mask);
// Test the |ASN1_STRING| form of |X509_ATTRIBUTE_set1_data|.
ASSERT_TRUE(X509_ATTRIBUTE_set1_data(attr.get(), V_ASN1_BMPSTRING, kTest2,
sizeof(kTest2)));
- check_attribute(attr.get(), /*has_test2=*/true);
+ check_attribute(attr.get(), kTest1Mask | kTest2Mask);
+
+ // The |ASN1_STRING| form of |X509_ATTRIBUTE_set1_data| should correctly
+ // handle negative integers.
+ const uint8_t kOne = 1;
+ ASSERT_TRUE(
+ X509_ATTRIBUTE_set1_data(attr.get(), V_ASN1_NEG_INTEGER, &kOne, 1));
+ check_attribute(attr.get(), kTest1Mask | kTest2Mask | kTest3Mask);
// Test the |ASN1_TYPE| form of |X509_ATTRIBUTE_set1_data|.
attr.reset(X509_ATTRIBUTE_new());
@@ -3909,7 +3932,13 @@
ASSERT_TRUE(ASN1_STRING_set(str.get(), kTest1, sizeof(kTest1)));
ASSERT_TRUE(
X509_ATTRIBUTE_set1_data(attr.get(), V_ASN1_BMPSTRING, str.get(), -1));
- check_attribute(attr.get(), /*has_test2=*/false);
+ check_attribute(attr.get(), kTest1Mask);
+
+ // An |attrtype| of zero leaves the attribute empty.
+ attr.reset(X509_ATTRIBUTE_create_by_NID(
+ nullptr, NID_friendlyName, /*attrtype=*/0, /*data=*/nullptr, /*len=*/0));
+ ASSERT_TRUE(attr);
+ check_attribute(attr.get(), 0);
}
// Test that, by default, |X509_V_FLAG_TRUSTED_FIRST| is set, which means we'll
diff --git a/include/openssl/x509.h b/include/openssl/x509.h
index 66668a6..852a96f 100644
--- a/include/openssl/x509.h
+++ b/include/openssl/x509.h
@@ -2101,21 +2101,21 @@
// X509_ATTRIBUTE_set1_data appends a value to |attr|'s value set and returns
// one on success or zero on error. The value is determined as follows:
//
-// If |attrtype| is a |MBSTRING_*| constant, the value is an ASN.1 string. The
-// string is determined by decoding |len| bytes from |data| in the encoding
-// specified by |attrtype|, and then re-encoding it in a form appropriate for
-// |attr|'s type. If |len| is -1, |strlen(data)| is used instead. See
-// |ASN1_STRING_set_by_NID| for details.
+// If |attrtype| is zero, this function returns one and does nothing. This form
+// may be used when calling |X509_ATTRIBUTE_create_by_*| to create an attribute
+// with an empty value set. Such attributes are invalid, but OpenSSL supports
+// creating them.
+//
+// Otherwise, if |attrtype| is a |MBSTRING_*| constant, the value is an ASN.1
+// string. The string is determined by decoding |len| bytes from |data| in the
+// encoding specified by |attrtype|, and then re-encoding it in a form
+// appropriate for |attr|'s type. If |len| is -1, |strlen(data)| is used
+// instead. See |ASN1_STRING_set_by_NID| for details.
//
// Otherwise, if |len| is not -1, the value is an ASN.1 string. |attrtype| is an
// |ASN1_STRING| type value and the |len| bytes from |data| are copied as the
// type-specific representation of |ASN1_STRING|. See |ASN1_STRING| for details.
//
-// WARNING: If this form is used to construct a negative INTEGER or ENUMERATED,
-// |attrtype| includes the |V_ASN1_NEG| flag for |ASN1_STRING|, but the function
-// forgets to clear the flag for |ASN1_TYPE|. This matches OpenSSL but is
-// probably a bug. For now, do not use this form with negative values.
-//
// Otherwise, if |len| is -1, the value is constructed by passing |attrtype| and
// |data| to |ASN1_TYPE_set1|. That is, |attrtype| is an |ASN1_TYPE| type value,
// and |data| is cast to the corresponding pointer type.