diff --git a/crypto/bn/bn_test.cc b/crypto/bn/bn_test.cc index 89d9feda..4f544a7f 100644 --- a/crypto/bn/bn_test.cc +++ b/crypto/bn/bn_test.cc @@ -747,6 +747,82 @@ static bool TestBN2BinPadded(BN_CTX *ctx) { return true; } +static bool TestLittleEndian() { + bssl::UniquePtr x(BN_new()); + bssl::UniquePtr 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 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 *out, const char *in) { BIGNUM *raw = NULL; int ret = BN_dec2bn(&raw, in); @@ -1569,6 +1645,7 @@ int main(int argc, char *argv[]) { !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 c44e7a8d..1fa0dd4f 100644 --- a/crypto/bn/convert.c +++ b/crypto/bn/convert.c @@ -118,6 +118,42 @@ BIGNUM *BN_bin2bn(const uint8_t *in, size_t len, BIGNUM *ret) { 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 @@ size_t BN_bn2bin(const BIGNUM *in, uint8_t *out) { 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 d3b66442..18beba4e 100644 --- a/include/openssl/bn.h +++ b/include/openssl/bn.h @@ -253,6 +253,18 @@ OPENSSL_EXPORT BIGNUM *BN_bin2bn(const uint8_t *in, size_t len, BIGNUM *ret); * 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