Skip to content

fix(pkg): give eos_pkg a real trust anchor, or refuse to verify - #120

Merged
srpatcha merged 6 commits into
embeddedos-org:masterfrom
Kartikey1306:fix/pkg-trust-anchor
Sep 1, 2026
Merged

srpatcha merged 6 commits into
embeddedos-org:masterfrom
Kartikey1306:fix/pkg-trust-anchor

Conversation

@Kartikey1306

Copy link
Copy Markdown
Contributor

Closes the second half of #98 — the trust anchor, which @srpatcha called
"still the more important half" after #99 landed the verifier half.

The state #99 left behind

#99 stopped services/crypto accepting low-order keys, which closed the half
where eos_pkg accepted every package. The anchor itself is untouched on
master:

services/pkg/eos_pkg.c:94
static const uint8_t eos_pkg_public_key[32] = {0};

So eos_pkg went from accepting everything to rejecting everything. Verified —
a package whose payload is RFC 8032 §7.1 Test 2's message, carrying Test 2's real
signature, built against master unmodified:

== the package is genuinely signed ==
ed25519_verify with the correct key : ACCEPT
ed25519_verify with the zero anchor : reject

== what eos_pkg_verify() reports, with the anchor it ships ==
eos-pkg: signature verification failed
eos_pkg_verify rc = -1

The package is fine. The key was never provisioned. The message says the
opposite, so the one diagnostic an operator gets points away from the cause.

Why nobody could have caught it

Verified — services/pkg/eos_pkg.c appears in no CMakeLists.txt:

$ grep -rn "services/pkg\|eos_pkg.c" --include=CMakeLists.txt .
(no matches)

Nothing compiled the file. tests/test_package.c tests pkg/src/package.c —
the build-recipe registry, a different module that happens to own the target
name eos_pkg. So the anchor was unreachable by any test, which is the reason
it survived four rounds of review.

It compiles clean as it stands (-Wall -Wextra -Wpedantic, 0 warnings); it was
simply never wired in. It is now the eos_eapp library.

The fix

The key comes from the platform, in the shape this repository already uses:

source precedent
eos_pkg_set_trust_anchor() — where a keystore or provisioning step installs it eos_ota_set_authenticator() (#75)
EOS_PKG_TRUST_ANCHOR_HEX — 64 hex characters from the build —
neither → refuse, and say why EOS_ALLOW_STUB_CRYPTO (#84), EOS_ALLOW_UNSIGNED_OTA (#75)

EOS_ALLOW_UNSIGNED_PKG is the opt-in, for tests and bring-up.

An anchor is validated when it is installed, not once per package:

eos-pkg: refusing a trust anchor that is not a prime-order Ed25519 point;
         such a key cannot authenticate anything

That needed #99's checks to be reachable from outside ed25519_verify(), so
they are exported as ed25519_public_key_is_usable() rather than duplicated —
one implementation of the rule, one set of constants.

The three failures are reported apart because they call for different actions:
fix the provisioning, fix the build, or reject the package.

Tests fail on the unfixed code

tests/test_pkg_trust_anchor.c, 8 tests. Verified they discriminate — built
against master's eos_pkg.c with only the new symbols shimmed in so it links:

  test_no_anchor_refuses_a_genuine_package                 [PASS]
  test_low_order_keys_are_refused_as_anchors               [FAIL]

and with the anchor-validation half also shimmed in, so the run reaches the
verification path:

  test_no_anchor_refuses_a_genuine_package                 [PASS]
  test_low_order_keys_are_refused_as_anchors               [PASS]
  test_a_real_key_is_accepted_as_an_anchor                 [PASS]
  test_a_genuine_package_verifies_under_its_own_key        [FAIL]

Both halves of the change are load-bearing and independently proven.

Coverage: all eight low-order encodings refused as anchors — the all-zero value
this file shipped is only one of them; a real key accepted; a genuinely signed
package accepted under its own key, so a verifier that rejects everything
does not pass; a different real key rejected; a tampered payload rejected with
the header hash recomputed over it
, so the SHA-256 check passes and only the
signature can reject it; and a rejected anchor leaving the one in force intact,
so a failed provisioning attempt cannot disarm a working installation.

Verification

item result
cmake -B build/host -DEOS_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release PASS
test_pkg_trust_anchor PASS 8/8
eos_pkg.c under -Wall -Wextra -Wpedantic PASS, 0 warnings
ctest, this branch (macOS) 34/35
ctest, master same machine, same flags 33/34

Verified — the single failure is test_net, identical on both sides and
pre-existing. It is macOS-only: tests/test_net.c calls assert() without
#include <assert.h>, which glibc admits transitively and clang does not. Not
touched here; worth its own one-line fix.

Left open deliberately

Two independent breakages, both in "CI — eos" on master (9861a3b).

net/src/net_posix.c did not compile in the default configuration.
net.h wraps its declarations in `#if EOS_ENABLE_NET`, and nothing sets
that flag without a product profile or a test build. CMake, however,
adds net_posix.c to eos_net whenever the build is not cross-compiling —
the flag never enters that decision. So the header expanded to nothing
while the file still defined the API against it:

    net/src/net_posix.c:43:30: error: unknown type name 'eos_net_addr_t'

net.c already guards its body the same way and survives; the POSIX
mapping was the half that did not. Guarded so the two translation units
appear and disappear together.

This is the exact gap the "Default configuration" job was added to
catch, and it caught it.

tests/test_net.c failed to link:

    undefined reference to `assert'

The file defines CHECK() precisely because CI builds Release, NDEBUG
deletes assert() along with the expression inside it, and half these
checks wrap the call under test — the comment above the macro says so.
Ten call sites in the POSIX section still used bare assert(), including
assert(eos_net_connect(...) == 0), the exact pattern named there. They
were invisible until embeddedos-org#104 added -UNDEBUG and stopped assert() being a
no-op; the file never includes <assert.h>, so it resolved to an implicit
function and failed at link. Converted to CHECK(), which the file
already establishes as the right macro here.

Verified locally on macOS, all three configurations CI builds:

  cmake -B build/host -DCMAKE_BUILD_TYPE=Release          builds (fails on master)
  -DEOS_PRODUCT=medical / aerospace                       both build
  -DEOS_BUILD_TESTS=ON -DEOS_PRODUCT=vbox_test + ctest    34/34 passed

Negative control: the same default-configuration build against
unpatched origin/master reproduces the unknown-type errors verbatim.
@Kartikey1306

Copy link
Copy Markdown
Contributor Author

Rebased onto #114 (518eccf), which carries #113 and the assert.h fix.

master is currently red for two reasons that have nothing to do with this
change, and the first build of this branch inherited both:

  • Default configuration (no tests, no product) — net/src/net_posix.c has
    zero EOS_ENABLE_NET occurrences on master, so its body compiles
    unconditionally. Fixed by fix(net): guard net_posix.c, which broke every non-test build #113.
  • Build & Test (Linux x86_64) — tests/test_net fails to link; test_net.c
    calls assert() with no #include <assert.h>, which glibc used to admit
    transitively. Fixed by 9d2f7ea.

Both are in the stack now, so this branch builds on its own.

Local after the rebase: 39/39 ctest, on macOS, Release.

The stack fast-forwards from master
(git merge-base --is-ancestor origin/master HEAD is true), so #113 → #114 →
this can merge in order, or the tip alone takes all three.

One thing this rebase made tidier than I expected: #114 is "test suites that
existed but were never compiled" and this is the same defect one level up — a
whole service that no CMakeLists.txt built. The all-zero trust anchor is
what was hiding inside it.

Comment thread tests/test_pkg_trust_anchor.c Fixed
Kartikey1306 and others added 4 commits September 1, 2026 21:05
…rd it

Nine test_*.c files in tests/ are named by no add_executable(), so they are
compiled by nothing. Nothing reports that: the file stays in the tree, ctest
counts one fewer test, and the run is green.

test_mpu_validate.c is the one that matters. It landed with the MPU fix it
guards (embeddedos-org#109) and a later merge kept the file while dropping the target that
built it, so its six cases -- regions the MPU cannot program, and the
divide-before-check that preceded them -- have not run since.

Four of the nine build and pass as they are:

  test_mpu_validate   6 cases, registration restored
  test_hal_stm32f4    21 cases; it needed only the board header directory
                      on the include path, which is the whole reason it
                      never ran
  test_e2e            kernel boot-to-task path
  test_net_mock       eNet socket API against a mock transport

ctest goes from 34 to 38.

The other five stay out, each for a reason recorded next to its name in the
new guard: one hangs because it asserts stub semantics against the real
POSIX implementation; one cannot link because backends/ is in no
add_subdirectory(); and three would pass without testing anything --
test_profiles reports 0/0 because its directory allowlist admits only
"products", which holds no *.yaml; test_performance_benchmarks asserts a
wall-clock threshold; test_emulation_simulation asserts a literal against
itself one line after assigning it.

tests/unit/test_cmake_test_registration.py makes the omission fail. It
parses tests/CMakeLists.txt statically -- no cmake, no compiler -- and
asserts every suite is either built or listed with a reason, that every
built target is registered with ctest, that the exclusion list has no stale
or non-existent entries, and, by name, that test_mpu_validate is built.
Dropping that registration again fails two of the five.

The eBoot repo has carried the same guard since a merge dropped
test_fw_transport there; this is the same file adapted to this layout.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
services/pkg/eos_pkg.c checked package signatures against

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

embeddedos-org#99 stopped that key accepting every package. It did not make package
signing work: the anchor is still all zeros, so eos_pkg now rejects every
package, including correctly signed ones, and reports "signature
verification failed" while doing it -- blaming the package for a key that
was never provisioned.

The key comes from the platform now. eos_pkg_set_trust_anchor() is the
seam a keystore or a provisioning step fills, EOS_PKG_TRUST_ANCHOR_HEX is
the build-time form, and with neither configured verification refuses and
names the reason. EOS_ALLOW_UNSIGNED_PKG is the opt-in for tests and
bring-up, matching EOS_ALLOW_STUB_CRYPTO in services/crypto and
EOS_ALLOW_UNSIGNED_OTA in services/ota.

An anchor is validated when it is installed rather than once per package,
so an unusable key is reported as an unusable key. That needed embeddedos-org#99's
checks to be reachable, so ed25519_public_key_is_usable() exports them
rather than duplicating the constants.

services/pkg was in no CMakeLists.txt, so nothing compiled this file and
no test could reach it -- which is why the all-zero anchor survived. It
is now the eos_eapp library (pkg/ already owns the name eos_pkg for the
build-recipe registry).

Closes the second half of embeddedos-org#98.
Adding services/pkg to the build put it in the ARM Cortex-M4 leg too, where
it does not belong and does not compile: eos_pkg.c lists directories, spawns
processes and waits on them, so it needs <dirent.h>, <unistd.h> and
<sys/wait.h>. Bare-metal newlib turns the first of those into a hard #error.

Gated on CMAKE_CROSSCOMPILING, which is the switch net_posix.c already uses
for the same reason, and for the same stated reason: EOS_PLATFORM defaults to
"linux" and a cross toolchain file does not change it, so that condition is
true even for a Cortex-M build.

Verified with a toolchain file that sets CMAKE_SYSTEM_NAME while keeping the
host compiler, so the CMake logic can be checked without arm-none-eabi
installed: eos_eapp and test_pkg_trust_anchor are not generated under it, and
both are present in the host build. Host ctest 39/39.
CodeQL flags this PR with one new high-severity alert:

    tests/test_pkg_trust_anchor.c:128
    File created without restricting permissions
    A file may be created here with mode 0666, which would make it
    world-writable.

fopen(path, "wb") creates with 0666 & ~umask, so on a permissive umask the
fixture is writable by anyone on the machine. These fixtures are the signed
packages the verifier is about to trust, so a world-writable one lets
anything on the box rewrite the payload between the write here and the read
under test -- which is the very substitution the suite exists to detect.

Create through open(O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR) and fdopen
the descriptor. Verified the call yields 0600.

39/39.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@srpatcha
srpatcha merged commit 769191a into embeddedos-org:master Sep 1, 2026
21 of 23 checks passed
Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 3, 2026
…ry directly

Answers the review on embeddedos-org#122. The malleability fix itself was found correct;
these are the findings around it.

Finding 1 (High) -- the digest was printed and never compared, so the shared
contract could not fail. EOS_ED25519_CONTRACT_DIGEST came from the header this
repo's own generator had just written, a hash taken over its own output, so
editing the generator here and not in eBoot produced a self-consistent corpus
with a new digest, a green test, and a divergence visible only to a human
reading two CI logs. Now pinned as a literal in committed source, together
with the vector count and accept count, which are generated for the same
reason and were equally unfalsifiable. The eBoot twin (embeddedos-org#89) pins the identical
value.

Finding 4 (Medium) -- sc_is_canonical()'s accept side had no committed test.
Through the public API the boundary is unreachable: S == L and a failed group
equation are both simply 0, so an off-by-one refusing a valid S = L - 1 would
satisfy every vector in the corpus and reject real signatures only in the
field. It is a pure function of 32 bytes, so tests/test_ed25519_canonical_s.c
tests it directly -- 0, 1, L-1, L, L+1, 2^252, 2^252+2^251 and all-ones -- with
no signing key and no `cryptography` module. ed25519_verify.c is compiled a
second time with EOS_ED25519_TEST_HOOKS to give the function external linkage;
the shipped build is unchanged.

  This is the case the finding was about, reproduced. With `S == L` accepted:
      test_ed25519_canonical_s   [FAIL] at the S = L case
      test_ed25519_contract      [PASS] all 76 vectors
  The corpus cannot see it. The direct test can.

  Also corrected while writing it: 2^252 is BELOW L (L = 2^252 + 2.77e37), so
  it is in range. The value that is both non-canonical and passed by ref10's
  `signature[63] & 224` test is 2^252 + 2^251 -- 0x18 & 224 == 0. Both are now
  asserted, the second alongside the mask check that shows why the partial
  test misses it.

Finding 2 (Medium) -- tests/vectors/ed25519_contract_vectors.h is now
committed rather than generated. CMAKE_CURRENT_SOURCE_DIR preceded
CMAKE_CURRENT_BINARY_DIR on the include path, so a stale source-tree copy
would silently win over the generated one -- and the PR body's own
re-derivation command wrote to exactly that path. Committing removes the
shadow, makes the corpus auditable from a tag, and drops
find_package(Python3 ... REQUIRED) from a pure-C suite.

NOT fixed here, and stated rather than left implied: finding 3, the corpus
omitting the y >= p (non-canonical public key) family. That is the class most
likely to diverge between the two verifiers and it is worth adding -- but
adding vectors changes the digest, which both repos now pin, so it has to land
in eos and eBoot together in one coordinated change. That is the mechanism
working as intended, and it is a follow-up rather than something to slip into
this PR.

Verified:
  ctest                          41/41 PASS
  test_ed25519_contract          76 vectors, 3 accept, 73 refuse
  test_ed25519_canonical_s       PASS
  rebased onto master; conflicts in tests/CMakeLists.txt and
  services/crypto/src/ed25519_verify.c resolved additively -- master's
  ed25519_public_key_is_usable() (embeddedos-org#120) and this branch's sc_is_canonical()
  both kept.

Refs embeddedos-org#122
Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 3, 2026
…ry directly

Answers the review on embeddedos-org#122. The malleability fix itself was found correct;
these are the findings around it.

Finding 1 (High) -- the digest was printed and never compared, so the shared
contract could not fail. EOS_ED25519_CONTRACT_DIGEST came from the header this
repo's own generator had just written, a hash taken over its own output, so
editing the generator here and not in eBoot produced a self-consistent corpus
with a new digest, a green test, and a divergence visible only to a human
reading two CI logs. Now pinned as a literal in committed source, together
with the vector count and accept count, which are generated for the same
reason and were equally unfalsifiable. The eBoot twin (embeddedos-org#89) pins the identical
value.

Finding 4 (Medium) -- sc_is_canonical()'s accept side had no committed test.
Through the public API the boundary is unreachable: S == L and a failed group
equation are both simply 0, so an off-by-one refusing a valid S = L - 1 would
satisfy every vector in the corpus and reject real signatures only in the
field. It is a pure function of 32 bytes, so tests/test_ed25519_canonical_s.c
tests it directly -- 0, 1, L-1, L, L+1, 2^252, 2^252+2^251 and all-ones -- with
no signing key and no `cryptography` module. ed25519_verify.c is compiled a
second time with EOS_ED25519_TEST_HOOKS to give the function external linkage;
the shipped build is unchanged.

  This is the case the finding was about, reproduced. With `S == L` accepted:
      test_ed25519_canonical_s   [FAIL] at the S = L case
      test_ed25519_contract      [PASS] all 76 vectors
  The corpus cannot see it. The direct test can.

  Also corrected while writing it: 2^252 is BELOW L (L = 2^252 + 2.77e37), so
  it is in range. The value that is both non-canonical and passed by ref10's
  `signature[63] & 224` test is 2^252 + 2^251 -- 0x18 & 224 == 0. Both are now
  asserted, the second alongside the mask check that shows why the partial
  test misses it.

Finding 2 (Medium) -- tests/vectors/ed25519_contract_vectors.h is now
committed rather than generated. CMAKE_CURRENT_SOURCE_DIR preceded
CMAKE_CURRENT_BINARY_DIR on the include path, so a stale source-tree copy
would silently win over the generated one -- and the PR body's own
re-derivation command wrote to exactly that path. Committing removes the
shadow, makes the corpus auditable from a tag, and drops
find_package(Python3 ... REQUIRED) from a pure-C suite.

NOT fixed here, and stated rather than left implied: finding 3, the corpus
omitting the y >= p (non-canonical public key) family. That is the class most
likely to diverge between the two verifiers and it is worth adding -- but
adding vectors changes the digest, which both repos now pin, so it has to land
in eos and eBoot together in one coordinated change. That is the mechanism
working as intended, and it is a follow-up rather than something to slip into
this PR.

Verified:
  ctest                          41/41 PASS
  test_ed25519_contract          76 vectors, 3 accept, 73 refuse
  test_ed25519_canonical_s       PASS
  rebased onto master; conflicts in tests/CMakeLists.txt and
  services/crypto/src/ed25519_verify.c resolved additively -- master's
  ed25519_public_key_is_usable() (embeddedos-org#120) and this branch's sc_is_canonical()
  both kept.

Refs embeddedos-org#122
Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 3, 2026
Retitles and reduces this PR to what it still does, per the review.

Both repairs it was opened for have landed on master by other routes:
net/src/net_posix.c is byte-identical to origin/master, and master's default
configuration builds clean. Against current master the branch's only real
content was this one line -- and merging it as it stood would have *reverted*
646 lines that landed since, including embeddedos-org#120's pkg trust anchor,
tests/test_pkg_trust_anchor.c and tests/unit/test_cmake_test_registration.py.
Reset to master and re-applied the one surviving change rather than leave a
stale branch that still looks mergeable.

The include is genuinely unused: tests/test_net.c has zero assert() calls and
uses its own CHECK() macro at 22 call sites. Worth correcting the record, per
finding 2: embeddedos-org#124 added it one commit ago as "the missing assert.h include that
broke master", and the file had no assert() at embeddedos-org#124's parent either. What
fixed that link error was the assert() -> CHECK() conversion already on
master; the include was never load-bearing, so removing it changes nothing
except the next reader's understanding.

Stacked on embeddedos-org#127, which restores the test_crypto_ed25519_loworder registration
master is currently red on.

Verified:
  pytest tests/    15 passed
  ctest            39/39 PASS

Refs embeddedos-org#116, embeddedos-org#124
Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 3, 2026
… the guard checks

Reduces this PR to what it still contributes, and answers the review.

The four registrations it opened with have landed on master by other routes --
test_mpu_validate, test_e2e, test_hal_stm32f4 and the registration guard are
all there. Merging the branch as it stood would have reverted 505 lines that
landed since, including embeddedos-org#120's pkg trust anchor. Reset onto embeddedos-org#127 and kept only
what is still true.

Finding 1 (Medium) -- test_net_mock was registered with no
target_link_libraries and includes no EmbeddedOS header. Its mock_net_*
functions are called directly rather than standing in for eos_net_*, so no
change to net/ can make it fail: 614 lines asserting a mock against itself.
Registering it added a green ctest entry covering nothing, which is precisely
the failure mode the guard in this same commit exists to surface -- and the
guard's own NOT_BUILT policy is where it belongs. Listed there with the reason,
including what would make it worth building: driving the real eos_net_* API
against the mock transport, which is what its docblock already claims it does.

Finding 2 (Low) -- documented the guard's limit where the policy is defined.
It checks that every suite is built and registered with ctest. It does not
check that a suite reaches the code it is named after, and a file that links
nothing satisfies it completely. Worth stating next to NOT_BUILT, because that
is the list someone reaches for when the guard fires.

Findings 3 and 4 are moot at this head: the test_mpu_validate comment and
test_net_mock's vestigial assert.h include both belonged to hunks that are
either upstream or removed here.

Verified:
  pytest tests/     15 passed
  ctest             37/37 PASS (was 38 with test_net_mock, which tested nothing)

Refs embeddedos-org#114
Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 3, 2026
… the guard checks

Reduces this PR to what it still contributes, and answers the review.

The four registrations it opened with have landed on master by other routes --
test_mpu_validate, test_e2e, test_hal_stm32f4 and the registration guard are
all there. Merging the branch as it stood would have reverted 505 lines that
landed since, including embeddedos-org#120's pkg trust anchor. Reset onto embeddedos-org#127 and kept only
what is still true.

Finding 1 (Medium) -- test_net_mock was registered with no
target_link_libraries and includes no EmbeddedOS header. Its mock_net_*
functions are called directly rather than standing in for eos_net_*, so no
change to net/ can make it fail: 614 lines asserting a mock against itself.
Registering it added a green ctest entry covering nothing, which is precisely
the failure mode the guard in this same commit exists to surface -- and the
guard's own NOT_BUILT policy is where it belongs. Listed there with the reason,
including what would make it worth building: driving the real eos_net_* API
against the mock transport, which is what its docblock already claims it does.

Finding 2 (Low) -- documented the guard's limit where the policy is defined.
It checks that every suite is built and registered with ctest. It does not
check that a suite reaches the code it is named after, and a file that links
nothing satisfies it completely. Worth stating next to NOT_BUILT, because that
is the list someone reaches for when the guard fires.

Findings 3 and 4 are moot at this head: the test_mpu_validate comment and
test_net_mock's vestigial assert.h include both belonged to hunks that are
either upstream or removed here.

Verified:
  pytest tests/     15 passed
  ctest             38/38 PASS
    (test_net_mock is no longer among them; the count is unchanged from
    master because embeddedos-org#127, which this is stacked on, adds
    test_crypto_ed25519_loworder -- a suite that does exercise real code.)

Refs embeddedos-org#114
Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 4, 2026
…ry directly

Answers the review on embeddedos-org#122. The malleability fix itself was found correct;
these are the findings around it.

Finding 1 (High) -- the digest was printed and never compared, so the shared
contract could not fail. EOS_ED25519_CONTRACT_DIGEST came from the header this
repo's own generator had just written, a hash taken over its own output, so
editing the generator here and not in eBoot produced a self-consistent corpus
with a new digest, a green test, and a divergence visible only to a human
reading two CI logs. Now pinned as a literal in committed source, together
with the vector count and accept count, which are generated for the same
reason and were equally unfalsifiable. The eBoot twin (embeddedos-org#89) pins the identical
value.

Finding 4 (Medium) -- sc_is_canonical()'s accept side had no committed test.
Through the public API the boundary is unreachable: S == L and a failed group
equation are both simply 0, so an off-by-one refusing a valid S = L - 1 would
satisfy every vector in the corpus and reject real signatures only in the
field. It is a pure function of 32 bytes, so tests/test_ed25519_canonical_s.c
tests it directly -- 0, 1, L-1, L, L+1, 2^252, 2^252+2^251 and all-ones -- with
no signing key and no `cryptography` module. ed25519_verify.c is compiled a
second time with EOS_ED25519_TEST_HOOKS to give the function external linkage;
the shipped build is unchanged.

  This is the case the finding was about, reproduced. With `S == L` accepted:
      test_ed25519_canonical_s   [FAIL] at the S = L case
      test_ed25519_contract      [PASS] all 76 vectors
  The corpus cannot see it. The direct test can.

  Also corrected while writing it: 2^252 is BELOW L (L = 2^252 + 2.77e37), so
  it is in range. The value that is both non-canonical and passed by ref10's
  `signature[63] & 224` test is 2^252 + 2^251 -- 0x18 & 224 == 0. Both are now
  asserted, the second alongside the mask check that shows why the partial
  test misses it.

Finding 2 (Medium) -- tests/vectors/ed25519_contract_vectors.h is now
committed rather than generated. CMAKE_CURRENT_SOURCE_DIR preceded
CMAKE_CURRENT_BINARY_DIR on the include path, so a stale source-tree copy
would silently win over the generated one -- and the PR body's own
re-derivation command wrote to exactly that path. Committing removes the
shadow, makes the corpus auditable from a tag, and drops
find_package(Python3 ... REQUIRED) from a pure-C suite.

NOT fixed here, and stated rather than left implied: finding 3, the corpus
omitting the y >= p (non-canonical public key) family. That is the class most
likely to diverge between the two verifiers and it is worth adding -- but
adding vectors changes the digest, which both repos now pin, so it has to land
in eos and eBoot together in one coordinated change. That is the mechanism
working as intended, and it is a follow-up rather than something to slip into
this PR.

Verified:
  ctest                          41/41 PASS
  test_ed25519_contract          76 vectors, 3 accept, 73 refuse
  test_ed25519_canonical_s       PASS
  rebased onto master; conflicts in tests/CMakeLists.txt and
  services/crypto/src/ed25519_verify.c resolved additively -- master's
  ed25519_public_key_is_usable() (embeddedos-org#120) and this branch's sc_is_canonical()
  both kept.

Refs embeddedos-org#122
srpatcha pushed a commit that referenced this pull request Sep 8, 2026
* fix(build): restore the low-order Ed25519 suite that #93 removed

`CI — eos` and `Python Unit Tests` are red on master (769191a), and both
fail for one reason:

    these suites exist in tests/ but no add_executable() in
    tests/CMakeLists.txt builds them, so they never run:
    ['test_crypto_ed25519_loworder.c']

3aa8644 (#93, "remove duplicate test targets from tests/CMakeLists.txt")
removed all four lines that build and register
test_crypto_ed25519_loworder. It was not a duplicate — there was exactly
one registration before that commit, and it took it.

What stopped running is the suite that guards #101: Ed25519 rejecting
low-order public keys. A low-order key makes every term of the
verification equation collapse to the identity regardless of the
message, so a signature of all zeros verifies against any content at
all. The fix is still in services/crypto/src/ed25519_verify.c and still
correct; nothing has been checking it since #93 merged, and nothing
would have noticed if a later change had removed it too.

Restored verbatim from 3aa8644^, in its original position before
test_crypto_sha512. No other file changes.

The guard that caught this is test_cmake_test_registration.py, added
recently. It did exactly its job — this is the first thing it found.

Verified:
  test_cmake_test_registration.py    -> 5 passed (1 failed on master)
  pytest tests/                      -> 15 passed (14 passed 1 failed on master)
  test_crypto_ed25519_loworder       -> 5/5 tests passed
  ctest                              -> 39/39 passed (master builds 38)

* chore(tests): drop the unused assert.h include from test_net.c

Retitles and reduces this PR to what it still does, per the review.

Both repairs it was opened for have landed on master by other routes:
net/src/net_posix.c is byte-identical to origin/master, and master's default
configuration builds clean. Against current master the branch's only real
content was this one line -- and merging it as it stood would have *reverted*
646 lines that landed since, including #120's pkg trust anchor,
tests/test_pkg_trust_anchor.c and tests/unit/test_cmake_test_registration.py.
Reset to master and re-applied the one surviving change rather than leave a
stale branch that still looks mergeable.

The include is genuinely unused: tests/test_net.c has zero assert() calls and
uses its own CHECK() macro at 22 call sites. Worth correcting the record, per
finding 2: #124 added it one commit ago as "the missing assert.h include that
broke master", and the file had no assert() at #124's parent either. What
fixed that link error was the assert() -> CHECK() conversion already on
master; the include was never load-bearing, so removing it changes nothing
except the next reader's understanding.

Stacked on #127, which restores the test_crypto_ed25519_loworder registration
master is currently red on.

Verified:
  pytest tests/    15 passed
  ctest            39/39 PASS

Refs #116, #124

* ci: pin the eBoot and ebuild checkouts instead of floating on master

Every open eos pull request is currently red on `EoS Full-Stack Simulation`,
and none of them caused it. The kernel job checks out embeddedos-org/eBoot at
`ref: master`, and eBoot master does not compile: include/eos_image.h:135 and
:142 static-assert offsetof(eos_image_header_t, reserved) for a member that
became tlv_len + tlv_hash, and core/ed25519_verify.c redefines
point_is_identity -- two pairs of PRs that each merged clean and broke the
build together. eBoot #94 repairs it and waits on review; until it lands, no
change to any eos PR can make this job pass, and after it lands the next
broken eBoot master does the same thing again.

A floating `ref: master` makes every eos PR's CI depend on the moment-to-
moment state of another repository's default branch. Pinning the two
compiled-against checkouts makes the job a function of this repository plus
two recorded SHAs -- red means the PR broke something, green means it did not,
and a cross-repo bump is a reviewable one-line diff with CI on it.

  EBOOT_COMMIT  a172a6d6e1e6  the newest eBoot master ancestor that compiles;
                found by walking master back and building each candidate.
                Its child 8a015b2 already carries the ed25519 redefinition.
  EBUILD_COMMIT e5d8052f3e2c  ebuild's current master, frozen as-is. Both of
                its checkouts feed steps that are commented out (eFab does
                not exist), so this pin only removes the float.

The middleware matrix (10 repos) still floats; it runs those repos' own
tests rather than compiling eos against them, and 10 more pins deserve their
own decision. When eBoot #94 merges, bump EBOOT_COMMIT to the merge SHA.

Verified before pushing, master vs pin under the job's own configuration
(-DEBLDR_BOARD=qemu_arm64 -DEBLDR_VERIFY_STAGE1=OFF -DCMAKE_BUILD_TYPE=Release,
cross-compile mode): eBoot@master fails at the eos_image.h asserts, the exact
error in the job log; eBoot@a172a6d builds every target, rc=0. Both pinned
SHAs verified reachable from their repos' master. Workflow parses; the three
`ref:` edits and the env block are the whole diff. This PR's own simulation
run is the live test: it uses this branch's workflow and should go green
while every master-based PR stays red.

Carried onto this branch from #135 (001128d) so this PR's own simulation
job runs against a buildable eBoot: each pull request executes its own copy
of the workflow, so the fix greens a PR only once its branch contains it.
Byte-identical to #135's commit -- git drops it as already-upstream the
moment #135 merges. The pin itself is #135's to review.
srpatcha pushed a commit that referenced this pull request Sep 8, 2026
…rd it (#114)

* fix(build): restore the low-order Ed25519 suite that #93 removed

`CI — eos` and `Python Unit Tests` are red on master (769191a), and both
fail for one reason:

    these suites exist in tests/ but no add_executable() in
    tests/CMakeLists.txt builds them, so they never run:
    ['test_crypto_ed25519_loworder.c']

3aa8644 (#93, "remove duplicate test targets from tests/CMakeLists.txt")
removed all four lines that build and register
test_crypto_ed25519_loworder. It was not a duplicate — there was exactly
one registration before that commit, and it took it.

What stopped running is the suite that guards #101: Ed25519 rejecting
low-order public keys. A low-order key makes every term of the
verification equation collapse to the identity regardless of the
message, so a signature of all zeros verifies against any content at
all. The fix is still in services/crypto/src/ed25519_verify.c and still
correct; nothing has been checking it since #93 merged, and nothing
would have noticed if a later change had removed it too.

Restored verbatim from 3aa8644^, in its original position before
test_crypto_sha512. No other file changes.

The guard that caught this is test_cmake_test_registration.py, added
recently. It did exactly its job — this is the first thing it found.

Verified:
  test_cmake_test_registration.py    -> 5 passed (1 failed on master)
  pytest tests/                      -> 15 passed (14 passed 1 failed on master)
  test_crypto_ed25519_loworder       -> 5/5 tests passed
  ctest                              -> 39/39 passed (master builds 38)

* test: stop building test_net_mock, which covers nothing, and say what the guard checks

Reduces this PR to what it still contributes, and answers the review.

The four registrations it opened with have landed on master by other routes --
test_mpu_validate, test_e2e, test_hal_stm32f4 and the registration guard are
all there. Merging the branch as it stood would have reverted 505 lines that
landed since, including #120's pkg trust anchor. Reset onto #127 and kept only
what is still true.

Finding 1 (Medium) -- test_net_mock was registered with no
target_link_libraries and includes no EmbeddedOS header. Its mock_net_*
functions are called directly rather than standing in for eos_net_*, so no
change to net/ can make it fail: 614 lines asserting a mock against itself.
Registering it added a green ctest entry covering nothing, which is precisely
the failure mode the guard in this same commit exists to surface -- and the
guard's own NOT_BUILT policy is where it belongs. Listed there with the reason,
including what would make it worth building: driving the real eos_net_* API
against the mock transport, which is what its docblock already claims it does.

Finding 2 (Low) -- documented the guard's limit where the policy is defined.
It checks that every suite is built and registered with ctest. It does not
check that a suite reaches the code it is named after, and a file that links
nothing satisfies it completely. Worth stating next to NOT_BUILT, because that
is the list someone reaches for when the guard fires.

Findings 3 and 4 are moot at this head: the test_mpu_validate comment and
test_net_mock's vestigial assert.h include both belonged to hunks that are
either upstream or removed here.

Verified:
  pytest tests/     15 passed
  ctest             38/38 PASS
    (test_net_mock is no longer among them; the count is unchanged from
    master because #127, which this is stacked on, adds
    test_crypto_ed25519_loworder -- a suite that does exercise real code.)

Refs #114

* ci: pin the eBoot and ebuild checkouts instead of floating on master

Every open eos pull request is currently red on `EoS Full-Stack Simulation`,
and none of them caused it. The kernel job checks out embeddedos-org/eBoot at
`ref: master`, and eBoot master does not compile: include/eos_image.h:135 and
:142 static-assert offsetof(eos_image_header_t, reserved) for a member that
became tlv_len + tlv_hash, and core/ed25519_verify.c redefines
point_is_identity -- two pairs of PRs that each merged clean and broke the
build together. eBoot #94 repairs it and waits on review; until it lands, no
change to any eos PR can make this job pass, and after it lands the next
broken eBoot master does the same thing again.

A floating `ref: master` makes every eos PR's CI depend on the moment-to-
moment state of another repository's default branch. Pinning the two
compiled-against checkouts makes the job a function of this repository plus
two recorded SHAs -- red means the PR broke something, green means it did not,
and a cross-repo bump is a reviewable one-line diff with CI on it.

  EBOOT_COMMIT  a172a6d6e1e6  the newest eBoot master ancestor that compiles;
                found by walking master back and building each candidate.
                Its child 8a015b2 already carries the ed25519 redefinition.
  EBUILD_COMMIT e5d8052f3e2c  ebuild's current master, frozen as-is. Both of
                its checkouts feed steps that are commented out (eFab does
                not exist), so this pin only removes the float.

The middleware matrix (10 repos) still floats; it runs those repos' own
tests rather than compiling eos against them, and 10 more pins deserve their
own decision. When eBoot #94 merges, bump EBOOT_COMMIT to the merge SHA.

Verified before pushing, master vs pin under the job's own configuration
(-DEBLDR_BOARD=qemu_arm64 -DEBLDR_VERIFY_STAGE1=OFF -DCMAKE_BUILD_TYPE=Release,
cross-compile mode): eBoot@master fails at the eos_image.h asserts, the exact
error in the job log; eBoot@a172a6d builds every target, rc=0. Both pinned
SHAs verified reachable from their repos' master. Workflow parses; the three
`ref:` edits and the env block are the whole diff. This PR's own simulation
run is the live test: it uses this branch's workflow and should go green
while every master-based PR stays red.

Carried onto this branch from #135 (001128d) so this PR's own simulation
job runs against a buildable eBoot: each pull request executes its own copy
of the workflow, so the fix greens a PR only once its branch contains it.
Byte-identical to #135's commit -- git drops it as already-upstream the
moment #135 merges. The pin itself is #135's to review.
srpatcha added a commit that referenced this pull request Sep 8, 2026
…Boot (#122)

* fix(build): restore the low-order Ed25519 suite that #93 removed

`CI — eos` and `Python Unit Tests` are red on master (769191a), and both
fail for one reason:

    these suites exist in tests/ but no add_executable() in
    tests/CMakeLists.txt builds them, so they never run:
    ['test_crypto_ed25519_loworder.c']

3aa8644 (#93, "remove duplicate test targets from tests/CMakeLists.txt")
removed all four lines that build and register
test_crypto_ed25519_loworder. It was not a duplicate — there was exactly
one registration before that commit, and it took it.

What stopped running is the suite that guards #101: Ed25519 rejecting
low-order public keys. A low-order key makes every term of the
verification equation collapse to the identity regardless of the
message, so a signature of all zeros verifies against any content at
all. The fix is still in services/crypto/src/ed25519_verify.c and still
correct; nothing has been checking it since #93 merged, and nothing
would have noticed if a later change had removed it too.

Restored verbatim from 3aa8644^, in its original position before
test_crypto_sha512. No other file changes.

The guard that caught this is test_cmake_test_registration.py, added
recently. It did exactly its job — this is the first thing it found.

Verified:
  test_cmake_test_registration.py    -> 5 passed (1 failed on master)
  pytest tests/                      -> 15 passed (14 passed 1 failed on master)
  test_crypto_ed25519_loworder       -> 5/5 tests passed
  ctest                              -> 39/39 passed (master builds 38)

* fix(ed25519): reject a non-canonical S, and share the contract with eBoot

Two things, and the second found the first.

eos and eBoot each carry their own Ed25519 verifier. Different code,
opposite return conventions — this one returns 1 to accept, eBoot's
returns EOS_OK — doing the same job on the same wire format. When only
eos rejected low-order public keys, nothing in either repo could notice:
there is no shared build, and the two are far too different for a source
diff to say anything. That divergence is why eBoot #73 survived here
after this side had already fixed it.

tests/vectors/ed25519_contract_vectors.h is the part that can be shared:
76 vectors of pure data with a SHA-256 over them, byte-identical to
eBoot's copy (eBoot #89). Each side runs its own verifier and prints the
digest, so a change to one copy that does not reach the other shows up
as two different digests.

On its first run it failed here, on three vectors eBoot passes.

ed25519_verify() had only ref10's partial check, `signature[63] & 224`.
That rejects any S with a bit set above 2^252, but

    L = 2^252 + 27742317777372353535851937790883648493

so every S in [L, 2^253) passes it while being non-canonical. Adding L
to a valid S lands squarely in that band, and (R, S) and (R, S + L) both
verified — signature malleability. Anything treating a signature as a
unique identifier (deduplication, replay caches, logging by signature
hash) sees two distinct signatures over one message and one key.

RFC 8032 5.1.7 requires rejecting S outside [0, L). eBoot's
core/ed25519_verify.c has done this all along via s_is_canonical();
this side did not. Added sc_is_canonical(), compared most-significant
byte first against a public constant — S is public, so this leaks
nothing.

Verified:
  contract vectors, before the fix -> 3 of 76 wrong (all three S+L)
  contract vectors, after          -> 76/76, 3 accepted, 73 refused
  test_crypto_ed25519_loworder     -> 5/5, unchanged
  ctest                            -> 35/35 passed
  digest 1059febedae2b3e3dfaa1ef5b419fb37ed7f0d53b377a3250b53b95a546282a2
         identical to eBoot #89's copy

Stacked on #116; eos master does not build without it.

* build(tests): generate the Ed25519 contract vectors, do not commit them

tests/vectors/ed25519_contract_vectors.h is 54 KB of generated data, and the
identical file was checked in to BOTH eos and eBoot, along with the generator
that produces it -- 61 KB duplicated verbatim across two repositories and kept
in step by hand.

The generator is pure stdlib and deterministic, so each repo can produce the
header into its own build tree instead. tools/gen_ed25519_contract_vectors.py
grows an -o/--output flag (stdout stays the default, so the regeneration
command in its docstring still works), and tests/CMakeLists.txt drives it from
add_custom_command. Only the 6.8 KB generator is now carried twice, and it is
source rather than generated output.

Verified: the generated header is byte-identical to the file it replaces; both
repos still report digest
1059febedae2b3e3dfaa1ef5b419fb37ed7f0d53b377a3250b53b95a546282a2, which is the
thing that actually holds the two verifiers to one contract. Suites pass (eos
35/35, eBoot 22/22). Breaking the generator fails the build rather than
silently reusing a stale header. The cross-compile legs configure with tests
OFF so find_package(Python3) is never reached there; confirmed by configuring
under a cross toolchain.

* test(ed25519): assert the contract digest, and test the [0, L) boundary directly

Answers the review on #122. The malleability fix itself was found correct;
these are the findings around it.

Finding 1 (High) -- the digest was printed and never compared, so the shared
contract could not fail. EOS_ED25519_CONTRACT_DIGEST came from the header this
repo's own generator had just written, a hash taken over its own output, so
editing the generator here and not in eBoot produced a self-consistent corpus
with a new digest, a green test, and a divergence visible only to a human
reading two CI logs. Now pinned as a literal in committed source, together
with the vector count and accept count, which are generated for the same
reason and were equally unfalsifiable. The eBoot twin (#89) pins the identical
value.

Finding 4 (Medium) -- sc_is_canonical()'s accept side had no committed test.
Through the public API the boundary is unreachable: S == L and a failed group
equation are both simply 0, so an off-by-one refusing a valid S = L - 1 would
satisfy every vector in the corpus and reject real signatures only in the
field. It is a pure function of 32 bytes, so tests/test_ed25519_canonical_s.c
tests it directly -- 0, 1, L-1, L, L+1, 2^252, 2^252+2^251 and all-ones -- with
no signing key and no `cryptography` module. ed25519_verify.c is compiled a
second time with EOS_ED25519_TEST_HOOKS to give the function external linkage;
the shipped build is unchanged.

  This is the case the finding was about, reproduced. With `S == L` accepted:
      test_ed25519_canonical_s   [FAIL] at the S = L case
      test_ed25519_contract      [PASS] all 76 vectors
  The corpus cannot see it. The direct test can.

  Also corrected while writing it: 2^252 is BELOW L (L = 2^252 + 2.77e37), so
  it is in range. The value that is both non-canonical and passed by ref10's
  `signature[63] & 224` test is 2^252 + 2^251 -- 0x18 & 224 == 0. Both are now
  asserted, the second alongside the mask check that shows why the partial
  test misses it.

Finding 2 (Medium) -- tests/vectors/ed25519_contract_vectors.h is now
committed rather than generated. CMAKE_CURRENT_SOURCE_DIR preceded
CMAKE_CURRENT_BINARY_DIR on the include path, so a stale source-tree copy
would silently win over the generated one -- and the PR body's own
re-derivation command wrote to exactly that path. Committing removes the
shadow, makes the corpus auditable from a tag, and drops
find_package(Python3 ... REQUIRED) from a pure-C suite.

NOT fixed here, and stated rather than left implied: finding 3, the corpus
omitting the y >= p (non-canonical public key) family. That is the class most
likely to diverge between the two verifiers and it is worth adding -- but
adding vectors changes the digest, which both repos now pin, so it has to land
in eos and eBoot together in one coordinated change. That is the mechanism
working as intended, and it is a follow-up rather than something to slip into
this PR.

Verified:
  ctest                          41/41 PASS
  test_ed25519_contract          76 vectors, 3 accept, 73 refuse
  test_ed25519_canonical_s       PASS
  rebased onto master; conflicts in tests/CMakeLists.txt and
  services/crypto/src/ed25519_verify.c resolved additively -- master's
  ed25519_public_key_is_usable() (#120) and this branch's sc_is_canonical()
  both kept.

Refs #122

* test(ed25519): hash the vectors, and drop the linkage macro CodeQL flagged

Answers the second round on #122. Both findings are defects in my previous
commit.

The digest pin did not cover the corpus. EOS_ED25519_CONTRACT_DIGEST is a
#define inside the generated header; EOS_ED25519_CONTRACT_EXPECTED is a
#define in the driver. The test compared two string literals and never hashed
a vector, so editing a committed vector changed the data and left the value it
was compared against untouched. Raised on the eBoot twin (#89) and stated
there to apply here too; it did.

contract_digest() now recomputes SHA-256 over the vectors at run time in the
generator's serialisation -- public_key[32] || signature[64] ||
message[message_len] || (uint8_t)expect_accept -- via eos_sha256_* from
eos/crypto.h. It compares the result to the pinned literal and to the header's
own claim, so a corpus and a header from different generator runs are caught.

  Verified with the probe from the eBoot review. Changing one byte of the
  order-1 identity public key:
      [FAIL] corpus digest changed
             expected 1059febe...282a2
             got      55370deb...cae9      exit 1
  That `got` value is byte-identical to the one eBoot's driver produces for
  the same edit, which is independent confirmation that the two committed
  corpora really are the same file.

  Worth recording because it nearly cost me the finding: the first probe run
  reported PASS. The build had not picked up the header change, so the binary
  was stale. `touch` on the driver and a rebuild gave the failure above. A
  probe that does not rebuild proves nothing -- check that the link step ran.

CodeQL alert 405, "Static function sc_is_canonical is unreachable" -- caused
by the EOS_ED25519_TEST_HOOKS macro I added, which gave the symbol a linkage
that varied by build. Removed: the function is static again, and
tests/test_ed25519_canonical_s.c includes the translation unit to reach it,
which is the ordinary way to test a static and needs no conditional
compilation in a crypto source file. eos_crypto is still linked for the
ge_*/sha512 helpers the included unit calls -- a static archive contributes
only objects needed for undefined symbols, and ed25519_verify.c.o is not one,
because the test object already defines everything in it.

Verified:
  ctest                              41/41 PASS
  test_ed25519_canonical_s           PASS, links with no duplicate symbols
  test_ed25519_contract              76 vectors, 3 accept, 73 refuse
  byte-flip probe                    FAILS (after a real rebuild)
  off-by-one probe, `S == L` accepted:
      test_ed25519_canonical_s       FAILS
      test_ed25519_contract          still exit 0
    so the direct boundary test still discriminates where the corpus cannot.

Refs #122

* docs(ed25519): make the two copies actually byte-identical, drop stale comments

Addresses srpatcha's review findings 1 and 3 on #122. Text and comments only;
no behaviour change, and the 76 vectors and their digest are untouched.

Finding 1 -- the body claimed the corpus was byte-identical to eBoot#89's copy
and it was not: the two generators had drifted in their docstrings and in the
header comment they emit, so the files differed by three comment lines while
all 76 vectors and the digest matched. Adopted eBoot#89's wording verbatim --
it is the more accurate description, since each side recomputes the digest and
compares it to a pinned literal rather than merely printing it -- and
regenerated. Both repos' tools/gen_ed25519_contract_vectors.py and
tests/vectors/ed25519_contract_vectors.h are now byte-identical:

    sha256 489b1fc0662187841c58e12c650ca1a25a974e4fafe4d4318f4fad62b7e4b5cf

which is the value the review measured for eBoot's copy, so the spot-check the
finding describes -- hash both files -- now passes.

Finding 3 -- three comment blocks described an approach that was abandoned,
two of them contradicting the code beside them:

(a) ed25519_verify.c opened "Linked, not static, when the test hook is on"
    six lines above "It stays static", and the function is static.
(b) tests/CMakeLists.txt described compiling ed25519_verify.c a second time
    with EOS_ED25519_TEST_HOOKS. That macro occurred exactly once in the tree
    -- in that sentence. The next line already describes what the test really
    does (#include the translation unit).
(c) the paragraph saying the vector table "is produced into the build tree
    rather than committed" sat directly above one saying it is committed. It
    is committed; the generate-at-build-time paragraph was mine, left behind
    when that approach was reverted, and it is the half that was false.

In each case the surviving text already explained the real design, so this is
subtraction. Verified: cmake+ctest 41/41 (the count finding 4 asks for, not
the 35/35 still in the body), test_ed25519_contract reports digest 1059feb...
over 76 vectors with 3 accepted and 0 wrong, and test_ed25519_canonical_s
still passes.

---------

Co-authored-by: Srikanth Patchava <srikanth.patchava@outlook.com>
srpatcha added a commit to embeddedos-org/eSec that referenced this pull request Oct 6, 2026
…acts

The status line said eSec is Implemented at services/crypto/ with "one part Experimental" without naming it, so a reader could take one of the seven crypto suites for the unfinished part. Name package signature verification (services/pkg/eos_pkg.c) in the status line itself.

Also correct the paragraph behind it: it described eos_pkg verifying against an all-zero key. embeddedos-org/eos#120 removed that key; eos_pkg now verifies only against a platform-supplied trust anchor (eos_pkg_set_trust_anchor() or EOS_PKG_TRUST_ANCHOR_HEX) and refuses otherwise unless EOS_ALLOW_UNSIGNED_PKG is set. It stays Experimental: eos ships no key and the v2 signature envelope landed on 2026-10-04. Still tracked as embeddedos-org/eos#98.
srpatcha added a commit to embeddedos-org/eSec that referenced this pull request Oct 6, 2026
* docs: eSec is Implemented in eos, and one part is not

The README said "Status: Planned. There is no implementation yet." The
crypto services are Implemented under §28 — 15 source files and seven test
suites passing in eos, at services/crypto/.

But flatly upgrading the status would overstate it. Package signature
verification is Experimental at best: services/pkg/eos_pkg.c verifies
against eos_pkg_public_key[32] = {0}, and the low-order-key rejection that
makes an all-zero anchor fail closed rather than open only just landed
(eos#99). A verifier with no real key authenticates nothing, and a README
claiming a working security framework should say so. Tracked as eos#98.

Also replaces the old rationale with v2.0 §10 and §21.1, adds the component
manifest as the way to depend on this, and records why splitting early is
costly — two Ed25519 implementations across eos and eBoot carried the same
bypass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: name the Experimental part in the status line, and update its facts

The status line said eSec is Implemented at services/crypto/ with "one part Experimental" without naming it, so a reader could take one of the seven crypto suites for the unfinished part. Name package signature verification (services/pkg/eos_pkg.c) in the status line itself.

Also correct the paragraph behind it: it described eos_pkg verifying against an all-zero key. embeddedos-org/eos#120 removed that key; eos_pkg now verifies only against a platform-supplied trust anchor (eos_pkg_set_trust_anchor() or EOS_PKG_TRUST_ANCHOR_HEX) and refuses otherwise unless EOS_ALLOW_UNSIGNED_PKG is set. It stays Experimental: eos ships no key and the v2 signature envelope landed on 2026-10-04. Still tracked as embeddedos-org/eos#98.

* docs: recount eos services/crypto/src (16 files at eos dc13c59)

Signed-off-by: Srikanth Patchava <srpatcha@users.noreply.github.com>

---------

Signed-off-by: Srikanth Patchava <srpatcha@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Srikanth Patchava <srpatcha@users.noreply.github.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