Omitly · Security

Cryptography

Every cryptographic primitive in Omitly, drawn directly from our cryptographic bill of materials (CBOM v1). We publish this so a reviewer can evaluate exactly what we use and how — not take our word for it.

On FIPS: where an algorithm is FIPS-approved, our implementation is nota CMVP-validated module. We state this as "approved algorithm, non-validated implementation" throughout — never "FIPS compliant."

Tamper-evidence seal — signing & verification

Primitivesignature · Ed25519
Implementationed25519-dalek 2
PurposeSigns the entire delivered PDF (trailing %%OMITLY-SEAL appendix); verify_strict rejects non-canonical/malleable signatures. Per-install key — integrity, not identity (see CLAUDE.md invariant #2).
FIPS postureFIPS 186-5 approved algorithm; ed25519-dalek is NOT a CMVP-validated module. State "approved algorithm, non-validated implementation" — never "FIPS compliant".
Sourcecrates/omitly-seal/src/lib.rs (seal_pdf, verify_sealed_pdf), src-tauri/src/redact.rs (per-install key)

Seal v2 verification — ML-DSA-87 leg (omitly#533)

Primitivesignature
Implementationml-dsa 0.1.1
PurposeVerifies the ML-DSA-87 leg (FIPS 204, pure, context "omitly-seal-v2") of a seal v2 (docs/SEAL-SPEC-v2.md) with TWO independent implementations, RustCrypto ml-dsa and fips204 (entry below); both must accept, and a split is reported invalid. 4.0.0-beta.1 is verification only — nothing signs with ML-DSA yet. Versions are past RUSTSEC-2025-0144 and GHSA-5x2r-hc65-25f9 (ml-dsa >= 0.1.0-rc.4).
FIPS postureFIPS 204 approved algorithm; ml-dsa is NOT a CMVP-validated module.
Sourcecrates/omitly-seal/src/seal_v2/mod.rs (verify), crates/omitly-seal/src/seal_v2/keys.rs (mint-time key derivation and the §8 two-implementation key check, producer only, omitly#1552)

Seal v2 verification — ML-DSA-87 second implementation (omitly#533)

Primitivesignature
Implementationfips204 0.4.6
PurposeThe second, independent ML-DSA-87 verifier for the seal v2 leg above (default features off: no OS RNG in the verify path). pqcrypto-mldsa, named as a candidate in the spec, is deliberately not used: RUSTSEC-2026-0166, unmaintained.
FIPS postureFIPS 204 approved algorithm; fips204 is NOT a CMVP-validated module.
Sourcecrates/omitly-seal/src/seal_v2/mod.rs (verify), crates/omitly-seal/src/seal_v2/keys.rs (mint-time key derivation and the §8 two-implementation key check, producer only, omitly#1552)

Seal v2 verification — ECDSA P-384 leg (omitly#533)

Primitivesignature · P-384
Implementationp384 0.14.0
PurposeVerifies the classical leg of a seal v2: ECDSA P-384 with SHA-384 over the signed core, compressed SEC1 keys, raw r||s, and low-S enforced by Omitly's own code (the crate accepts high-S). Required for "valid" in policy epoch 1 (spec §9.3). Verification only in 4.0.0-beta.1.
FIPS postureFIPS 186-5 approved algorithm (ECDSA P-384, SHA-384); p384 is NOT a CMVP-validated module.
Sourcecrates/omitly-seal/src/seal_v2/mod.rs (verify), crates/omitly-seal/src/seal_v2/keys.rs (mint-time key derivation and the §8 two-implementation key check, producer only, omitly#1552)

Seal v2 key derivation — HKDF-SHA-512 (omitly#1552)

Primitivekdf
Implementationhkdf 0.13.0
PurposeDerives both seal v2 legs from one 32-byte master seed (SEAL-SPEC-v2 §7.6, decided D3): okm = HKDF-SHA-512(salt "omitly-seal-v2-keys", info "omitly-seal-v2 keygen 1", L 88); xi = okm[0..32] seeds ML-DSA-87; d = (okm[32..88] mod (n-1)) + 1 is the P-384 private key (FIPS 186-5 A.2.1), reduced with crypto-bigint's constant-time remainder. Producer only (mint time). Pinned by §13 vector 24: OpenSSL computes every cryptographic step, the generator's Python integer arithmetic computes d.
FIPS postureSP 800-56C / RFC 5869 HKDF with an approved hash (SHA-512); hkdf is NOT a CMVP-validated module.
Sourcecrates/omitly-seal/src/seal_v2/keys.rs

Seal v2 key check — P-384 second implementation; TLS provider (omitly#1552)

Primitivekey-agree · P-384
Implementationaws-lc-rs 1.18.1
PurposeTwo roles. (1) Since omitly#1552: the second, independent P-384 implementation in the §8 step-1 key check. It derives the public point from the seal's private scalar (through its ECDH_P384 agreement API, used only as a scalar-multiplication oracle: no key agreement or signature is performed), and sealing refuses unless it equals p384's. Producer only. Its ML-DSA cannot serve as an ML-DSA cross-check: it supports only an empty context string, and seal v2 signs with ctx "omitly-seal-v2". (2) As before: the crypto provider under rustls, rustls-webpki and quinn-proto for the app's HTTPS connections.
FIPS postureAWS-LC has FIPS-validated builds, but this build uses the non-FIPS aws-lc-sys backend; not a CMVP-validated module as shipped.
Sourcecrates/omitly-seal/src/seal_v2/keys.rs (key check), crates/omitly-seal/src/seal_v2/mint.rs (P-384 leg cross-verification); transitively via rustls (TLS)

Seal v2 minting — ML-DSA-87 third implementation, cross-verification (omitly#1552)

Primitivesignature
Implementationlibcrux-ml-dsa 0.0.11
Purpose§8 step 3: at mint time every ML-DSA-87 leg (signed by fips204, hedged) must verify in TWO implementations other than the signer, RustCrypto ml-dsa and libcrux-ml-dsa, with ctx "omitly-seal-v2". Sealing refuses otherwise. Chosen by the owner 2026-10-12 (#1552): aws-lc-rs supports an empty context only, and pqcrypto-mldsa is RUSTSEC-2026-0166. Portable backend only (no simd features). 0.0.11 includes the fixes for every advisory on libcrux-ml-dsa and libcrux-sha3 (RUSTSEC-2026-0076/0077/ 0125/0126/0074/0207/0208). Producer only.
FIPS postureFIPS 204 approved algorithm; libcrux-ml-dsa is NOT a CMVP-validated module (formally verified with hax, which is not a validation).
Sourcecrates/omitly-seal/src/seal_v2/mint.rs

Offline license verification

Primitivesignature · Ed25519
Implementationed25519-dalek 2
PurposeVerifies Omitly Pro license signatures fully offline, no network, no PII.
FIPS postureSame posture as the seal above — FIPS 186-5 approved algorithm, non-validated implementation.
Sourcecrates/licensing/src/lib.rs

Document / report hashing

Primitivehash
Implementationsha2 0.10
PurposeAudit-report and delivered-bytes hashing feeding the seal + license verification digests.
FIPS postureFIPS 180-4 approved algorithm; sha2 is NOT a CMVP-validated module.
Sourcecrates/omitly-seal/src/lib.rs, crates/licensing/src/lib.rs

PAdES signing — CMS SignedData

Implementationcryptographic-message-syntax 0.28
PurposeBuilds RFC 5652 CMS SignedData over a BYOC (bring-your-own-certificate) RSA or ECDSA key for PAdES interop with real-world PDF viewers (Acrobat). The crate does not fix one algorithm — RSA vs ECDSA is determined by the imported certificate. Composes with, never precedes, the Ed25519 seal (CLAUDE.md invariant #6). Algorithm floors: SHA-1-issued certs and RSA <2048 bits are refused outright; RSA <3072 warns (NIST SP 800-131A / ASD ISM). Revocation (OCSP/CRL) is NOT checked in this milestone.
Sourcesrc-tauri/src/sign.rs

X.509 certificate parsing

Implementationx509-certificate 0.25
PurposeParses/validates imported signing certificates for PAdES (algorithm floor + expiry re-checked at sign time, not just import — CLAUDE.md invariant #9).
Sourcesrc-tauri/src/sign.rs

Crypto backend for CMS/X.509

Primitiveother
Implementationring 0.17
PurposeDirect dependency of both cryptographic-message-syntax and x509-certificate (confirmed via Cargo.lock reverse-dependency trace, not assumed) — the actual crypto backend behind their CMS/X.509 operations, not just a transitive pull-through.

Bundled qpdf crypto provider

Primitiveother
Purposeqpdf sidecar crypto provider used for PDF encryption/decryption handling, including password-protected PDF open via --password-file=-. Since omitly#834, macOS builds qpdf from source with its `native` crypto provider (-DUSE_IMPLICIT_CRYPTO=OFF -DREQUIRE_CRYPTO_NATIVE=ON) — qpdf's own bundled implementation of the PDF standard security handler's RC4/AES, not OpenSSL — so the macOS bundle no longer ships libcrypto.3.dylib at all (previously Homebrew OpenSSL libcrypto.3.dylib v3.6.2; the switch was forced by libcrypto inheriting the CI runner's macOS version rather than the app's actual deployment target). Windows is unaffected and still bundles qpdf30.dll from the official qpdf v12.3.2 msvc64 zip, whose embedded strings and qpdf/external-libs release metadata identify statically linked OpenSSL v3.6.0.
FIPS postureNeither qpdf's native crypto provider (macOS) nor the bundled qpdf30.dll OpenSSL (Windows) is a CMVP-validated module. State "approved algorithms, non-validated bundled implementation" — never "FIPS compliant" — for either platform.
Sourcesrc-tauri/binaries/qpdf-aarch64-apple-darwin (native provider, macOS), src-tauri/binaries/qpdf30.dll (OpenSSL, Windows)

Signing-key seed generation

Primitivedrbg
Implementationgetrandom 0.2
PurposeSeeds every seal key. Per install (desktop), on first use: the v1 Ed25519 seed, still used for the legacy-report and PAdES paths, and, since omitly#1552 (beta.2), the seal v2 master seed (SEAL-SPEC-v2 §7.6: keystore service app.omitly.signing-key, account seal-v2, OS keystore only, no file fallback, written once). Per output: the engine CLI always, and the desktop when its seal v2 key cannot be loaded, draw a fresh 32-byte seal v2 seed (`omitly_seal::producer::ephemeral_seal_v2_keys`) held in a Zeroizing buffer, never written to disk, and recorded as signature.key_scope "one_time"; it proves the file is unchanged since sealing and identifies nothing across files. Both seal v2 seeds are derived with the §8 two-implementation key check.
FIPS postureOS-provided CSPRNG accessed via getrandom, not a crate-implemented DRBG construction — closest available CycloneDX primitive, noted here for precision.
Sourcesrc-tauri/src/redact.rs (desktop per-install seeds: v1 Ed25519 for the legacy and PAdES paths, seal v2), crates/omitly-seal/src/producer.rs (one-output seal v2 seed)

Software-update signature verification (updater)

Primitivesignature · Ed25519
Implementationminisign-verify
Purposetauri-plugin-updater verifies every downloaded update package against the minisign public key compiled into the app (src-tauri/tauri.conf.json plugins.updater.pubkey) before installing it; the update manifest comes from releases.omitly.app. Minisign signatures are Ed25519 (over a BLAKE2b-512 prehash for prehashed signatures), implemented inside minisign-verify itself. Classical signature on the software-update trust path: like licence signing it is vendor-rotatable in a release, so it is a documented exception in the PQC plan's terms, not part of the long-horizon document evidence.
FIPS postureFIPS 186-5 approved algorithm (Ed25519); minisign-verify is NOT a CMVP-validated module. BLAKE2b is not a FIPS-approved hash.
Sourcesrc-tauri/tauri.conf.json (pubkey, endpoint), src/App.tsx and src/about/AboutModal.tsx (update check)

Post-quantum

Omitly's shipped signatures (Ed25519) are classical. There is no harvest-now-decrypt-later exposure through Omitly — no document content is ever encrypted for transmission or stored by us — but sealed documents are long-lived, so the seal is where post-quantum matters. We now publish a dated, ISM-2073-aligned post-quantum transition plan: an algorithm-agile seal envelope, hybrid ECDSA P-384 + ML-DSA-87 signing with independent cross-verification at mint, opt-in RFC 3161 timestamps, and a published policy cutover ahead of the ISM's 2030 line.