From 79cd8deafbfb1a5bed740519b2f8ad1003dd8a15 Mon Sep 17 00:00:00 2001 From: muhammadburhandevv-hub Date: Fri, 28 Aug 2026 14:42:39 +0500 Subject: [PATCH] fix(core): reject low-order keys and guard source registration --- CMakeLists.txt | 22 +++++++------- core/ed25519_verify.c | 30 +++++++++++++++++++ tests/unit/test_cmake_core_sources.py | 43 +++++++++++++++++++++++++++ tests/unit/test_ed25519.c | 16 +++++++++- 4 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 tests/unit/test_cmake_core_sources.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 943bce7..7fa5aeb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,17 +104,17 @@ add_library(eboot_core STATIC core/mpu_boot.c core/ecc_scrub.c core/bmc_handoff.c - core/os_adapter.c - core/ed25519_verify.c - core/sha512.c - core/rollback.c - core/keystore.c - core/debug_lock.c - core/fw_decrypt.c - core/image_tlv.c - core/sha512.c - core/rollback.c -) + core/os_adapter.c + core/ed25519_verify.c + core/sha512.c + core/secure_boot.c + core/fdt_loader.c + core/rollback.c + core/keystore.c + core/debug_lock.c + core/fw_decrypt.c + core/image_tlv.c +) target_include_directories(eboot_core PUBLIC ${EBLDR_INCLUDE_DIR}) target_link_libraries(eboot_core PUBLIC eboot_hal) diff --git a/core/ed25519_verify.c b/core/ed25519_verify.c index 022b679..0367fa1 100644 --- a/core/ed25519_verify.c +++ b/core/ed25519_verify.c @@ -286,6 +286,34 @@ static void scalarbase(gf r[4], const uint8_t *s) 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]; + gf q[4], multiple[4]; + + for (int i = 0; i < 32; i++) + order_l[i] = (uint8_t)ORDER_L[i]; + for (int i = 0; i < 4; i++) + fe_copy16(q[i], public_key[i]); + + scalarmult(multiple, q, order_l); + return point_is_identity(multiple) && !point_is_identity(public_key); +} + /* Decode a compressed point into -P (the negation is what verification wants). */ static int unpackneg(gf r[4], const uint8_t p[32]) { @@ -416,6 +444,8 @@ int eos_ed25519_verify(const uint8_t signature[64], gf A[4]; if (unpackneg(A, public_key) != EOS_OK) return EOS_ERR_SIGNATURE; + if (!public_key_is_valid_subgroup(A)) + return EOS_ERR_SIGNATURE; /* k = SHA-512(R || A || M) mod L */ eos_sha512_ctx_t ctx; diff --git a/tests/unit/test_cmake_core_sources.py b/tests/unit/test_cmake_core_sources.py new file mode 100644 index 0000000..73a86e2 --- /dev/null +++ b/tests/unit/test_cmake_core_sources.py @@ -0,0 +1,43 @@ +"""Ensure every core implementation is built exactly once.""" + +import re +import unittest +from collections import Counter +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +CMAKELISTS = ROOT / "CMakeLists.txt" +CORE_DIR = ROOT / "core" + + +def _registered_core_sources(): + text = CMAKELISTS.read_text(encoding="utf-8") + match = re.search(r"add_library\(eboot_core STATIC(?P.*?)\n\)", text, re.DOTALL) + if match is None: + raise AssertionError("eboot_core source list was not found in CMakeLists.txt") + return re.findall(r"core/[A-Za-z0-9_]+\.c", match.group("body")) + + +class CoreSourceRegistrationTests(unittest.TestCase): + def test_every_core_source_is_built(self): + expected = { + path.relative_to(ROOT).as_posix() + for path in CORE_DIR.glob("*.c") + } + registered = set(_registered_core_sources()) + + self.assertEqual( + expected, + registered, + "eboot_core must contain every core/*.c implementation", + ) + + def test_core_sources_are_registered_once(self): + counts = Counter(_registered_core_sources()) + duplicates = sorted(source for source, count in counts.items() if count > 1) + self.assertFalse(duplicates, f"duplicate eboot_core sources: {duplicates}") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/test_ed25519.c b/tests/unit/test_ed25519.c index f9933fe..a794503 100644 --- a/tests/unit/test_ed25519.c +++ b/tests/unit/test_ed25519.c @@ -219,6 +219,19 @@ 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); +} + /* ---- SHA-512, the hash Ed25519 is defined over (FIPS 180-4) ---- */ TEST(test_sha512_known_answers) @@ -281,10 +294,11 @@ int main(void) run_test_ed25519_null_args(); run_test_ed25519_zero_pubkey_rejected(); run_test_ed25519_zero_signature_rejected(); + run_test_ed25519_identity_key_forgery_rejected(); run_test_sha512_known_answers(); run_test_sha512_streaming_matches_one_shot(); - tests_run = 10; + tests_run = 11; printf("\n%d/%d tests passed\n", tests_passed, tests_run); return (tests_passed == tests_run) ? 0 : 1; }