Skip to content

Commit

Permalink
EC support for PPC64 big endian (#1214)
Browse files Browse the repository at this point in the history
  • Loading branch information
justsmth authored Oct 4, 2023
1 parent a5c5b0b commit 01b55d9
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 68 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@ cmake-build-debug/
symbols.txt

.DS_Store
.idea
.idea/
.fleet/
.cache/
19 changes: 3 additions & 16 deletions crypto/ec_extra/hash_to_curve.c
Original file line number Diff line number Diff line change
Expand Up @@ -158,19 +158,6 @@ static int num_bytes_to_derive(size_t *out, const BIGNUM *modulus, unsigned k) {
return 1;
}

// big_endian_to_words decodes |in| as a big-endian integer and writes the
// result to |out|. |num_words| must be large enough to contain the output.
static void big_endian_to_words(BN_ULONG *out, size_t num_words,
const uint8_t *in, size_t len) {
assert(len <= num_words * sizeof(BN_ULONG));
// Ensure any excess bytes are zeroed.
OPENSSL_memset(out, 0, num_words * sizeof(BN_ULONG));
uint8_t *out_u8 = (uint8_t *)out;
for (size_t i = 0; i < len; i++) {
out_u8[len - 1 - i] = in[i];
}
}

// hash_to_field implements the operation described in section 5.2
// of draft-irtf-cfrg-hash-to-curve-16, with count = 2. |k| is the security
// factor.
Expand All @@ -186,9 +173,9 @@ static int hash_to_field2(const EC_GROUP *group, const EVP_MD *md,
}
BN_ULONG words[2 * EC_MAX_WORDS];
size_t num_words = 2 * group->field.width;
big_endian_to_words(words, num_words, buf, L);
bn_big_endian_to_words(words, num_words, buf, L);
group->meth->felem_reduce(group, out1, words, num_words);
big_endian_to_words(words, num_words, buf + L, L);
bn_big_endian_to_words(words, num_words, buf + L, L);
group->meth->felem_reduce(group, out2, words, num_words);
return 1;
}
Expand All @@ -207,7 +194,7 @@ static int hash_to_scalar(const EC_GROUP *group, const EVP_MD *md,

BN_ULONG words[2 * EC_MAX_WORDS];
size_t num_words = 2 * group->order.width;
big_endian_to_words(words, num_words, buf, L);
bn_big_endian_to_words(words, num_words, buf, L);
ec_scalar_reduce(group, out, words, num_words);
return 1;
}
Expand Down
60 changes: 60 additions & 0 deletions crypto/endian_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,26 @@ TEST(EndianTest, BN_le2bn) {
EXPECT_EQ((uint64_t)0x0201 << (BN_BITS2-16), x.get()->d[(256*8/BN_BITS2)-1]);
}

// This test creates a BIGNUM, where 255 bytes are significant.
// Notice that 255 = 7 (mod 8) and 255 = 3 (mod 4), so the most significant
// bytes do not fill an entire word in the output BIGNUM, requiring special
// handling in the underlying logic.
TEST(EndianTest, BN_le2bn_255) {
bssl::UniquePtr<BIGNUM> x(BN_new());
uint8_t input[255];
OPENSSL_memset(input, 0, sizeof(input));
input[0] = 0xaa;
input[1] = 0x01;
input[254] = 0x01;
ASSERT_TRUE(BN_le2bn(input, sizeof(input), x.get()));
EXPECT_FALSE(BN_is_zero(x.get()));
for (size_t i = 1; i <= (255/sizeof(BN_ULONG)) - 1; i++) {
EXPECT_EQ((BN_ULONG)0, x.get()->d[i]);
}
EXPECT_EQ((BN_ULONG)0x01aa, x.get()->d[0]);
EXPECT_EQ((BN_ULONG)0x01 << (BN_BITS2-16), x.get()->d[255/sizeof(BN_ULONG)]);
}

TEST(EndianTest, BN_bn2bin) {
bssl::UniquePtr<BIGNUM> x(BN_new());
uint8_t input[256];
Expand Down Expand Up @@ -191,6 +211,46 @@ TEST(EndianTest, BN_bn2le_padded) {
EXPECT_EQ(Bytes(input), Bytes(out));
}

// This test creates a BIGNUM, where 255 bytes are significant.
// It then calls |BN_bn2le_padded| to write the number into a 255-byte array
// in little-endian byte-order.
// Notice that 255 = 7 (mod 8) and 255 = 3 (mod 4), so the output array does not
// have room to hold every word of the input, requiring special handling
// in the underlying logic.
TEST(EndianTest, BN_bn2le_padded_255) {
bssl::UniquePtr<BIGNUM> x(BN_new());
uint8_t input[255];
OPENSSL_memset(input, 0, sizeof(input));
input[0] = 0xaa;
input[1] = 0x01;
input[253] = 0x01;
input[254] = 0x01;
ASSERT_TRUE(BN_le2bn(input, sizeof(input), x.get()));

uint8_t out[255];
OPENSSL_memset(out, 0, sizeof(out));
EXPECT_EQ(1, BN_bn2le_padded(out, sizeof(out), x.get()));
EXPECT_EQ(Bytes(input), Bytes(out));
}

// This test creates a 256-byte BIGNUM, where only 2 bytes are significant.
// It then calls |BN_bn2le_padded| to write the number into a 2-byte array
// in little-endian byte-order.
TEST(EndianTest, BN_bn2le_padded_much) {
bssl::UniquePtr<BIGNUM> x(BN_new());
uint8_t input[256];
OPENSSL_memset(input, 0, sizeof(input));
input[0] = 0xaa;
input[1] = 0x01;
ASSERT_TRUE(BN_le2bn(input, sizeof(input), x.get()));

uint8_t out[2];
OPENSSL_memset(out, 0, sizeof(out));
EXPECT_EQ(1, BN_bn2le_padded(out, sizeof(out), x.get()));
EXPECT_EQ(Bytes(input, 2), Bytes(out, 2));
}


TEST(EndianTest, BN_bn2bin_padded) {
bssl::UniquePtr<BIGNUM> x(BN_new());
uint8_t input[256];
Expand Down
82 changes: 53 additions & 29 deletions crypto/fipsmodule/bn/bytes.c
Original file line number Diff line number Diff line change
Expand Up @@ -140,27 +140,42 @@ BIGNUM *BN_le2bn(const uint8_t *in, size_t len, BIGNUM *ret) {
}
ret->width = (int)num_words;

// Make sure the top bytes will be zeroed.
ret->d[num_words - 1] = 0;
bn_little_endian_to_words(ret->d, ret->width, in, len);

return ret;
}

void bn_little_endian_to_words(BN_ULONG *out, size_t out_len, const uint8_t *in, const size_t in_len) {
assert(out_len > 0);
#ifdef OPENSSL_BIG_ENDIAN
BN_ULONG word = 0;
unsigned m;
size_t in_index = 0;
for (size_t i = 0; i < out_len; i++) {
if ((in_len-in_index) < sizeof(BN_ULONG)) {
// Load the last partial word.
BN_ULONG word = 0;
// size_t is unsigned, so j >= 0 is always true.
for (size_t j = in_len-1; j >= in_index && j < in_len; j--) {
word = (word << 8) | in[j];
}
in_index = in_len;
out[i] = word;

m = (len - 1) % BN_BYTES;
// size_t is unsigned so i >= 0 is always true
for (size_t i = len - 1; i < len; i--) {
word = (word << 8) | in[i];
if (m-- == 0) {
ret->d[--num_words] = word;
word = 0;
m = BN_BYTES - 1;
// Fill the remainder with zeros.
OPENSSL_memset(out + i + 1, 0, (out_len - i - 1) * sizeof(BN_ULONG));
break;
}

out[i] = CRYPTO_load_word_le(in + in_index);
in_index += sizeof(BN_ULONG);
}

// The caller should have sized the output to avoid truncation.
assert(in_index == in_len);
#else
OPENSSL_memcpy(ret->d, in, len);
OPENSSL_memcpy(out, in, in_len);
// Fill the remainder with zeros.
OPENSSL_memset( ((uint8_t*)out) + in_len, 0, sizeof(BN_ULONG)*out_len - in_len);
#endif
return ret;
}

// fits_in_bytes returns one if the |num_words| words in |words| can be
Expand Down Expand Up @@ -220,27 +235,36 @@ 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 (!fits_in_bytes(in->d, in->width, len)) {
return 0;
}

size_t num_bytes = in->width * BN_BYTES;
if (len < num_bytes) {
num_bytes = len;
void bn_words_to_little_endian(uint8_t *out, size_t out_len, const BN_ULONG *in, const size_t in_len) {
// The caller should have selected an output length without truncation.
assert(fits_in_bytes(in, in_len, out_len));
size_t num_bytes = in_len * sizeof(BN_ULONG);
if (out_len < num_bytes) {
num_bytes = out_len;
}
#ifdef OPENSSL_BIG_ENDIAN
BN_ULONG l;
for (size_t i = 0; i < num_bytes; i++) {
l = in->d[i / BN_BYTES];
out[i] = (uint8_t)(l >> (8 * (i % BN_BYTES))) & 0xff;
size_t byte_idx = 0;
for (size_t word_idx = 0; word_idx < in_len; word_idx++) {
BN_ULONG l = in[word_idx];
for(size_t j = 0; j < BN_BYTES && byte_idx < num_bytes; j++) {
out[byte_idx] = (uint8_t)(l & 0xff);
l >>= 8;
byte_idx++;
}
}
#else
const uint8_t *bytes = (const uint8_t *)in->d;
const uint8_t *bytes = (const uint8_t *)in;
OPENSSL_memcpy(out, bytes, num_bytes);
#endif
// Pad out the rest of the buffer with zeroes.
OPENSSL_memset(out + num_bytes, 0, len - num_bytes);
// Fill the remainder with zeros.
OPENSSL_memset(out + num_bytes, 0, out_len - num_bytes);
}

int BN_bn2le_padded(uint8_t *out, size_t len, const BIGNUM *in) {
if (!fits_in_bytes(in->d, in->width, len)) {
return 0;
}
bn_words_to_little_endian(out, len, in->d, in->width);
return 1;
}

Expand Down
29 changes: 23 additions & 6 deletions crypto/fipsmodule/bn/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -745,21 +745,38 @@ void bn_mod_inverse0_prime_mont_small(BN_ULONG *r, const BN_ULONG *a,
// Word-based byte conversion functions.

// bn_big_endian_to_words interprets |in_len| bytes from |in| as a big-endian,
// unsigned integer and writes the result to |out_len| words in |out|. |out_len|
// must be large enough to represent any |in_len|-byte value. That is, |out_len|
// must be at least |BN_BYTES * in_len|.
// unsigned integer and writes the result to |out_len| words in |out|. The output
// is in little-endian word order with |out[0]| being the least-significant word.
// |out_len| must be large enough to represent any |in_len|-byte value. That is,
// |out_len| must be at least |BN_BYTES * in_len|.
void bn_big_endian_to_words(BN_ULONG *out, size_t out_len, const uint8_t *in,
size_t in_len);

// bn_words_to_big_endian represents |in_len| words from |in| as a big-endian,
// unsigned integer in |out_len| bytes. It writes the result to |out|. |out_len|
// must be large enough to represent |in| without truncation.
// bn_words_to_big_endian represents |in_len| words from |in| (in little-endian
// word order) as a big-endian, unsigned integer in |out_len| bytes. It writes
// the result to |out|. |out_len| must be large enough to represent |in| without
// truncation.
//
// Note |out_len| may be less than |BN_BYTES * in_len| if |in| is known to have
// leading zeros.
void bn_words_to_big_endian(uint8_t *out, size_t out_len, const BN_ULONG *in,
size_t in_len);

// bn_little_endian_to_words interprets |in_len| bytes from |in| as a little-endian,
// unsigned integer and writes the result to |out_len| words in |out|. The output
// is in little-endian word order with |out[0]| being the least-significant word.
// |out_len| must be large enough to represent any |in_len|-byte value. That is,
// |out_len| must be at least |BN_BYTES * in_len|.
void bn_little_endian_to_words(BN_ULONG *out, size_t out_len, const uint8_t *in, const size_t in_len);

// bn_words_to_little_endian represents |in_len| words from |in| (in little-endian
// word order) as a little-endian, unsigned integer in |out_len| bytes. It
// writes the result to |out|. |out_len| must be large enough to represent |in|
// without truncation.
//
// Note |out_len| may be less than |BN_BYTES * in_len| if |in| is known to have
// leading zeros.
void bn_words_to_little_endian(uint8_t *out, size_t out_len, const BN_ULONG *in, const size_t in_len);

#if defined(__cplusplus)
} // extern C
Expand Down
9 changes: 4 additions & 5 deletions crypto/fipsmodule/ec/ec_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1886,7 +1886,6 @@ TEST(ECTest, LargeXCoordinateVectors) {
bssl::UniquePtr<EC_POINT> pub_key(EC_POINT_new(group.get()));
ASSERT_TRUE(pub_key);

size_t len = BN_num_bytes(&group.get()->field); // Modulus byte-length
ASSERT_TRUE(EC_KEY_set_group(key.get(), group.get()));

// |EC_POINT_set_affine_coordinates_GFp| sets given (x, y) according to the
Expand All @@ -1900,10 +1899,10 @@ TEST(ECTest, LargeXCoordinateVectors) {
// Set the raw point directly with the BIGNUM coordinates.
// Note that both are in little-endian byte order.
OPENSSL_memcpy(key.get()->pub_key->raw.X.words,
x.get()->d, len);
x.get()->d, BN_BYTES * group->field.width);
OPENSSL_memcpy(key.get()->pub_key->raw.Y.words,
y.get()->d, len);
OPENSSL_memset(key.get()->pub_key->raw.Z.words, 0, len);
y.get()->d, BN_BYTES * group->field.width);
OPENSSL_memset(key.get()->pub_key->raw.Z.words, 0, BN_BYTES * group->field.width);
key.get()->pub_key->raw.Z.words[0] = 1;

// |EC_KEY_check_fips| first calls the |EC_KEY_check_key| function that
Expand All @@ -1922,7 +1921,7 @@ TEST(ECTest, LargeXCoordinateVectors) {

// Now replace the x-coordinate with the larger one, x+p.
OPENSSL_memcpy(key.get()->pub_key->raw.X.words,
xpp.get()->d, len);
xpp.get()->d, BN_BYTES * group->field.width);
// We expect |EC_KEY_check_fips| to always fail when given key with x > p.
ASSERT_FALSE(EC_KEY_check_fips(key.get()));

Expand Down
33 changes: 26 additions & 7 deletions crypto/fipsmodule/ec/p384.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,7 @@ static inline uint8_t p384_use_s2n_bignum_alt(void) {
#define p384_felem_add(out, in0, in1) bignum_add_p384(out, in0, in1)
#define p384_felem_sub(out, in0, in1) bignum_sub_p384(out, in0, in1)
#define p384_felem_opp(out, in0) bignum_neg_p384(out, in0)
// TODO: convert to p384_felem_to_words
#define p384_felem_to_bytes(out, in0) bignum_tolebytes_6(out, in0)
// TODO: convert to p384_felem_from_words
#define p384_felem_from_bytes(out, in0) bignum_fromlebytes_6(out, in0)

// The following four functions need bmi2 and adx support.
Expand Down Expand Up @@ -134,9 +132,7 @@ static p384_limb_t p384_felem_nz(const p384_limb_t in1[P384_NLIMBS]) {
#define p384_felem_sqr(out, in0) fiat_p384_square(out, in0)
#define p384_felem_to_mont(out, in0) fiat_p384_to_montgomery(out, in0)
#define p384_felem_from_mont(out, in0) fiat_p384_from_montgomery(out, in0)
// TODO: convert to p384_felem_to_words
#define p384_felem_to_bytes(out, in0) fiat_p384_to_bytes(out, in0)
// TODO: convert to p384_felem_from_words
#define p384_felem_from_bytes(out, in0) fiat_p384_from_bytes(out, in0)

static p384_limb_t p384_felem_nz(const p384_limb_t in1[P384_NLIMBS]) {
Expand All @@ -147,6 +143,8 @@ static p384_limb_t p384_felem_nz(const p384_limb_t in1[P384_NLIMBS]) {

#endif // P384_USE_S2N_BIGNUM_FIELD_ARITH

#define P384_FELEM_BYTES (48)

static void p384_felem_copy(p384_limb_t out[P384_NLIMBS],
const p384_limb_t in1[P384_NLIMBS]) {
for (size_t i = 0; i < P384_NLIMBS; i++) {
Expand All @@ -164,19 +162,40 @@ static void p384_felem_cmovznz(p384_limb_t out[P384_NLIMBS],
}
}

// NOTE: the input and output are in little-endian representation.
static void p384_from_generic(p384_felem out, const EC_FELEM *in) {
#ifdef OPENSSL_BIG_ENDIAN
uint8_t tmp[P384_FELEM_BYTES];
bn_words_to_little_endian(tmp, P384_FELEM_BYTES, in->words, P384_NLIMBS);
p384_felem_from_bytes(out, tmp);
#else
p384_felem_from_bytes(out, (const uint8_t *)in->words);
#endif
}

// NOTE: the input and output are in little-endian representation.
static void p384_to_generic(EC_FELEM *out, const p384_felem in) {
// This works because 384 is a multiple of 64, so there are no excess bytes to
// zero when rounding up to |BN_ULONG|s.
OPENSSL_STATIC_ASSERT(
384 / 8 == sizeof(BN_ULONG) * ((384 + BN_BITS2 - 1) / BN_BITS2),
p384_felem_to_bytes_leaves_bytes_uninitialized);

#ifdef OPENSSL_BIG_ENDIAN
uint8_t tmp[P384_FELEM_BYTES];
p384_felem_to_bytes(tmp, in);
bn_little_endian_to_words(out->words, P384_NLIMBS, tmp, P384_FELEM_BYTES);
#else
p384_felem_to_bytes((uint8_t *)out->words, in);
#endif
}

static void p384_from_scalar(p384_felem out, const EC_SCALAR *in) {
#ifdef OPENSSL_BIG_ENDIAN
uint8_t tmp[P384_FELEM_BYTES];
bn_words_to_little_endian(tmp, P384_FELEM_BYTES, in->words, P384_NLIMBS);
p384_felem_from_bytes(out, tmp);
#else
p384_felem_from_bytes(out, (const uint8_t *)in->words);
#endif
}

// p384_inv_square calculates |out| = |in|^{-2}
Expand Down Expand Up @@ -586,7 +605,7 @@ static int ec_GFp_nistp384_cmp_x_coordinate(const EC_GROUP *group,
p384_felem_mul(Z2_mont, Z2_mont, Z2_mont);

p384_felem r_Z2;
p384_felem_from_bytes(r_Z2, (const uint8_t*)r->words); // r < order < p, so this is valid.
p384_from_scalar(r_Z2, r); // r < order < p, so this is valid.
p384_felem_mul(r_Z2, r_Z2, Z2_mont);

p384_felem X;
Expand Down
Loading

0 comments on commit 01b55d9

Please sign in to comment.