Skip to content

crypto: refuse stub RSA/ECC signing unless EOS_ALLOW_STUB_CRYPTO - #84

Open
JoaoMorais03 wants to merge 1 commit into
embeddedos-org:masterfrom
JoaoMorais03:fix/stub-crypto-sign-fail-closed
Open

crypto: refuse stub RSA/ECC signing unless EOS_ALLOW_STUB_CRYPTO#84
JoaoMorais03 wants to merge 1 commit into
embeddedos-org:masterfrom
JoaoMorais03:fix/stub-crypto-sign-fail-closed

Conversation

@JoaoMorais03

Copy link
Copy Markdown

Contribution after reviewing the project: RSA/ECC verify already fail-closed unless EOS_ALLOW_STUB_CRYPTO is defined, but sign still minted fake signatures in production builds.

Issue

services/crypto/src/rsa_ecc_sha512.c documents that the RSA/ECC routines are stubs, not PKCS#1/ECDSA. After the earlier verify-side fix, eos_rsa_verify_sha256 / eos_ecc_verify refuse to run without the opt-in. eos_rsa_sign_sha256 still wrote a PKCS#1-looking blob ending in the hash and returned 0; eos_ecc_sign still wrote a fake 64-byte r||s and returned 0. A signing pipeline could look like it worked while producing forgeries.

tests/test_crypto_failclosed.c already asserts that signing must refuse, and is listed among unregistered tests. It was not in tests/CMakeLists.txt.

Approach

Call EOS_STUB_CRYPTO_REFUSE from both sign functions after the existing NULL checks, matching verify. Register test_crypto_failclosed against eos_crypto without EOS_ALLOW_STUB_CRYPTO so CI covers the production path. Existing test_crypto_rsa / test_crypto_ecc only (void) the sign/verify status, so they still pass.

Testing

The fail-closed suite checks:

  • the old RSA trailing-hash forgery is rejected
  • RSA/ECC verify never succeed without the opt-in
  • RSA/ECC sign refuse to mint fake signatures

I did not run CTest here (no local checkout). test_crypto_rsa.c / test_crypto_ecc.c were read to confirm they do not assert sign success.

Limitations

Does not implement real RSA/ECDSA. Tests that want the old stub behavior must define EOS_ALLOW_STUB_CRYPTO (none of the currently registered tests do). Master currently has duplicate test_crypto_aes / test_crypto_sha512 CMake targets, so a full test configure still fails until that is fixed independently; this PR only adds test_crypto_failclosed.

Verify already fail-closed; sign still minted fake signatures in production.
Register the existing test_crypto_failclosed suite so that cannot regress.

Signed-off-by: João Morais <118842104+JoaoMorais03@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 29, 2026 16:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The test CMakeLists currently defines duplicate targets (breaking test configuration) and the RSA signing stub still risks unsafe length underflow/overflow in EOS_ALLOW_STUB_CRYPTO builds.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR hardens the stub RSA/ECC implementation in eos_crypto by making sign fail-closed (matching existing verify behavior) unless EOS_ALLOW_STUB_CRYPTO is explicitly enabled, and registers a regression test intended to prevent “fake signature” behavior from silently returning success in production builds.

Changes:

  • Add EOS_STUB_CRYPTO_REFUSE(...) guards to eos_rsa_sign_sha256() and eos_ecc_sign() so stub signing refuses to run without EOS_ALLOW_STUB_CRYPTO.
  • Register test_crypto_failclosed in the test CMake list to exercise the fail-closed production path.
File summaries
File Description
tests/CMakeLists.txt Registers test_crypto_failclosed (but currently contains duplicate AES/SHA512 targets that will prevent CMake from configuring tests).
services/crypto/src/rsa_ecc_sha512.c Makes RSA/ECC signing match verify’s fail-closed behavior; small stub-safety/cleanup issues remain.
Review details

Suppressed comments (1)

services/crypto/src/rsa_ecc_sha512.c:58

  • eos_rsa_sign_sha256() computes len from key_bits and then uses len - 35/len - 33 later. If a caller sets an unexpectedly small key_bits value (or an oversized one), this can underflow/overflow and cause out-of-bounds writes to sig in EOS_ALLOW_STUB_CRYPTO builds.
    EOS_STUB_CRYPTO_REFUSE("eos_rsa_sign_sha256");

    /* Stub: produce a deterministic fake signature for testing.
     * Replace with real PKCS#1 v1.5 or PSS signing. */
    size_t len = (size_t)(key->key_bits / 8);
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/CMakeLists.txt
Comment on lines 72 to +76
add_executable(test_crypto_sha512 test_crypto_sha512.c)
target_link_libraries(test_crypto_sha512 PRIVATE eos_crypto)
add_test(NAME test_crypto_sha512 COMMAND test_crypto_sha512)

# --- test_crypto_failclosed: unimplemented RSA/ECC stubs refuse to run ---
Comment on lines +85 to 88
EOS_STUB_CRYPTO_REFUSE("eos_ecc_sign");
/* Stub: produce a fake 64-byte signature (r||s) */
size_t len = 64;
if (hash_len < 32) len = 64;

@srpatcha srpatcha left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified — this closes a real asymmetry

Checked origin/master before reviewing. The guards are on verify only:

50:int eos_rsa_sign_sha256(...)                      ← unguarded
67:int eos_rsa_verify_sha256(...)
69:    EOS_STUB_CRYPTO_REFUSE("eos_rsa_verify_sha256")
80:int eos_ecc_sign(...)                             ← unguarded
92:int eos_ecc_verify(...)
97:    EOS_STUB_CRYPTO_REFUSE("eos_ecc_verify")

So a build that correctly refuses to verify with stub crypto would still happily sign with it. Your reasoning for why that matters is the right one: a stub that mints a PKCS#1-looking blob or a fake r||s lets a signing pipeline appear to work end to end. The artifacts it produces are not signed by anything, and the failure surfaces at some later point — a real verifier rejecting them, or worse, a stub verifier accepting them — a long way from the code that caused it.

Making sign and verify symmetric is the correct fix, and reusing EOS_ALLOW_STUB_CRYPTO rather than inventing a second opt-in keeps one switch for one concept.

The test is the durable part

test_crypto_failclosed deliberately does not define EOS_ALLOW_STUB_CRYPTO, so it exercises the production path rather than the test path. That is the distinction that makes it a guard: the existing crypto tests all opt in, so none of them would notice the refusal being removed.

The note to replace it with known-answer tests against published vectors when real verification lands is the right instruction to leave behind — a fail-closed test becomes actively misleading once the function works.

One merge-order note

This conflicts with #82, which repairs master. Both touch tests/CMakeLists.txt: your new test_crypto_failclosed registration sits inside the duplicated test_crypto_aes / test_crypto_sha512 block that #82 removes — the duplicate is why master currently fails to configure at all.

The resolution is to keep your test_crypto_failclosed lines and drop the duplicated aes/sha512 ones, which are already registered earlier in the file. I did exactly that locally and it is clean.

Verification

Merged onto origin/master + #82, resolved as above: configure OK, 0 build errors, ctest 29/29. Not verified against master alone, because master does not currently configure.

srpatcha added a commit that referenced this pull request Aug 31, 2026
ed25519_verify() decoded the public key and never checked which subgroup it
was in. Ed25519 has eight low-order points, and for any of them every term
of the verification equation collapses regardless of the message, so a
signature of all zeros verifies against arbitrary content.

This was not hypothetical. eos_pkg.c:94 ships

    static const uint8_t eos_pkg_public_key[32] = {0};

and the all-zero encoding is one of the eight. Measured against the built
library, before this change:

    all-zero key: 2 of 256 arbitrary signatures ACCEPTED

Package installation authenticated nothing. The SHA-256 check two lines
above is no help — it compares a hash of the payload against a field in the
same attacker-supplied file, which detects corruption, not origin. See #98.

The check multiplies the key by L and requires the identity, rather than
comparing against a table of the eight encodings. Both are correct; this one
cannot be wrong in a way that silently rejects valid keys because a constant
was transcribed incorrectly.

Two things this took to get right, both found by testing rather than
reasoning:

The subgroup test alone is insufficient. The identity has order 1, which
divides L, so [L]identity = identity and it passes. The first version of
this check still accepted an identity key. It is now rejected explicitly,
which is why the helper has two parts.

The regression test's first version passed against the unfixed code. The
all-zero key is order 4, so with S = 0 the equation reduces to [k](-A) and
whether that lands on R depends on k mod 4 — on the message. The forgery
succeeds for roughly one message in four, and "untrusted firmware" happened
to be one of the three that do not verify. It looked like a regression test
and was not one. It now sweeps messages verified to be accepted by the
unfixed implementation.

    unfixed   test_all_zero_key_is_rejected  [FAIL] at line 112
    fixed     5/5 tests passed

The suite covers all 64 combinations of the eight low-order encodings as key
and as R, plus RFC 8032 vector 1 — without which a check that rejected every
key would also pass.

    ctest   28/29 -> 29/29 passed, 0 build errors

This does not make package signing work. A correct verifier with an all-zero
trust anchor now rejects everything instead of accepting everything, which is
the safer failure and still not a functioning signature check. #98 covers
supplying a real key, and whether eos_pkg should refuse to run at all while
its anchor is unset — the shape #84 uses for stub RSA/ECC.

Refs #98

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants