Correctly handle LONG_MIN in ASN1_INTEGER_get.
Along the way, add ASN1_INTEGER_get_uint64 from upstream, which has much
better error-handling. Also fold the IntegerSetting test into the main
integer test as the test data is largely redundant.
Change-Id: I7ec84629264ebf405bea4bce59a13c4495d81ed7
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/51634
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/asn1/a_int.c b/crypto/asn1/a_int.c
index 63deded..512472a 100644
--- a/crypto/asn1/a_int.c
+++ b/crypto/asn1/a_int.c
@@ -62,6 +62,7 @@
#include <openssl/bytestring.h>
#include <openssl/err.h>
#include <openssl/mem.h>
+#include <openssl/type_check.h>
#include "../internal.h"
@@ -309,43 +310,76 @@
return asn1_string_set_uint64(out, v, V_ASN1_ENUMERATED);
}
+static int asn1_string_get_abs_uint64(uint64_t *out, const ASN1_STRING *a,
+ int type)
+{
+ if ((a->type & ~V_ASN1_NEG) != type) {
+ OPENSSL_PUT_ERROR(ASN1, ASN1_R_WRONG_INTEGER_TYPE);
+ return 0;
+ }
+ uint8_t buf[sizeof(uint64_t)] = {0};
+ if (a->length > (int)sizeof(buf)) {
+ OPENSSL_PUT_ERROR(ASN1, ASN1_R_INVALID_INTEGER);
+ return 0;
+ }
+ OPENSSL_memcpy(buf + sizeof(buf) - a->length, a->data, a->length);
+ *out = CRYPTO_load_u64_be(buf);
+ return 1;
+}
+
+static int asn1_string_get_uint64(uint64_t *out, const ASN1_STRING *a, int type)
+{
+ if (!asn1_string_get_abs_uint64(out, a, type)) {
+ return 0;
+ }
+ if (a->type & V_ASN1_NEG) {
+ OPENSSL_PUT_ERROR(ASN1, ASN1_R_INVALID_INTEGER);
+ return 0;
+ }
+ return 1;
+}
+
+int ASN1_INTEGER_get_uint64(uint64_t *out, const ASN1_INTEGER *a)
+{
+ return asn1_string_get_uint64(out, a, V_ASN1_INTEGER);
+}
+
+int ASN1_ENUMERATED_get_uint64(uint64_t *out, const ASN1_ENUMERATED *a)
+{
+ return asn1_string_get_uint64(out, a, V_ASN1_ENUMERATED);
+}
+
static long asn1_string_get_long(const ASN1_STRING *a, int type)
{
- int neg = 0, i;
-
- if (a == NULL)
- return (0L);
- i = a->type;
- if (i == (type | V_ASN1_NEG))
- neg = 1;
- else if (i != type)
- return -1;
-
- OPENSSL_STATIC_ASSERT(sizeof(uint64_t) >= sizeof(long),
- "long larger than uint64_t");
-
- if (a->length > (int)sizeof(uint64_t)) {
- /* hmm... a bit ugly, return all ones */
- return -1;
+ if (a == NULL) {
+ return 0;
}
- uint64_t r64 = 0;
- if (a->data != NULL) {
- for (i = 0; i < a->length; i++) {
- r64 <<= 8;
- r64 |= (unsigned char)a->data[i];
- }
-
- if (r64 > LONG_MAX) {
- return -1;
- }
+ uint64_t v;
+ if (!asn1_string_get_abs_uint64(&v, a, type)) {
+ goto err;
}
- long r = (long) r64;
- if (neg)
- r = -r;
+ int64_t i64;
+ int fits_in_i64;
+ /* Check |v != 0| to handle manually-constructed negative zeros. */
+ if ((a->type & V_ASN1_NEG) && v != 0) {
+ i64 = (int64_t)(0u - v);
+ fits_in_i64 = i64 < 0;
+ } else {
+ i64 = (int64_t)v;
+ fits_in_i64 = i64 >= 0;
+ }
+ OPENSSL_STATIC_ASSERT(sizeof(long) <= sizeof(int64_t), "long is too big");
- return r;
+ if (fits_in_i64 && LONG_MIN <= i64 && i64 <= LONG_MAX) {
+ return (long)i64;
+ }
+
+err:
+ /* This function's return value does not distinguish overflow from -1. */
+ ERR_clear_error();
+ return -1;
}
long ASN1_INTEGER_get(const ASN1_INTEGER *a)
diff --git a/crypto/asn1/asn1_test.cc b/crypto/asn1/asn1_test.cc
index ae3ddc2..7d69889 100644
--- a/crypto/asn1/asn1_test.cc
+++ b/crypto/asn1/asn1_test.cc
@@ -328,14 +328,17 @@
objs["der"] = std::move(by_der);
// Construct |ASN1_INTEGER| from |long| or |uint64_t|, if it fits.
- bool fits_in_long = false;
+ bool fits_in_long = false, fits_in_u64 = false;
+ uint64_t u64 = 0;
long l = 0;
uint64_t abs_u64;
if (BN_get_u64(bn.get(), &abs_u64)) {
- if (!BN_is_negative(bn.get())) {
+ fits_in_u64 = !BN_is_negative(bn.get());
+ if (fits_in_u64) {
+ u64 = abs_u64;
bssl::UniquePtr<ASN1_INTEGER> by_u64(ASN1_INTEGER_new());
ASSERT_TRUE(by_u64);
- ASSERT_TRUE(ASN1_INTEGER_set_uint64(by_u64.get(), abs_u64));
+ ASSERT_TRUE(ASN1_INTEGER_set_uint64(by_u64.get(), u64));
objs["u64"] = std::move(by_u64);
}
@@ -383,8 +386,16 @@
ASSERT_TRUE(bn2);
EXPECT_EQ(0, BN_cmp(bn.get(), bn2.get()));
- // TODO(davidben): Fix |ASN1_INTEGER_get| to correctly handle |LONG_MIN|.
- if (fits_in_long && l != LONG_MIN) {
+ if (fits_in_u64) {
+ uint64_t v;
+ ASSERT_TRUE(ASN1_INTEGER_get_uint64(&v, obj));
+ EXPECT_EQ(v, u64);
+ } else {
+ uint64_t v;
+ EXPECT_FALSE(ASN1_INTEGER_get_uint64(&v, obj));
+ }
+
+ if (fits_in_long) {
EXPECT_EQ(l, ASN1_INTEGER_get(obj));
} else {
EXPECT_EQ(-1, ASN1_INTEGER_get(obj));
diff --git a/include/openssl/asn1.h b/include/openssl/asn1.h
index d9a9ca9..d6fa2f7 100644
--- a/include/openssl/asn1.h
+++ b/include/openssl/asn1.h
@@ -1076,16 +1076,25 @@
// |ASN1_INTEGER*|.
DECLARE_ASN1_ITEM(ASN1_INTEGER)
-// ASN1_INTEGER_set sets |a| to an INTEGER with value |v|. It returns one on
-// success and zero on error.
-OPENSSL_EXPORT int ASN1_INTEGER_set(ASN1_INTEGER *a, long v);
-
// ASN1_INTEGER_set_uint64 sets |a| to an INTEGER with value |v|. It returns one
// on success and zero on error.
OPENSSL_EXPORT int ASN1_INTEGER_set_uint64(ASN1_INTEGER *out, uint64_t v);
+// ASN1_INTEGER_set sets |a| to an INTEGER with value |v|. It returns one on
+// success and zero on error.
+OPENSSL_EXPORT int ASN1_INTEGER_set(ASN1_INTEGER *a, long v);
+
+// ASN1_INTEGER_get_uint64 converts |a| to a |uint64_t|. On success, it returns
+// one and sets |*out| to the result. If |a| did not fit or has the wrong type,
+// it returns zero.
+OPENSSL_EXPORT int ASN1_INTEGER_get_uint64(uint64_t *out,
+ const ASN1_INTEGER *a);
+
// ASN1_INTEGER_get returns the value of |a| as a |long|, or -1 if |a| is out of
// range or the wrong type.
+//
+// WARNING: This function's return value cannot distinguish errors from -1.
+// Prefer |ASN1_INTEGER_get_uint64|.
OPENSSL_EXPORT long ASN1_INTEGER_get(const ASN1_INTEGER *a);
// BN_to_ASN1_INTEGER sets |ai| to an INTEGER with value |bn| and returns |ai|
@@ -1131,22 +1140,31 @@
// |ASN1_ENUMERATED*|.
DECLARE_ASN1_ITEM(ASN1_ENUMERATED)
-// ASN1_ENUMERATED_set sets |a| to an ENUMERATED with value |v|. It returns one
-// on success and zero on error.
-OPENSSL_EXPORT int ASN1_ENUMERATED_set(ASN1_ENUMERATED *a, long v);
-
// ASN1_ENUMERATED_set_uint64 sets |a| to an ENUMERATED with value |v|. It
// returns one on success and zero on error.
OPENSSL_EXPORT int ASN1_ENUMERATED_set_uint64(ASN1_ENUMERATED *out, uint64_t v);
+// ASN1_ENUMERATED_set sets |a| to an ENUMERATED with value |v|. It returns one
+// on success and zero on error.
+OPENSSL_EXPORT int ASN1_ENUMERATED_set(ASN1_ENUMERATED *a, long v);
+
+// ASN1_ENUMERATED_get_uint64 converts |a| to a |uint64_t|. On success, it
+// returns one and sets |*out| to the result. If |a| did not fit or has the
+// wrong type, it returns zero.
+OPENSSL_EXPORT int ASN1_ENUMERATED_get_uint64(uint64_t *out,
+ const ASN1_ENUMERATED *a);
+
// ASN1_ENUMERATED_get returns the value of |a| as a |long|, or -1 if |a| is out
// of range or the wrong type.
+//
+// WARNING: This function's return value cannot distinguish errors from -1.
+// Prefer |ASN1_ENUMERATED_get_uint64|.
OPENSSL_EXPORT long ASN1_ENUMERATED_get(const ASN1_ENUMERATED *a);
// BN_to_ASN1_ENUMERATED sets |ai| to an ENUMERATED with value |bn| and returns
// |ai| on success or NULL or error. If |ai| is NULL, it returns a
-// newly-allocated |ASN1_INTEGER| on success instead, which the caller must
-// release with |ASN1_INTEGER_free|.
+// newly-allocated |ASN1_ENUMERATED| on success instead, which the caller must
+// release with |ASN1_ENUMERATED_free|.
OPENSSL_EXPORT ASN1_ENUMERATED *BN_to_ASN1_ENUMERATED(const BIGNUM *bn,
ASN1_ENUMERATED *ai);