Skip to content

FIPS 140-3 feasibility assessment

Recorded 2026-09-14. This page preserves a cryptography inventory and compile-only migration experiments. It does not describe a released FIPS variant or validated product build. Recheck the inventory against the selected revision and module certificate before making a deployment claim.

The recorded rustls provider swap and FIPS-feature build compiled after the specified changes; all changes were reverted. No FIPS runtime handshake or test suite was run. Direct signing, hashing, key generation, GitHub sealed boxes and SSH signing still require migration or a defined module boundary.

Classification key

  • (a) covered by swapping the rustls crypto provider or a dependency feature flag; no algorithm-level source changes.
  • (b) needs source migration to aws-lc-rs APIs; the algorithm itself is FIPS approved.
  • (c) no FIPS path without redesign or scoping the usage out of the cryptographic module boundary.

Inventory

TLS and certificates

Crate File Primitive Protects Class
opaqued crates/opaqued/src/main.rs:759 rustls ring provider, process default All daemon TLS (installed once at startup) a
opaque-approver crates/opaque-approver/src/client.rs:94,191 (also 42, 56) rustls ring provider, TLS 1.2/1.3 signature verification Pinned TLS client to the approval server a
opaque-approval crates/opaque-approval/src/approval_server.rs:421 tokio-rustls TlsAcceptor Approval HTTPS server (mobile device pairing and decisions) a
opaque-approval crates/opaque-approval/src/approval_server.rs:256 rcgen PKCS_ED25519 keygen and self-signed certificate Approval server TLS identity a (rcgen 0.14 has an aws_lc_rs backend feature)
opaque-federation-runtime crates/opaque-federation-runtime/src/export.rs:388-455 tokio-rustls TLS client, custom roots SIEM export streams a
eight crates reqwest with rustls-tls (workspace Cargo.toml:83) rustls via hyper-rustls Outbound HTTPS: OIDC, cloud providers, push, federation fetch a, with the graph caveat below
opaque-core (indirect) jsonschema 0.45 pulls reqwest 0.13 second rustls consumer Schema reference fetching a, same caveat
test harnesses crates/opaque-approval/src/approval_server.rs:759, approval_server/workstation.rs:426, factors.rs:912,987 rustls ring provider installs Test TLS setup a
opaqued crates/opaqued/Cargo.toml:43 direct ring dependency Nothing: no ring:: usage exists in the crate a (delete it)

Graph caveat for reqwest: rustls features are additive across the dependency graph. reqwest's rustls-tls feature enables hyper-rustls's ring feature, which re-enables rustls/ring even after the workspace declaration changes. The runtime provider is still whatever install_default() selects, but ring stays linked into the binary. Removing it from the graph requires switching reqwest to rustls-tls-no-provider (reqwest then uses the installed process default) and declaring tokio-rustls with default-features = false. Auditors read cargo tree; budget for this trim even though it does not change runtime behavior. Both reqwest versions (0.12 direct, 0.13 via jsonschema) need the same treatment.

Signatures

Crate File Primitive Protects Class
opaque-core crates/opaque-core/src/identity.rs:27 ed25519-dalek sign/verify Delegation identity: role assertions over Ed25519 signatures b
opaque-core crates/opaque-core/src/attest.rs:19 ed25519-dalek sign/verify Attestation signatures b
opaque-core crates/opaque-core/src/bundle.rs:22 ed25519-dalek sign/verify Federation trust bundles (opqd1 delegation tokens, trust anchors) b
opaque-core crates/opaque-core/src/workstation.rs:8 ed25519-dalek verify Workstation approval decisions b
opaqued crates/opaqued/src/identity/keys.rs:12-32 ed25519-dalek keygen from a getrandom seed Daemon identity signing key (identity.key, mode 0600) b
opaque-approval crates/opaque-approval/src/pairing/ (mod.rs:15, challenge.rs:9, store.rs:11) ed25519-dalek sign/verify Device pairing, approval decision signatures, paired-device store b
opaque-approver crates/opaque-approver/src/custody.rs:7, src/main.rs:5 ed25519-dalek signing Approver device key custody b
opaque-federation-runtime crates/opaque-federation-runtime/src/attest.rs:28, src/federation.rs:52,180 ed25519-dalek sign/verify Federation bundle signatures and trust anchors b
opaque crates/opaque/src/main.rs:5149,5333 Ed25519 key parsing CLI trust anchors and attestation keys b
opaque-approval crates/opaque-approval/src/fido2.rs:20-21 p256 ECDSA P-256 verify WebAuthn/FIDO2 ES256 assertion verification (human approval factor) b
opaque-bounded-work crates/opaque-bounded-work/src/ssh.rs:12,92 ed25519-dalek via the ssh-key crate Broker task-grant SSH certificates (issue and verify) c (see below)
opaque-core crates/opaque-core/src/evidence_checkpoint.rs:5,366,413 ed25519-dalek sign/verify Evidence checkpoint and retention receipts (bounded-work inference evidence chain) b

Ed25519 is approved in FIPS 186-5, and aws-lc-rs provides Ed25519 including under the FIPS feature, so the ed25519-dalek rows are migrations, not redesigns. Two mechanical concerns: keys are stored as raw 32-byte seeds (crates/opaqued/src/identity/keys.rs), so the migration needs seed-compatible construction on the aws-lc-rs side, and key generation must move from getrandom into the module's DRBG. Before a federal commitment, verify that the CMVP certificate revision matching aws-lc-fips-sys 0.14.x lists Ed25519 as approved; the aws-lc-rs FIPS documentation maps crate versions to module certificates.

The ssh-key row is class (c) because ssh-key 0.6 hardwires its Ed25519 backend to ed25519-dalek; there is no aws-lc-rs backend. Options, in rough order of preference: keep ssh-key for OpenSSH encoding only and drive the signature bytes through aws-lc-rs via custom signer plumbing, contribute a backend upstream, or accept the deviation and document SSH grant signing as outside the validated boundary in the interim.

Hashing and MACs

Crate File Primitive Protects Class
opaque-core crates/opaque-core/src/audit.rs:14,1004 hmac + sha2 (HMAC-SHA-256) Tamper-evident audit chain b
opaque-core crates/opaque-core/src/seal.rs:24,108 HMAC-SHA-256 Sealed state integrity b
opaque-core crates/opaque-core/src/resource_auth.rs:1051-1063 HMAC-SHA-256 Resource authority token integrity b
opaque-core crates/opaque-core/src/keyfile.rs:33 getrandom key generation HMAC key files (audit.hmac, store keys) b (module DRBG for key material)
opaque-approval crates/opaque-approval/src/fido2.rs:320-323 HMAC-SHA-256 FIDO2 credential store integrity b
opaque-approval crates/opaque-approval/src/fido2.rs:538,752,771 SHA-256 WebAuthn rpIdHash and clientDataHash verification b
opaque-approval crates/opaque-approval/src/pairing/challenge.rs:69-111, pairing/store.rs:76 SHA-256 Pairing challenge derivation, key fingerprints b
opaqued crates/opaqued/src/enclave.rs:297,334 SHA-256 Lease keys: client fingerprint and canonical parameter hash (anti param-swapping) b
opaqued crates/opaqued/src/identity/persona.rs:157, identity/provisioning.rs:340,537 SHA-256 Identity and provisioning fingerprints b
opaqued crates/opaqued/src/identity/oidc.rs:403 SHA-256 OIDC PKCE challenge b
opaque-approver crates/opaque-approver/src/client.rs:28,180 SHA-256 TLS certificate pinning fingerprint b
opaque-native-approval crates/opaque-native-approval/src/lib.rs:173-181 SHA-256 Display digest of the approval reason (UI only) b (low priority)
opaque-core crates/opaque-core/src/inference.rs:31 SHA-256 (sha256/prompt_sha256 helpers) Bounded-work inference receipt digests: profile, prompt, output, and source-snapshot hashes b
opaque-sandbox crates/opaque-sandbox/src/lib.rs:151 SHA-256 Sandbox profile fingerprint (profile_sha256 in execution evidence) b
opaque-showcase src/approval_oauth.rs, src/server.rs, src/bounded_demo.rs SHA-256 Demo PKCE and evidence hashes; showcase is sales collateral outside default members b (lowest priority)

All of these are SHA-256 or HMAC-SHA-256 through RustCrypto crates. The algorithms are approved; the implementations are not validated. The migration to aws_lc_rs::digest and aws_lc_rs::hmac is mechanical but wide. Beyond the rows above, the same SHA-256 helper appears in several more opaque-core digest sites that a complete migration must sweep: mcp.rs:457 (registry document hash), policy.rs:675, task.rs:621, operation.rs:450, identity_lifecycle.rs:185, and crates/opaque-bounded-work/src/mcp/transport.rs:355 (MCP response digest). All class (b), same treatment.

PR #87 widened this surface. opaque-providers is a default workspace member, so its connectors compile into the shipped daemon; SHA-256 now appears there in aws/action.rs:35, aws/client.rs:61 (SigV4 canonical payload hash), azure/client.rs:338, bitwarden/client.rs:147, vault/resolve.rs:189, and github/release.rs:281 (workflow hash check). It also appears in opaque-approval at remote/mod.rs:341 and remote/notices.rs:57, in opaque-federation-runtime at fleet/mod.rs:58 and export/detector.rs:45, and in opaqued at identity/lifecycle.rs:138 (token, credential, and batch digests). All class (b).

Separately, AWS request signing uses AWS Signature V4 (HMAC-SHA-256) through the aws-sigv4 1.5.1 crate (crates/opaque-providers/src/aws/client.rs, sign::v4), whose signing crypto is the AWS SDK's own backend rather than the RustCrypto stack; the canonical payload hash next to it is RustCrypto sha2. The algorithm is approved, so this is class (a) if aws-sigv4 can be pointed at aws-lc-rs and class (b) otherwise; either way, budget the aws-sigv4 backend as a distinct FIPS-scope item alongside the rustls provider swap.

JWT (already on the aws-lc-rs backend)

The workspace declares jsonwebtoken = { version = "10", features = ["aws_lc_rs", "use_pem"] } (workspace Cargo.toml:90), so these sites already run on aws-lc-rs, and cargo feature unification means the FIPS build recompiles them against the FIPS module with no source change:

Crate File Algorithm Protects
opaqued crates/opaqued/src/identity/oidc.rs:269-298 RS256 verify OIDC id_token validation
opaque-core crates/opaque-core/src/resource_auth.rs:24 RS256 verify Resource authority JWTs
opaque-approval crates/opaque-approval/src/push.rs:302 ES256 sign APNs push authentication
opaque-providers crates/opaque-providers/src/gcp/client.rs:648-650 RS256 sign GCP service account tokens
opaque-showcase src/approval_oauth.rs RS256/JWKS Demo OAuth flows

All class (a).

No FIPS path

Crate File Primitive Protects Class
opaque-providers crates/opaque-providers/src/github/crypto.rs crypto_box sealed box (X25519 + XSalsa20-Poly1305) GitHub Actions secret upload c

The wire format is mandated by the GitHub API: secrets must be encrypted to the repository's Curve25519 public key as a libsodium sealed box. Neither X25519 key agreement in this construction nor XSalsa20-Poly1305 is FIPS approved, and the format cannot change unilaterally. Treatment: document it as an external-service interoperability requirement outside the module boundary (the plaintext is a secret destined for GitHub, encrypted with GitHub's key, transported inside FIPS TLS). Deployments that cannot accept the deviation must disable the GitHub secrets feature.

Non-issues

zeroize is memory hygiene, not cryptography. getrandom uses for non-key material (session tokens at crates/opaque-web/src/security.rs:114, daemon auth tokens at crates/opaqued/src/main.rs:1078, PKCE verifiers) are acceptable as-is, though routing them through the module DRBG is cheap once it is in place. The p256 usages in crates/opaque-approval/src/factors.rs and crates/opaqued/src/provisioning_api_tests.rs are test-side signing only. No argon2, bcrypt, pbkdf2, or direct AES usage exists anywhere in the workspace.

Classification totals

  • (a) provider or feature-flag swap: 9 clusters (all TLS, rcgen, JWT, the unused ring dependency).
  • (b) source migration to aws-lc-rs, approved algorithms: 5 clusters (ed25519-dalek, p256 WebAuthn verify, sha2, hmac, key-generation RNG).
  • (c) no FIPS path without redesign or scoping: 2 clusters (GitHub sealed boxes, ssh-key Ed25519 backend).

Toolchain findings

The dev machine (macOS arm64) meets every aws-lc-fips-sys build requirement:

Tool Version Path
cmake 4.4.3 /opt/homebrew/bin/cmake
go 1.27.0 /opt/homebrew/bin/go
perl 5.34.1 /usr/bin/perl
rustc 1.95.0 (rustup)

The FIPS build also compiled clang-sys/bindgen locally, so libclang is a de facto requirement on macOS hosts (Linux x86_64/aarch64 targets ship pregenerated bindings).

Build attempts

All at cargo check level, on a warm cache. Baseline first: the untouched tree checks green in 28s.

Attempt 1, feature-only swap. Changed workspace Cargo.toml:101 from rustls = { version = "0.23.45", features = ["ring"] } to features = ["aws-lc-rs"]. Result: green, zero errors, but inert. The rustls::crypto::ring call sites still compiled because reqwest and hyper-rustls re-enable rustls/ring additively (cargo tree shows rustls 0.23.43 with both aws_lc_rs and ring features), and the runtime provider is still selected by the explicit install_default() calls.

Attempt 2, real provider swap. Additionally changed the seven rustls::crypto::ring::default_provider() references to rustls::crypto::aws_lc_rs::default_provider() (crates/opaqued/src/main.rs:759, crates/opaque-approver/src/client.rs:94,191, plus four test-harness sites in opaque-approval). Result: cargo check --workspace --all-targets green, zero errors. The entire non-FIPS swap is one feature flag and seven lines.

Attempt 3, FIPS. Changed the feature list to ["aws-lc-rs", "fips"]. Cargo resolved aws-lc-rs 1.18.1 with its fips feature and aws-lc-fips-sys 0.14.2, ran the full CMake and Go build of the FIPS module, and the whole workspace checked green in 30.75s, including --all-targets. No errors of any kind. jsonwebtoken 10.4.0 recompiled against the FIPS-enabled aws-lc-rs in the same pass, confirming feature unification carries the JWT paths onto the module.

Reverted everything afterward (the feature line and the seven source lines); cargo check --workspace is green on the reverted tree and Cargo.lock returned to byte-identical (cargo prunes the FIPS packages on re-resolve).

Caveats on what this proves: compile-level compatibility only. No runtime TLS handshake was exercised under the FIPS provider, and no cargo test run was made against the FIPS build. Given this project's track record of compile-green bugs surfacing only against a live daemon, the first migration PR should include a live handshake and approval-flow test under the FIPS provider. Separately, a FIPS 140-3 certificate covers specific operating environments; the macOS arm64 build demonstrated here is developer convenience, not a validated OE. Deployment claims should name the module certificate and its listed environments (Linux x86_64/aarch64 are the relevant ones for the daemon).

Remaining migration work

These are proposals, not implemented release capabilities. The inventory above identifies the applicable call sites.

Work Required check
Remove unused ring; select aws-lc-rs in rustls and rcgen Runtime provider selection, including certificate generation
Trim additive dependency features Both reqwest versions and tokio-rustls use the selected process provider; inspect the resolved graph
Add an explicit FIPS build variant Fail closed outside FIPS mode; qualify handshake and approval flows
Migrate SHA-256/HMAC and P-256 verification Preserve audit/seal outputs and verify existing FIDO2 vectors
Migrate Ed25519 signing and key generation Preserve seed/key formats and old signatures; use the module DRBG and verify the matching certificate's approved algorithms
Resolve SSH signing and GitHub sealed boxes Implement a suitable backend or explicitly scope/redesign the affected operation

A TLS provider swap alone does not move Opaque's audit, identity and approval cryptography into a validated module boundary.

Requirements for a future release variant

An implemented variant would need pinned module/toolchain versions, supported operating environments, runtime checks and tests beyond cargo check. The recorded toolchain requirements above are experiment evidence, not qualification of another runner. Publish distinct artifact names and verification evidence so a default binary cannot be mistaken for the variant.

Nothing in this assessment changes shipped artifacts. Use certification status and release verification for current public evidence.