FreeHSM C

An open-source PKCS#11 v3.2 cryptographic module under Simorgh Labs

View the Project on GitHub afchine1337/freehsm

FreeHSM v1.5.0: the integrity self-test that never ran

2026-07-18

The headline of this release is not a feature. It is a defect we shipped, in every signed build, for months — and that our own tests never caught, because the one place it would have surfaced was switched off everywhere.

The self-test that self-approved

FIPS 140-3 §7.10.2 requires a cryptographic module to verify its own integrity at start-up: hash the loaded binary, compare against a digest embedded at build time, refuse to run on a mismatch. FreeHSM has that check. It is wired into C_Initialize. It is documented. And until v1.5.0 it never actually verified anything.

The embedded-digest slot was declared like this:

const uint8_t fhsm_module_integrity_digest[32] = { 0 };

with a comment, one line above, stating that a volatile qualifier “prevents the compiler from constant-folding the digest into the code generator.” The rationale was written. The keyword was not there.

The digest is patched into the binary after compilation by the signing script. Without volatile, GCC is free to assume the slot still holds its initialiser — and it folded the explicitly-initialised element [0] to a literal zero. So byte 0 of every comparison came from the code generator and bytes 1–31 from memory:

computed = 52fad82b6ccecf78306ca4249d37a173031b514276c318ab742755faa7a94e5a
embedded = 00fad82b6ccecf78306ca4249d37a173031b514276c318ab742755faa7a94e5a
           ^^

Every correctly signed build failed its own integrity check on that one byte. Nobody noticed, because the entire toolchain — unit tests, the external harness, CI — runs with FHSM_INTEGRITY_ALLOW_UNSIGNED=1, which downgrades the failure to a warning. A security mechanism exercised only from behind its own bypass is not exercised at all. That process gap, not the missing keyword, is the real bug.

The fix is one qualifier. The lesson is the test we added alongside it: make test-integrity now runs the check without the bypass, on a freshly signed binary, and asserts three things — an unsigned binary must fail, a signed binary must pass, and a signed-then-tampered binary must fail. It is gating in CI. We confirmed it catches the original bug by reverting the fix and watching it go red.

Worth noting: a v1.2.1 fix had already removed a different bypass in the same function (an unconditional return CKR_OK). That turned “always passes” into “always fails”, which the env var then masked. The check has never actually gated anything until now.

361 → 13 against a harness that doesn’t know us

The rest of v1.5.0 is the result of running FreeHSM against pkcs11-check, Denis Mingulov’s vendor-neutral PKCS#11 behavioural harness — 100k+ checks that know nothing about our implementation. Over the campaign, failures went from 361 to 13 and crashes from 7 to 0.

Several of the findings were not “a test is red” but “the module does the wrong thing and no counter was measuring it”:

One breaking change, stated plainly

Correcting the AES-MAC code points is a breaking change. If your code hard-codes 0x108C for AES-CMAC, move it to 0x108A. Callers using 0x108A (including OpenSC’s pkcs11-tool, which was right all along) are unaffected and now get real CMAC. The binary name libfreehsm.so and every other PKCS#11 identifier are unchanged.

What we did not fix, and won’t

Thirteen harness failures remain, and they should. Roughly half are design positions (the CBC-PAD padding oracle is inherent to the mode — the harness says so itself; GCM IV-reuse detection needs state a stateless software token does not keep) or gaps in the harness (two tests drive non-FIPS RSA PKCS#1 v1.5 encryption unconditionally, which a FIPS-strict build correctly does not offer). The full triage — fixable vs. design vs. report-upstream — is published in the repo, next to the security advisories we file against ourselves. Same logic as always: a green number bought by weakening the module is not a win.

The rename, briefly

This is also the first release under the new name. The repository is now freehsm (was freehsm-c; old URLs redirect). FreeHSM is the Apache-2.0 PKCS#11 library; Simorgh PKI is the product being built on it; Simorgh Labs is the org. Nothing consumer-facing changes: binary name, PKCS#11 identifiers, and the GPG signing key are all stable across the rename.


v1.5.0 is signed with GPG key 743A 6A59 04A1 4616 46A6 408D E485 6016 2DBB F28A 2 and available on GitHub, GitLab, and Codeberg.