diff --git a/core/ed25519_verify.c b/core/ed25519_verify.c index 4075666..78a80ad 100644 --- a/core/ed25519_verify.c +++ b/core/ed25519_verify.c @@ -277,6 +277,16 @@ static void scalarmult(gf r[4], gf q[4], const uint8_t *s) } } +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); +} + /* The identity encodes as y = 1 with the sign bit clear. Takes a point rather * than an encoding so both callers below can pass one directly. */ static int point_is_identity(gf p[4]) @@ -311,13 +321,9 @@ static void scalarbase(gf r[4], const uint8_t *s) * for every point whose order divides L -- including the identity itself, * whose order is 1 -- so the identity must also be excluded explicitly. * - * The scalar is derived from ORDER_L rather than written out a second time, - * so there is no separate constant to transcribe wrongly: a mistyped L would - * reject valid keys, and only in the field. - * - * The key arrives negated from unpackneg(). [L](-A) = -[L]A and the identity - * is its own negation, so neither condition is affected by the sign. - */ + * 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]; diff --git a/include/eos_image.h b/include/eos_image.h index 24cbb16..4974d62 100644 --- a/include/eos_image.h +++ b/include/eos_image.h @@ -133,18 +133,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"); -/* tlv_len and tlv_hash are asserted above, where #93 introduced them; the - * 30 bytes they occupy are the ones this block used to pin as reserved[]. */ +/* offsetof(tlv_len) == 62 and offsetof(tlv_hash) == 64 are already asserted + * above, next to EOS_IMG_TLV_HASH_LEN -- this used to be a single reserved[30] + * field before #93 split it into tlv_len + tlv_hash for anti-rollback, and + * this block was never updated to match, leaving `reserved` referring to a + * struct member that no longer exists (a hard compile error, not just a stale + * comment: eos_image_header_t has never had every field pinned since #93). + */ /* Field widths. An offset assert cannot see a field growing into padding that - * happens to keep every later offset -- the 30 bytes at 62 absorb exactly - * that, which is why both halves of that span carry a width assert. */ + * happens to keep every later offset -- tlv_hash[] absorbs exactly that. */ 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)->tlv_len) == 2, - "tlv_len is 2 bytes on the wire"); -EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->tlv_hash) == 28, - "tlv_hash is 28 bytes on the wire"); +EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->tlv_hash) == + EOS_IMG_TLV_HASH_LEN, + "tlv_hash[] is EOS_IMG_TLV_HASH_LEN bytes on the wire"); EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->signature) == 64, "signature[] is 64 bytes on the wire"); diff --git a/tests/unit/test_ed25519.c b/tests/unit/test_ed25519.c index 9ea837c..8504ae7 100644 --- a/tests/unit/test_ed25519.c +++ b/tests/unit/test_ed25519.c @@ -90,6 +90,46 @@ static const struct rfc_vector k_vectors[] = { #define N_VECTORS (sizeof(k_vectors) / sizeof(k_vectors[0])) +/* All eight compressed encodings of edwards25519's order-8 torsion subgroup + * (the cyclic group for any order-8 point G, i.e. {1G, 2G, ..., 8G=O}). + * Order 8/gcd(k,8) for kG: orders present are 1 (identity), 2, 4, 4, 8, 8, 8, 8. + * + * Not hand-transcribed: derived by decoding an order-8 generator from this + * file's own unpackneg()/point_add()/point_is_identity(), then enumerating + * 1G..8G with point_add() and re-encoding with point_pack() -- so their + * correctness rests on the same curve arithmetic this file already uses for + * real verification, not on a separately-copied constant that could carry a + * transcription error. Cross-checked: point k and point (8-k) differ only in + * the sign bit (byte 31), as required since (8-k)G = -(kG); the order-2 + * element (k=4) is its own negation, as required since 2P = O implies P = -P. + */ +static const uint8_t k_low_order[8][32] = { + {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,0x85}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80}, + {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,0xfa}, + {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}, + {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}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {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}, + {0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, +}; + +/* A handful of distinct messages, so a low-order-R rejection isn't pinned + * against only one k = SHA-512(R || A || M) value. */ +static const char *messages[] = { + "", + "a", + "untrusted firmware", +}; + /* ---- positive tests: a conforming signature MUST be accepted ---- */ TEST(test_ed25519_rfc8032_vectors_accepted) @@ -337,6 +377,8 @@ TEST(test_ed25519_zero_signature_rejected) ASSERT(eos_ed25519_verify(sig, pk, msg, 1) != EOS_OK); } +/* ---- SHA-512, the hash Ed25519 is defined over (FIPS 180-4) ---- */ + TEST(test_ed25519_low_order_R_with_a_valid_key_is_not_a_forgery) { /* The subgroup check guards the public key, not R, and that is @@ -433,9 +475,11 @@ int main(void) run_test_ed25519_low_order_R_with_a_valid_key_is_not_a_forgery(); run_test_ed25519_zero_pubkey_rejected(); run_test_ed25519_zero_signature_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 = 13; printf("\n%d/%d tests passed\n", tests_passed, tests_run); return (tests_passed == tests_run) ? 0 : 1; }