An open-source PKCS#11 v3.2 cryptographic module under Simorgh Labs
Not certified. FreeHSM is built to the requirements of FIPS 140-3 Level 1 and documented with its methodology. A certificate costs more than this project will ever have, and that cost is exactly the barrier that keeps public bodies, universities and developing countries away from auditable cryptography.
This document is written, as the methodology requires, in the register of a submission. The two lines below name the standard it is written to and the authority that would receive it — not an engagement anyone has entered.
docs/FIPS_140_3_SECURITY_TARGET.mdsays the same in its own status note, anddocs/AGD_PRE.mdanddocs/AGD_OPE.mdcarry this banner; this file lacked it, which left “Target validation” and “Validation authority” reading as a live intent. See Certification inREADME.md.
Module name: FreeHSM Cryptographic Module (libfreehsm.so) Version: 1.0.0-FIPS Module type: Software, Level 1 (multi-chip standalone, OS bound) Written to: FIPS 140-3 (ISO/IEC 19790:2012 + 24759:2017) Would be received by: NIST CMVP (US) / CCCS (Canada) — no submission made
FreeHSM is a software cryptographic module that exposes a PKCS#11 v3.2 interface to applications. It is intended for use as a Soft HSM in production environments where a hardware HSM is unavailable but FIPS 140-3 Level 1 conformance is required (e.g. internal PKIs, code-signing servers, regulated SaaS workloads).
The module’s cryptographic boundary is the single shared object libfreehsm.so plus the OpenSSL FIPS provider (fips.so) that it dynamically loads at C_Initialize. All other software — including the calling application, the host OS, and the OpenSSL base/default providers — is outside the boundary.
┌─────────────────────────────────────────────┐
│ Application │
└──────────────────┬──────────────────────────┘
│ PKCS#11 v3.2 ABI (C_*)
┌─── boundary ───────────────────┼─────────────────────────────┐
│ ▼ │
│ libfreehsm.so (this module) │
│ fhsm_pkcs11.c --- entry points (audit + dispatch)│
│ fhsm_crypto.c --- KAT, AES-GCM, HMAC, PBKDF2 │
│ fhsm_token.c --- token store, PIN, throttle │
│ fhsm_session.c --- session table │
│ fhsm_audit.c --- HMAC-chained audit log │
│ fhsm_memory.c --- secure heap, zeroize │
│ fhsm_state.c --- state machine │
│ ▼ │
│ OpenSSL FIPS provider (fips.so) │
└────────────────────────────────────────────────────────────────┘
The module supports two operator roles:
| Role | Identifier in PKCS#11 | Authentication |
|---|---|---|
| Crypto Officer (CO) | CKU_SO |
PBKDF2-HMAC-SHA-256 200_000 iter |
| User | CKU_USER |
PBKDF2-HMAC-SHA-256 200_000 iter |
Authentication is per slot. PINs must be at least 8 characters. After FHSM_PIN_MAX_FAILED = 5 consecutive failures, the role is locked (FHSM_RV_PIN_LOCKED); only C_InitPIN (for USER, called by SO) or C_InitToken (for both) clears the lock.
Between failed attempts, the module enforces exponential backoff:
delay_n = min(500 ms * 2^(n-1), 60 s). The throttle is persisted in the token file so a process restart does not bypass it.
| Service | Role(s) allowed | Approved? | FIPS mode? |
|---|---|---|---|
C_Initialize |
(none) | n/a | yes |
C_GetInfo |
(none) | n/a | yes |
C_Login |
CO, USER | n/a | yes |
C_GenerateKey |
CO, USER | yes | yes |
C_Encrypt (AES-GCM) |
USER | yes | yes |
C_Decrypt (AES-GCM) |
USER | yes | yes |
C_Sign (RSA-PSS, ECDSA, EdDSA, ML-DSA) |
USER | yes | yes |
C_Verify (idem) |
USER | yes | yes |
C_Digest (SHA-2, SHA-3) |
(none) | yes | yes |
C_DeriveKey (HKDF, PBKDF2) |
USER | yes | yes |
C_EncapsulateKey (ML-KEM) |
USER | yes | yes |
C_DecapsulateKey (ML-KEM) |
USER | yes | yes |
C_Sign (MD5, SHA-1, DSA, RSA-PKCS1-v1.5) |
— | no | rejected |
Services marked not approved return FHSM_RV_FIPS_NOT_APPROVED when FHSM_FIPS_ONLY = 1. They remain dispatchable in non-approved builds for interoperability with legacy systems; transition into / out of approved mode is signaled in the audit log (state_transition event).
| Family | Variant / parameter set | Reference | CAVP cert # |
|---|---|---|---|
| AES | 128 / 192 / 256-GCM, CBC, KW, KWP | FIPS 197 / SP 800-38{A,D,F} | TBD |
| SHA-2 | SHA-256, SHA-384, SHA-512 | FIPS 180-4 | TBD |
| SHA-3 | SHA3-256/384/512, SHAKE128/256 | FIPS 202 | TBD |
| HMAC | with all approved SHA-2/3; SHA-1 in HMAC only | FIPS 198-1, SP 800-131A rev. 2 | TBD |
| RSA-PSS | 2048, 3072, 4096 | FIPS 186-5 | TBD |
| ECDSA | P-256, P-384, P-521 | FIPS 186-5 | TBD |
| EdDSA | Ed25519, Ed448 | FIPS 186-5 | TBD |
| ECDH/X25519/X448 | P-256, P-384, X25519, X448 | SP 800-56A | TBD |
| ML-KEM | 512 / 768 / 1024 | FIPS 203 | TBD |
| ML-DSA | 44 / 65 / 87 | FIPS 204 | TBD |
| SLH-DSA | SHA2/SHAKE × 128/192/256 × s/f | FIPS 205 | TBD |
| HKDF | with all approved SHA-2/3; SHA-1 as PRF | SP 800-56C rev. 2 | TBD |
| PBKDF2 | HMAC-SHA-256, ≥ 200_000 iter | SP 800-132 | TBD |
| CTR_DRBG | AES-256, derivation function | SP 800-90A | TBD |
| Entropy source | Linux getrandom(GRND_RANDOM) |
SP 800-90B | ESV TBD |
SHA-1, and why it appears in two rows above and in none of the signature
rows. SP 800-131A rev. 2 scopes the SHA-1 prohibition to digital signature
generation. It remains acceptable inside HMAC, and therefore inside the
HMAC-based KDFs of SP 800-56C rev. 2. The module follows that boundary rather
than a blanket rule: CKM_SHA_1_HMAC and CKM_HKDF_DERIVE with
prfHashMechanism = CKM_SHA_1 are accepted under fips-strict, while
C_Digest(CKM_SHA_1) and CKM_SHA1_RSA_PKCS are refused by it.
Until 2026-09-19 the code did not draw that boundary. One flag marked SHA-1
non-approved for every use, so HMAC-SHA-1 was approved when reached through
C_SignInit and refused when reached through HKDF’s PRF – one primitive, two
paths, two answers. It failed closed, so it cost no safety; it cost the 84
valid vectors of hkdf_sha1_test.json and an explanation on every reading of
the report. This table said SHA-2/3 only, and docs/MECHANISMS.md, which is
generated from the profile, said CKM_SHA_1_HMAC was approved. The two
disagreed about the same primitive. The generated one was right.
The TBD cells will be filled in once the CMVP queue advances and the lab runs ACVP. The full CAVP test vector set is loaded at module init via fhsm_kat_run_all (see §6).
All SSPs are stored exclusively in the OpenSSL secure heap (OPENSSL_secure_malloc), which is a single mlock-ed mmap arena. The arena is sized at build time (default 8 MiB, configurable via FHSM_SECURE_HEAP_BYTES or the secure_heap_kb setting) and holds every sensitive object value, not only the DEKs (#127). Allocations are returned to the arena via OPENSSL_secure_clear_free, which zeroizes the block before release.
| SSP | Algorithm | Storage | Zeroization trigger |
|---|---|---|---|
| DEK (Data Encryption Key) | AES-256 | Secure heap (live) | C_Logout, C_Finalize, lock |
| KEK derivés du PIN | AES-256 | Secure heap (live) | After token write |
| Private keys (RSA, EC, PQ) | RSA-2048 to 4096, EC, PQ | Secure heap (live) | C_DestroyObject, logout |
| Audit MAC key | HMAC-SHA-256 | Secure heap (live) | C_Finalize |
| KAT plaintexts | (see §6) | Secure heap (transient) | End of KAT |
| DRBG state | CTR_DRBG | OpenSSL FIPS provider | Provider unload |
Executed at C_Initialize, before any service becomes available:
libfreehsm.so is computed and compared to the value embedded at build time in fhsm_module_integrity_digest[]. Mismatch → ERROR state.fhsm_drbg_init runs the multi-source seed (getrandom + RDRAND + /dev/urandom + TSC jitter → SHA-384 conditioner → RAND_add). Source unavailability → ERROR. See RNG.md for the pipeline.kat/fhsm_kat_vectors.c; v1.1.0 adds an extended CAVP set in kat/cavp_extended.c (2 AES-GCM-256 from NIST SP 800-38D Annex B + 4 HMAC-SHA-256 from RFC 4231). Any mismatch → ERROR.| Trigger | Test | Implementation |
|---|---|---|
| AEAD decrypt | Tag verification (constant-time) | fhsm_aes_gcm_decrypt rejects on tag mismatch |
| Signature verify | Algorithm-specific verification | Inline in C_Verify |
| Asymmetric key generation (§7.10.2.b) | Pair-wise consistency check : encrypt-decrypt round-trip (RSA), sign-verify (EC, ML-DSA, SLH-DSA), encap-decap (ML-KEM). Failure latches ERROR and refuses to persist the keypair. | src/fhsm_pairwise.c, invoked inline by C_GenerateKeyPair |
| RNG output (§7.10.3.b) | Continuous test : RCT (cutoff 6 successive identical bytes) + APT (W=512, cutoff 51) + CRNGT (16-byte block compared between calls). | src/fhsm_drbg.c health_rct_apt + health_crngt |
After init, fhsm_kat_results() returns the structured pass/fail/duration vector. The same data is appended to the audit log as event kat_report.
The module is built and tested against the following operational environment (it is not validated, and no validation is planned):
mlock permission and RLIMIT_MEMLOCK ≥ 1 MiBopenssl.cnfappend semantics)Operators must:
openssl list -providers shows fips with status active).*.tok) and audit log files (*.audit.log) on a filesystem that is not network-mounted (NFS/CIFS can lose atomicity).CAP_IPC_LOCK to allow secure-heap mlock.| Attack | Mitigation |
|---|---|
| Timing channels on PIN | Throttle is enforced before PBKDF2; constant-time tag/MAC compare |
| Cold-boot DEK exfiltration | Secure heap mlock + zeroize on free |
| Coredump leak | prctl(PR_SET_DUMPABLE, 0) at C_Initialize |
| Log injection | Audit emit refuses any byte outside [0x20, 0x7e] and [",\\] |
| Tamper of audit | HMAC-chained log + token-derived MAC key |
| Brute-force on PIN | Exponential backoff + max-5 lock |
| DRBG seed exhaustion | SP 800-90A reseed thresholds + reseed entry point |