Add Little-endian BIGNUM conversions
Towards an eventual goal of opaquifying BoringSSL structs, we want
our consumers -- in this case, Android's libcore -- to not directly
manipulate BigNums; and it would be convenient for them if we would
perform the appropriate gymnastics to interpret little-endian byte
streams.
It also seems a priori a bit strange to have only big-endian varieties
of BN byte-conversions.
This CL provides little-endian equivalents of BN_bn2bin_padded
and BN_bin2bn.
BUG=97
Change-Id: I0e92483286def86d9bd71a46d6a967a3be50f80b
Reviewed-on: https://boringssl-review.googlesource.com/12641
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/crypto/bn/bn_test.cc b/crypto/bn/bn_test.cc
index 89d9fed..4f544a7 100644
--- a/crypto/bn/bn_test.cc
+++ b/crypto/bn/bn_test.cc
@@ -747,6 +747,82 @@
return true;
}
+static bool TestLittleEndian() {
+ bssl::UniquePtr<BIGNUM> x(BN_new());
+ bssl::UniquePtr<BIGNUM> y(BN_new());
+ if (!x || !y) {
+ fprintf(stderr, "BN_new failed to malloc.\n");
+ return false;
+ }
+
+ // Test edge case at 0. Fill |out| with garbage to ensure |BN_bn2le_padded|
+ // wrote the result.
+ uint8_t out[256], zeros[256];
+ OPENSSL_memset(out, -1, sizeof(out));
+ OPENSSL_memset(zeros, 0, sizeof(zeros));
+ if (!BN_bn2le_padded(out, sizeof(out), x.get()) ||
+ OPENSSL_memcmp(zeros, out, sizeof(out))) {
+ fprintf(stderr, "BN_bn2le_padded failed to encode 0.\n");
+ return false;
+ }
+
+ if (!BN_le2bn(out, sizeof(out), y.get()) ||
+ BN_cmp(x.get(), y.get()) != 0) {
+ fprintf(stderr, "BN_le2bn failed to decode 0 correctly.\n");
+ return false;
+ }
+
+ // Test random numbers at various byte lengths.
+ for (size_t bytes = 128 - 7; bytes <= 128; bytes++) {
+ if (!BN_rand(x.get(), bytes * 8, BN_RAND_TOP_ONE, BN_RAND_BOTTOM_ANY)) {
+ ERR_print_errors_fp(stderr);
+ return false;
+ }
+
+ // Fill |out| with garbage to ensure |BN_bn2le_padded| wrote the result.
+ OPENSSL_memset(out, -1, sizeof(out));
+ if (!BN_bn2le_padded(out, sizeof(out), x.get())) {
+ fprintf(stderr, "BN_bn2le_padded failed to encode random value.\n");
+ return false;
+ }
+
+ // Compute the expected value by reversing the big-endian output.
+ uint8_t expected[sizeof(out)];
+ if (!BN_bn2bin_padded(expected, sizeof(expected), x.get())) {
+ return false;
+ }
+ for (size_t i = 0; i < sizeof(expected) / 2; i++) {
+ uint8_t tmp = expected[i];
+ expected[i] = expected[sizeof(expected) - 1 - i];
+ expected[sizeof(expected) - 1 - i] = tmp;
+ }
+
+ if (OPENSSL_memcmp(expected, out, sizeof(out))) {
+ fprintf(stderr, "BN_bn2le_padded failed to encode value correctly.\n");
+ hexdump(stderr, "Expected: ", expected, sizeof(expected));
+ hexdump(stderr, "Got: ", out, sizeof(out));
+ return false;
+ }
+
+ // Make sure the decoding produces the same BIGNUM.
+ if (!BN_le2bn(out, bytes, y.get()) ||
+ BN_cmp(x.get(), y.get()) != 0) {
+ bssl::UniquePtr<char> x_hex(BN_bn2hex(x.get())),
+ y_hex(BN_bn2hex(y.get()));
+ if (!x_hex || !y_hex) {
+ return false;
+ }
+ fprintf(stderr, "BN_le2bn failed to decode value correctly.\n");
+ fprintf(stderr, "Expected: %s\n", x_hex.get());
+ hexdump(stderr, "Encoding: ", out, bytes);
+ fprintf(stderr, "Got: %s\n", y_hex.get());
+ return false;
+ }
+ }
+
+ return true;
+}
+
static int DecimalToBIGNUM(bssl::UniquePtr<BIGNUM> *out, const char *in) {
BIGNUM *raw = NULL;
int ret = BN_dec2bn(&raw, in);
@@ -1569,6 +1645,7 @@
!TestDec2BN(ctx.get()) ||
!TestHex2BN(ctx.get()) ||
!TestASC2BN(ctx.get()) ||
+ !TestLittleEndian() ||
!TestMPI() ||
!TestRand() ||
!TestASN1() ||
diff --git a/crypto/bn/convert.c b/crypto/bn/convert.c
index c44e7a8..1fa0dd4 100644
--- a/crypto/bn/convert.c
+++ b/crypto/bn/convert.c
@@ -118,6 +118,42 @@
return ret;
}
+BIGNUM *BN_le2bn(const uint8_t *in, size_t len, BIGNUM *ret) {
+ BIGNUM *bn = NULL;
+ if (ret == NULL) {
+ bn = BN_new();
+ ret = bn;
+ }
+
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ if (len == 0) {
+ ret->top = 0;
+ ret->neg = 0;
+ return ret;
+ }
+
+ /* Reserve enough space in |ret|. */
+ size_t num_words = ((len - 1) / BN_BYTES) + 1;
+ if (!bn_wexpand(ret, num_words)) {
+ BN_free(bn);
+ return NULL;
+ }
+ ret->top = num_words;
+
+ /* Make sure the top bytes will be zeroed. */
+ ret->d[num_words - 1] = 0;
+
+ /* We only support little-endian platforms, so we can simply memcpy the
+ * internal representation. */
+ OPENSSL_memcpy(ret->d, in, len);
+
+ bn_correct_top(ret);
+ return ret;
+}
+
size_t BN_bn2bin(const BIGNUM *in, uint8_t *out) {
size_t n, i;
BN_ULONG l;
@@ -130,6 +166,23 @@
return n;
}
+int BN_bn2le_padded(uint8_t *out, size_t len, const BIGNUM *in) {
+ /* If we don't have enough space, fail out. */
+ size_t num_bytes = BN_num_bytes(in);
+ if (len < num_bytes) {
+ return 0;
+ }
+
+ /* We only support little-endian platforms, so we can simply memcpy into the
+ * internal representation. */
+ OPENSSL_memcpy(out, in->d, num_bytes);
+
+ /* Pad out the rest of the buffer with zeroes. */
+ OPENSSL_memset(out + num_bytes, 0, len - num_bytes);
+
+ return 1;
+}
+
/* constant_time_select_ulong returns |x| if |v| is 1 and |y| if |v| is 0. Its
* behavior is undefined if |v| takes any other value. */
static BN_ULONG constant_time_select_ulong(int v, BN_ULONG x, BN_ULONG y) {
diff --git a/include/openssl/bn.h b/include/openssl/bn.h
index d3b6644..18beba4 100644
--- a/include/openssl/bn.h
+++ b/include/openssl/bn.h
@@ -253,6 +253,18 @@
* number of bytes written. */
OPENSSL_EXPORT size_t BN_bn2bin(const BIGNUM *in, uint8_t *out);
+/* BN_le2bn sets |*ret| to the value of |len| bytes from |in|, interpreted as
+ * a little-endian number, and returns |ret|. If |ret| is NULL then a fresh
+ * |BIGNUM| is allocated and returned. It returns NULL on allocation
+ * failure. */
+OPENSSL_EXPORT BIGNUM *BN_le2bn(const uint8_t *in, size_t len, BIGNUM *ret);
+
+/* BN_bn2le_padded serialises the absolute value of |in| to |out| as a
+ * little-endian integer, which must have |len| of space available, padding
+ * out the remainder of out with zeros. If |len| is smaller than |BN_num_bytes|,
+ * the function fails and returns 0. Otherwise, it returns 1. */
+OPENSSL_EXPORT int BN_bn2le_padded(uint8_t *out, size_t len, const BIGNUM *in);
+
/* BN_bn2bin_padded serialises the absolute value of |in| to |out| as a
* big-endian integer. The integer is padded with leading zeros up to size
* |len|. If |len| is smaller than |BN_num_bytes|, the function fails and