Make BN_bn2bin_padded work with non-minimal BIGNUMs.
Checking the excess words for zero doesn't need to be in constant time,
but it's free. BN_bn2bin_padded is a little silly as read_word_padded
only exists to work around bn->top being minimal. Once non-minimal
BIGNUMs are turned on and the RSA code works right, we can simplify
BN_bn2bin_padded.
Bug: 232
Change-Id: Ib81e30ca1e5a8ea90ab3278bf4ded219bac481ac
Reviewed-on: https://boringssl-review.googlesource.com/25253
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/fipsmodule/bn/bn_test.cc b/crypto/fipsmodule/bn/bn_test.cc
index 89db4ad..94b8973 100644
--- a/crypto/fipsmodule/bn/bn_test.cc
+++ b/crypto/fipsmodule/bn/bn_test.cc
@@ -864,6 +864,17 @@
EXPECT_EQ(Bytes(zeros, sizeof(out) - bytes),
Bytes(out, sizeof(out) - bytes));
EXPECT_EQ(Bytes(reference, bytes), Bytes(out + sizeof(out) - bytes, bytes));
+
+#if !defined(BORINGSSL_SHARED_LIBRARY)
+ // Repeat some tests with a non-minimal |BIGNUM|.
+ EXPECT_TRUE(bn_resize_words(n.get(), 32));
+
+ EXPECT_FALSE(BN_bn2bin_padded(out, bytes - 1, n.get()));
+
+ ASSERT_TRUE(BN_bn2bin_padded(out, bytes + 1, n.get()));
+ EXPECT_EQ(0u, out[0]);
+ EXPECT_EQ(Bytes(reference, bytes), Bytes(out + 1, bytes));
+#endif
}
}
diff --git a/crypto/fipsmodule/bn/bytes.c b/crypto/fipsmodule/bn/bytes.c
index 887f526..aa65483 100644
--- a/crypto/fipsmodule/bn/bytes.c
+++ b/crypto/fipsmodule/bn/bytes.c
@@ -159,22 +159,9 @@
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;
-}
+// TODO(davidben): This does not need to be quite so complex once the |BIGNUM|s
+// we care about are fixed-width. |read_word_padded| is a hack to paper over
+// parts of the |bn_correct_top| leak. Fix that, and this can be simpler.
// 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.
@@ -197,6 +184,10 @@
// the access would be out of bounds, it reads the last word of |in|. |in| must
// not be zero.
static BN_ULONG read_word_padded(const BIGNUM *in, size_t i) {
+ if (in->dmax == 0) {
+ return 0;
+ }
+
// Read |in->d[i]| if valid. Otherwise, read the last word.
BN_ULONG l = in->d[constant_time_select_ulong(
constant_time_le_size_t(in->dmax, i), in->dmax - 1, i)];
@@ -205,23 +196,44 @@
return constant_time_select_ulong(constant_time_le_size_t(in->top, i), 0, l);
}
-int BN_bn2bin_padded(uint8_t *out, size_t len, const BIGNUM *in) {
- // Special case for |in| = 0. Just branch as the probability is negligible.
- if (BN_is_zero(in)) {
- OPENSSL_memset(out, 0, len);
- return 1;
- }
-
- // Check if the integer is too big. This case can exit early in non-constant
- // time.
- if ((size_t)in->top > (len + (BN_BYTES - 1)) / BN_BYTES) {
- return 0;
+static int fits_in_bytes(const BIGNUM *in, size_t len) {
+ BN_ULONG mask = 0;
+ for (size_t i = (len + (BN_BYTES - 1)) / BN_BYTES; i < (size_t)in->top; i++) {
+ mask |= in->d[i];
}
if ((len % BN_BYTES) != 0) {
BN_ULONG l = read_word_padded(in, len / BN_BYTES);
- if (l >> (8 * (len % BN_BYTES)) != 0) {
- return 0;
- }
+ mask |= l >> (8 * (len % BN_BYTES));
+ }
+ return mask == 0;
+}
+
+int BN_bn2le_padded(uint8_t *out, size_t len, const BIGNUM *in) {
+ // If we don't have enough space, fail out.
+ if (!fits_in_bytes(in, len)) {
+ return 0;
+ }
+
+ size_t todo = in->top * BN_BYTES;
+ if (todo > len) {
+ todo = len;
+ }
+
+ // We only support little-endian platforms, so we can simply memcpy into the
+ // internal representation.
+ OPENSSL_memcpy(out, in->d, todo);
+
+ // Pad out the rest of the buffer with zeroes.
+ OPENSSL_memset(out + todo, 0, len - todo);
+
+ return 1;
+}
+
+int BN_bn2bin_padded(uint8_t *out, size_t len, const BIGNUM *in) {
+ // Check if the integer is too big. This case can exit early in non-constant
+ // time.
+ if (!fits_in_bytes(in, len)) {
+ return 0;
}
// Write the bytes out one by one. Serialization is done without branching on