Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 10 additions & 39 deletions core/ed25519_verify.c
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,16 @@ static int point_is_identity(gf p[4])
return diff == 0;
}

static void scalarbase(gf r[4], const uint8_t *s)
{
gf q[4];
fe_copy16(q[0], BX);
fe_copy16(q[1], BY);
fe_copy16(q[2], gf1);
fe_mul(q[3], BX, BY);
scalarmult(r, q, s);
}

/* Reject a public key outside the prime-order subgroup.
*
* Decoding a point is not enough. Ed25519 has eight points of low order, and
Expand All @@ -310,45 +320,6 @@ static int point_is_identity(gf p[4])
* Formulation taken from eBoot#57 by @muhammadburhandevv-hub, which reached
* this before I did and states both conditions in one expression.
*/
static int key_has_prime_order(gf A[4])
{
uint8_t order_l[32];
gf q[4], multiple[4];
int i;

for (i = 0; i < 32; i++)
order_l[i] = (uint8_t)ORDER_L[i];
for (i = 0; i < 4; i++)
fe_copy16(q[i], A[i]);

scalarmult(multiple, q, order_l);
return point_is_identity(multiple) && !point_is_identity(A);
}

static void scalarbase(gf r[4], const uint8_t *s)
{
gf q[4];
fe_copy16(q[0], BX);
fe_copy16(q[1], BY);
fe_copy16(q[2], gf1);
fe_mul(q[3], BX, BY);
scalarmult(r, q, s);
}

static int point_is_identity(gf p[4])
{
uint8_t encoded[32];
point_pack(encoded, p);

uint8_t diff = (uint8_t)(encoded[0] ^ 1U);
for (int i = 1; i < 32; i++)
diff |= encoded[i];
return diff == 0;
}

/* Public keys must be non-identity points in Ed25519's prime-order subgroup.
* Merely decoding a point is insufficient: an identity or torsion key can
* make the verification equation true without knowledge of a private key. */
static int public_key_is_valid_subgroup(gf public_key[4])
{
uint8_t order_l[32];
Expand Down
16 changes: 11 additions & 5 deletions include/eos_image.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,21 @@ EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, flags) == 24,
"flags must stay at offset 24");
EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, sig_len) == 61,
"sig_len must stay at offset 61");
EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, reserved) == 62,
"reserved[] must stay at offset 62");
/* tlv_len (offset 62) and tlv_hash (offset 64) occupy the 30 bytes that sat
* between sig_len and signature[] before the TLV area was authenticated; both
* offsets are pinned with the rest of the TLV binding in the first block above,
* so they are not repeated here. */

/* Field widths. An offset assert cannot see a field growing into padding that
* happens to keep every later offset -- reserved[] absorbs exactly that. */
* happens to keep every later offset -- the tlv_len/tlv_hash pair absorbs
* exactly that gap, so pin each field's width as well. */
EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->hash) == 32,
"hash[] is 32 bytes on the wire");
EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->reserved) == 30,
"reserved[] is 30 bytes on the wire");
EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->tlv_len) == 2,
"tlv_len is 2 bytes on the wire");
EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->tlv_hash) ==
EOS_IMG_TLV_HASH_LEN,
"tlv_hash[] is 28 bytes on the wire");
EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->signature) == 64,
"signature[] is 64 bytes on the wire");

Expand Down
52 changes: 37 additions & 15 deletions tests/unit/test_ed25519.c
Original file line number Diff line number Diff line change
Expand Up @@ -260,20 +260,40 @@ TEST(test_ed25519_zero_signature_rejected)
ASSERT(eos_ed25519_verify(sig, pk, msg, 1) != EOS_OK);
}

TEST(test_ed25519_identity_key_forgery_rejected)
{
/* The identity point has compressed encoding 01 00...00. With both the
* public key and R set to the identity and S set to zero, the verification
* equation is true for every message unless low-order keys are rejected. */
uint8_t identity_pub[32] = {1};
uint8_t identity_sig[64] = {1};
const uint8_t msg[] = "untrusted firmware";

ASSERT(eos_ed25519_verify(identity_sig, identity_pub,
msg, sizeof(msg) - 1) != EOS_OK);
}
/* The eight low-order point encodings. A public key must be none of them, and
* so must the R half of a signature. */
/* Messages the low-order sweeps run each candidate pair against. Which pair
* forges depends on k = SHA-512(R || A || M), so the message matters: the
* count below is for exactly this list. */
static const char *const messages[] = {
"untrusted firmware", "malicious package payload",
"AB", "ABC", "boot this image", "", "A", "0123456789",
};

/* ---- SHA-512, the hash Ed25519 is defined over (FIPS 180-4) ---- */
static const uint8_t k_low_order[8][32] = {
/* y = 0, order 4 */
{0},
/* the identity, order 1 */
{1},
/* order 8 */
{0x26,0xe8,0x95,0x8f,0xc2,0xb2,0x27,0xb0,0x45,0xc3,0xf4,0x89,0xf2,0xef,0x98,0xf0,
0xd5,0xdf,0xac,0x05,0xd3,0xc6,0x33,0x39,0xb1,0x38,0x02,0x88,0x6d,0x53,0xfc,0x05},
/* order 8 */
{0xc7,0x17,0x6a,0x70,0x3d,0x4d,0xd8,0x4f,0xba,0x3c,0x0b,0x76,0x0d,0x10,0x67,0x0f,
0x2a,0x20,0x53,0xfa,0x2c,0x39,0xcc,0xc6,0x4e,0xc7,0xfd,0x77,0x92,0xac,0x03,0x7a},
/* p - 1 */
{0xec,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f},
/* p, which reduces to y = 0 */
{0xed,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f},
/* p + 1, which reduces to the identity */
{0xee,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f},
/* non-canonical, above p */
{0xd9,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff},
};

TEST(test_ed25519_low_order_R_with_a_valid_key_is_not_a_forgery)
{
Expand Down Expand Up @@ -307,6 +327,8 @@ TEST(test_ed25519_low_order_R_with_a_valid_key_is_not_a_forgery)
}
}

/* ---- SHA-512, the hash Ed25519 is defined over (FIPS 180-4) ---- */

TEST(test_sha512_known_answers)
{
struct { const char *msg; const char *digest; } kat[] = {
Expand Down Expand Up @@ -369,11 +391,11 @@ int main(void)
run_test_ed25519_low_order_keys_rejected();
run_test_ed25519_zero_pubkey_rejected();
run_test_ed25519_zero_signature_rejected();
run_test_ed25519_identity_key_forgery_rejected();
run_test_ed25519_low_order_R_with_a_valid_key_is_not_a_forgery();
run_test_sha512_known_answers();
run_test_sha512_streaming_matches_one_shot();

tests_run = 11;
tests_run = 13;
printf("\n%d/%d tests passed\n", tests_passed, tests_run);
return (tests_passed == tests_run) ? 0 : 1;
}