Repository navigation
feat: FIPS 140-3 compliance path for TLS and SSH crypto stack #900
Description
Problem Statement
OpenShell's TLS and SSH subsystems use non-FIPS-validated cryptographic libraries and default to non-FIPS-approved algorithms. On FIPS-enabled clusters (common in government, defense, and regulated-industry Kubernetes deployments), this makes OpenShell non-compliant from an audit perspective -- even though the kernel does not block the operations at runtime.
The sandbox has been split into two crates: openshell-supervisor-process (SSH, process lifecycle) and openshell-supervisor-network (proxy, L7, TLS MITM). References below reflect this split.
Specifically:
- TLS (all connections): Uses
ring 0.17via rustls. ring has no FIPS validation path. Default cipher suites include ChaCha20-Poly1305 and X25519 key exchange, neither of which are FIPS-approved. - SSH (sandbox transport): Uses russh 0.62 with a mix of aws-lc-rs, ed25519-dalek, and curve25519-dalek. The sandbox SSH server hardcodes Ed25519 host keys (
openshell-supervisor-process/src/ssh.rs:52). Default negotiation prefers ChaCha20-Poly1305 and Curve25519 key exchange. - PKI (certificate generation): Uses rcgen 0.13 backed by ring. The default algorithm (ECDSA P-256) is FIPS-approved, but the implementation module is not validated.
- Credential encryption (at rest): The
openshell-driver-db-credstorecrate performs envelope encryption using ring directly (AES-256-GCM viaring::aead, key material viaring::rand::SystemRandom). This is the most audit-exposed crypto surface outside of TLS. - JWT signing: Sandbox JWTs use EdDSA (Ed25519) for signing and validation. Ed25519 is approved only under FIPS 186-5 with a validated module.
FIPS-enabled RHEL 9 / OpenShift 4.x clusters enforce FIPS 140-3 via system-wide crypto policies. Processes using non-validated crypto modules fail compliance audits regardless of the algorithms selected. There are no existing FIPS-related issues in the tracker.
This is complementary to #899 (Platform mode / restricted SCC support) -- FIPS clusters are a subset of the managed Kubernetes deployments that issue addresses.
Proposed Design
Add a workspace-level fips Cargo feature flag that switches the crypto backend from ring to aws-lc-rs in FIPS mode (CMVP certificate #4631), restricts algorithm negotiation to FIPS-approved algorithms only, and documents the SSH layer's validation gap.
Phase 1: Feature-flagged FIPS for TLS + PKI + credential encryption + EdDSA JWT decision
Crypto provider switch -- Five binary-facing sites install the rustls CryptoProvider and would switch based on the feature flag:
// Current (all five sites):
rustls::crypto::ring::default_provider().install_default()
// With --features fips:
rustls::crypto::aws_lc_rs::default_provider().install_default()The five sites are:
openshell-server/src/cli.rs:222openshell-cli/src/main.rs:2154openshell-sandbox/src/main.rs:522(debug-rpc)openshell-sandbox/src/main.rs:568(main supervisor)openshell-sdk/src/transport.rs:186(SDK crate)
Additionally, openshell-server/src/tls.rs:21 has a direct ring import (use rustls::crypto::ring::sign;) that needs editing even after the provider swap.
Workspace dependency changes:
# Current:
rustls = { version = "0.23", default-features = false, features = ["std", "logging", "tls12", "ring"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12", "ring"] }
rcgen = { version = "0.13", features = ["crypto"] }
# With fips feature:
rustls = { version = "0.23", default-features = false, features = ["std", "logging", "tls12", "aws_lc_rs", "fips"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12", "aws-lc-rs"] }
rcgen = { version = "0.13", default-features = false, features = ["aws_lc_rs", "pem"] }TLS cipher suite restriction -- In FIPS mode, configure the provider to exclude ChaCha20-Poly1305 cipher suites and X25519 key exchange, allowing only:
- TLS 1.3:
TLS13_AES_256_GCM_SHA384,TLS13_AES_128_GCM_SHA256 - TLS 1.2:
TLS_ECDHE_ECDSA_WITH_AES_*_GCM_SHA*,TLS_ECDHE_RSA_WITH_AES_*_GCM_SHA* - Key exchange: ECDH-P256, ECDH-P384 (no X25519)
SSH algorithm restriction -- Change the sandbox SSH server's host key from Ed25519 to ECDSA-P256 and configure russh::server::Config::preferred / russh::client::Config::preferred to exclude non-FIPS algorithms:
- Host keys:
ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,rsa-sha2-256,rsa-sha2-512(nossh-ed25519) - Key exchange:
ecdh-sha2-nistp256,ecdh-sha2-nistp384,diffie-hellman-group14-sha256,diffie-hellman-group16-sha512(no Curve25519, no post-quantum mlkem) - Ciphers:
aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes128-ctr(no ChaCha20-Poly1305)
Credential encryption backend switch -- The openshell-driver-db-credstore crate performs envelope encryption (AES-256-GCM KEK/DEK, key material via ring's SystemRandom). This is the most audit-exposed crypto surface and must be included in Phase 1. The ring imports (ring::aead, ring::rand) switch to aws-lc-rs equivalents under the fips feature flag.
EdDSA JWT decision -- Sandbox JWTs use EdDSA (sandbox_jwt.rs:117, validation at :177, :181-182), keyed by Ed25519 (bootstrap/jwt.rs:42 PKCS_ED25519). Ed25519 is approved only under FIPS 186-5 with a validated module. Decision needed: pin to ES256 in FIPS mode, or confirm coverage in the target aws-lc-fips certificate. Wire-format change either way.
Transitive dependency updates -- Several workspace dependencies pull in rustls and/or ring. Each needs feature flags updated for the FIPS build:
- reqwest: switch from
rustls-tls-native-rootsto using the globally-installed CryptoProvider - sqlx with
tls-rustls-ring-native-roots: feature name explicitly pins ring, needs updating - kube with
rustls-tls: needs feature flag update - hyper-rustls: switch from
ringtoaws-lc-rsfeature - tokio-tungstenite: switch from
rustls-tls-native-rootsto aws-lc-rs backend
rustls 0.21 leg -- rustls 0.21.12 exists in the dependency graph via hyper-rustls 0.24.2 (AWS SDK path). The 0.21 leg predates pluggable CryptoProvider, so the workspace feature flag won't reach it. Either unify onto rustls 0.23 or account for a second unvalidated ring copy.
Phase 1 testing -- No test today would catch a crypto backend swap or a regression in algorithm restriction. Phase 1 needs negotiated-suite assertions, not just build-flag changes:
- Integration tests that verify the TLS cipher suites actually negotiated in FIPS mode are restricted to the approved set
- Tests that the
ring::default_provider()sites are replaced byaws_lc_rs::default_provider()when thefipsfeature is active - Negative tests confirming that non-FIPS algorithms (ChaCha20-Poly1305, X25519, Ed25519 host keys) are rejected during negotiation in FIPS mode
Go SDK -- GOFIPS140=v1.0.0 build flag for go 1.24+/1.26 toolchain is the cheapest FIPS win for the Go SDK. The Python SDK's BoringSSL gap is documented but out of scope for this issue.
UBI glibc supervisor image -- The supervisor currently uses an Alpine/musl static build (Dockerfile.supervisor). A musl static build cannot dynamically link a system OpenSSL FIPS provider. A UBI-based FIPS image variant needs a separate glibc build. The feat(build): add glibc-static supervisor libc variant commit is a step toward this, but a full UBI glibc variant is needed for the system-OpenSSL FIPS path.
Phase 2 (deferred): SSH transport FIPS validation
Phase 1 restricts SSH to FIPS-approved algorithms but the underlying implementations (ed25519-dalek, p256, aes from RustCrypto) remain non-validated modules. This is a known gap. The SSH transport only operates within the cluster's mTLS boundary (gateway-to-sandbox), providing defense-in-depth rather than being the primary trust boundary.
The NSSH1 nonce/HMAC handshake has been removed. SSH authentication is now unconditional (auth_none and auth_publickey both return Auth::Accept), with trust established via the unix socket (SO_PEERCRED) or the abstract socket in sidecar topology. This strengthens the Phase 2 deferral argument: the SSH layer carries no authentication of its own and operates entirely inside the mTLS boundary, making it even more clearly defense-in-depth.
If strict auditors require validated modules for the SSH layer, Phase 2 options include:
- Upstream russh support for aws-lc-rs as its crypto backend
- Replacing the embedded russh server with an OpenSSH subprocess (significant architecture change given the deep integration at
ssh.rs-- 1700+ lines of process spawning, PTY management, channel handling, SFTP subsystem)
Note: ssh_tunnel.rs was deleted (#1029). russh is now 0.62 (was 0.57 in the original investigation).
Scope boundaries:
- The
fipsfeature is off by default -- current behavior is preserved - Phase 1 achieves FIPS-validated crypto for all TLS operations (the external-facing attack surface), credential-at-rest encryption, and FIPS-approved algorithms for SSH
- Phase 1 includes the EdDSA JWT decision (pin to ES256 or confirm aws-lc-rs coverage)
- Phase 1 includes negotiated-suite test assertions
- Phase 1 explicitly documents the SSH validation gap
- Phase 2 is deferred to actual audit requirements
Alternatives Considered
-
System OpenSSL for everything -- Replace rustls with the
opensslcrate and russh with libssh2 or OpenSSH subprocess. True FIPS validation for all operations via RHEL 9's OpenSSL 3.x (CMVP fix(vm): bump libkrun to v1.19.6 #4282). Rejected for Phase 1: massive rewrite, loses rustls memory safety guarantees, adds system library dependency, and significantly complicates cross-platform builds. -
Partial compliance with documented exceptions -- FIPS for TLS only, document SSH as internal-only transport. This is essentially what Phase 1 achieves, but framed as the complete solution rather than a stepping stone. May not satisfy strict auditors.
-
No FIPS support -- Require FIPS-mode clusters to use custom crypto policy exceptions for OpenShell pods. Not viable for enterprise adoption in regulated environments.
-
gVisor RuntimeClass -- gVisor provides its own syscall interception and could theoretically handle crypto at the runtime level. Not applicable -- gVisor intercepts syscalls, not userspace crypto library calls.
Agent Investigation
Investigation performed with a coding agent pointed at the repo. Skills loaded: create-spike, generate-sandbox-policy. The agent traced every crypto dependency, configuration point, and algorithm choice across the workspace.
Crypto dependency map
The TLS and SSH subsystems use different crypto backends -- a critical finding for the migration path:
TLS path (all connections):
rustls 0.23.38 -> ring 0.17
rcgen 0.13 -> ring 0.17
rustls-webpki -> ring 0.17
quinn-proto -> ring 0.17
SSH path (sandbox transport):
russh 0.62 -> aws-lc-rs
russh 0.62 -> ed25519-dalek -> curve25519-dalek
russh 0.62 -> aes, cbc, ctr (RustCrypto symmetric)
russh 0.62 -> p256, p384, p521
russh 0.62 -> libcrux-ml-kem (post-quantum)
Credential encryption path:
openshell-driver-db-credstore -> ring 0.17 (aead, rand)
openshell-driver-db-credstore -> sha2 (RustCrypto, for key ID)
Code references
| Location | Description |
|---|---|
Cargo.toml:38-39 |
Workspace rustls/tokio-rustls pinned to ring feature |
Cargo.toml:41 |
rcgen 0.13 with crypto feature (ring backend) |
Cargo.toml:90 |
reqwest with rustls-tls-native-roots |
Cargo.toml:97 |
tokio-tungstenite with rustls-tls-native-roots |
Cargo.toml:105 |
jsonwebtoken 10 with aws_lc_rs (non-FIPS build) |
Cargo.toml:107 |
ring 0.17 as a direct workspace dependency |
Cargo.toml:126 |
sqlx with tls-rustls-ring-native-roots -- feature name pins ring explicitly |
Cargo.toml:129 |
kube with rustls-tls |
openshell-server/src/cli.rs:222 |
ring::default_provider().install_default() |
openshell-cli/src/main.rs:2154 |
ring::default_provider().install_default() |
openshell-sandbox/src/main.rs:522, :568 |
ring::default_provider().install_default() -- two sites (debug-rpc and main supervisor) |
openshell-sdk/src/transport.rs:186 |
ring::default_provider() -- SDK crate, fifth binary-facing site |
openshell-server/src/tls.rs:21 |
use rustls::crypto::ring::sign; -- direct ring import, needs editing even after provider swap |
openshell-server/src/tls.rs:280, :285 |
Gateway mTLS ServerConfig (no cipher suite or kx customization) |
openshell-cli/src/tls.rs:220 |
CLI mTLS ClientConfig (no cipher suite customization) |
openshell-cli/src/tls.rs:268, openshell-sdk/src/transport.rs:186 |
supported_verify_schemes() reads ring's signature_verification_algorithms directly |
openshell-supervisor-network/src/l7/tls.rs:156 |
MITM proxy ServerConfig |
openshell-supervisor-network/src/l7/tls.rs:210 |
MITM proxy upstream ClientConfig |
openshell-supervisor-network/src/l7/tls.rs:44, :56 |
MITM ephemeral CA keygen + self_signed (rcgen/ring) |
openshell-supervisor-network/src/l7/tls.rs:116, :123 |
MITM leaf keygen + signed_by (rcgen/ring) |
openshell-supervisor-process/src/ssh.rs:52 |
PrivateKey::random(&mut rng, Algorithm::Ed25519) -- hardcoded Ed25519 host key |
openshell-supervisor-process/src/ssh.rs:54 |
russh::server::Config { auth_rejection_time, ..Default::default() } -- preferred unset |
openshell-server/src/grpc/sandbox.rs:1780 |
exec_ssh_client_config() -- russh client Config with Default, preferred unset |
openshell-bootstrap/src/pki.rs:55, :75, :93 |
CA / server / client keygen via rcgen::KeyPair::generate() (ECDSA P-256 via ring) |
openshell-driver-db-credstore/src/lib.rs:29-30 |
ring::aead and ring::rand imports |
openshell-driver-db-credstore/src/lib.rs:40 |
const ALGORITHM: &str = "AES-256-GCM" |
openshell-driver-db-credstore/src/lib.rs:594 |
KEK generation via ring SystemRandom |
openshell-driver-db-credstore/src/lib.rs:663 |
Per-value DEK generation |
openshell-driver-db-credstore/src/lib.rs:726, :742 |
seal_in_place_append_tag / open_in_place |
openshell-driver-db-credstore/src/lib.rs:907 |
random_bytes_status() -- key and nonce material from ring's RNG |
openshell-driver-db-credstore/src/lib.rs:915 |
key_id() -- SHA-256 via RustCrypto sha2 |
openshell-server/src/auth/sandbox_jwt.rs:117 |
EdDSA JWT signing |
openshell-server/src/auth/sandbox_jwt.rs:177, :181-182 |
EdDSA validation pinned to Algorithm::EdDSA |
openshell-bootstrap/src/jwt.rs:42 |
PKCS_ED25519 key generation |
openshell-bootstrap/src/jwt.rs:61 |
SHA-256 key ID |
deploy/docker/Dockerfile.gateway:22 |
Gateway base: gcr.io/distroless/cc-debian13:nonroot |
deploy/docker/Dockerfile.supervisor:22 |
Supervisor base: alpine:3.22 (musl target) |
deploy/docker/Dockerfile.ci:9 |
CI base: nvcr.io/nvidia/base/ubuntu:noble-20251013 |
Non-FIPS algorithm inventory
| Operation | Current Algorithm | FIPS? | FIPS Alternative | Controlling Code |
|---|---|---|---|---|
| TLS 1.3 cipher | ChaCha20-Poly1305 (in default list) | No | AES-256-GCM, AES-128-GCM | CryptoProvider cipher suite list |
| TLS key exchange | X25519 (in default list) | No | ECDH-P256, ECDH-P384 | CryptoProvider kx_group list |
| TLS crypto module | ring 0.17 | No | aws-lc-rs (CMVP #4631) | Cargo.toml feature flags |
| SSH host key | Ed25519 (hardcoded) | No | ECDSA-P256, ECDSA-P384 | ssh.rs:52 |
| SSH key exchange | curve25519-sha256 (default preferred) | No | ecdh-sha2-nistp256/384 | russh::Config::preferred |
| SSH cipher | chacha20-poly1305 (default preferred) | No | aes256-gcm, aes128-gcm | russh::Config::preferred |
| SSH KEX (PQ) | mlkem768x25519 | No | Remove from preference list | russh::Config::preferred |
| SSH crypto module | ed25519-dalek, RustCrypto AES, p256 | No | aws-lc-fips-sys (requires upstream russh changes) | russh internals |
| PKI key generation | ECDSA P-256 via ring | Algorithm OK, module not validated | ECDSA P-256 via aws-lc-rs | rcgen backend feature |
| Credential KEK/DEK | AES-256-GCM via ring | Algorithm OK, module not validated | AES-256-GCM via aws-lc-rs | openshell-driver-db-credstore |
| Credential RNG | ring SystemRandom | Module not validated | aws-lc-rs DRBG | openshell-driver-db-credstore |
| Credential key ID | SHA-256 via RustCrypto sha2 | Algorithm OK, module not validated | SHA-256 via aws-lc-rs | credstore lib.rs:915 |
| Sandbox JWT signing | Ed25519/EdDSA | Approved under FIPS 186-5 only with validated module | ES256 or confirm aws-lc-rs EdDSA coverage | sandbox_jwt.rs, bootstrap/jwt.rs |
Existing FIPS awareness: Zero. The only mention of "FIPS" in the codebase is in an OCSF schema JSON referencing NIST FIPS 199 (information classification standard, unrelated to crypto).
Dead dependency: openshell-server/Cargo.toml:102 declares hmac = "0.12" with no remaining .rs usage in the server crate -- leftover from the removed NSSH1 handshake. Should be cleaned up regardless of FIPS work.
SSH RNG note: rand::rng() (userspace ChaCha12, OS-seeded) generates the SSH host key at ssh.rs:51. FIPS posture wants a validated DRBG for key material.
SHA-1 note: openshell-supervisor-network/src/l7/rest.rs:2274 uses SHA-1 for RFC 6455 Sec-WebSocket-Accept. This is protocol-mandated, not a security function. Permissible but will trip scanners; needs a documented exemption.
Test-only RSA note: rsa 0.9 at openshell-server/Cargo.toml:131 is a dev-dependency for test RS256 keys. Not in the shipped dependency graph.
Feature flag patterns: The codebase already uses workspace-level feature propagation (bundled-z3, dev-settings) and platform-conditional compilation via #[cfg(target_os = "linux")]. A #[cfg(feature = "fips")] pattern would be consistent.
Risks & open questions:
- aws-lc-rs FIPS build requires CMake + Go, adding build toolchain complexity
- russh's internal crypto (ed25519-dalek, p256, RustCrypto AES) is not FIPS-validated regardless of algorithm selection -- Phase 1 documents this gap
- Does russh have upstream plans for an aws-lc-rs or FIPS backend?
- Cross-compilation from macOS to linux/amd64 for FIPS container builds may require remote builds
- SSH host key change from Ed25519 to ECDSA-P256 changes fingerprint -- sandboxes are ephemeral, so should not cause persistent trust issues
- The current tree has aws-lc-rs but no aws-lc-fips-sys -- the FIPS variant is a distinct sys crate. Verify that the target aws-lc-fips-sys build matches CMVP feat(services): use stable hash-based service hostnames to relax name limits #4631
- Transitive deps (reqwest, sqlx, tokio-tungstenite, hyper-rustls, kube) each need verification with aws-lc-rs provider
- Single
fipsfeature flag vs separatefips-tls/fips-sshfor phased rollout? - The musl supervisor image blocks the system-OpenSSL FIPS path; a UBI glibc variant is needed for production FIPS compliance
- The rustls 0.21 leg (via hyper-rustls 0.24.2, AWS SDK path) predates pluggable CryptoProvider and will carry an unvalidated ring copy even after the workspace feature flag switch
- EdDSA JWT wire-format change: pinning to ES256 in FIPS mode changes the JWT signing algorithm, requiring coordinated rollout between gateway and sandbox components
Checklist
- I've reviewed existing issues and the architecture docs
- This is a design proposal, not a "please build this" request
Activity
mrunalp commentedon Aug 11, 2026
Code reference refresh against current
mainThe original investigation was done in April. The workspace has since been restructured, so every file path and line number in the issue body is stale. I re-verified each finding against
mainatc825b1f8(2026-08-11).The design and the phasing still hold. Nothing has been implemented, no crypto backend has changed, and the conclusion is unchanged: zero FIPS awareness,
ringfor all TLS, and unvalidated modules on every crypto path. What follows corrects the references and adds surfaces that did not exist in April.Structural changes since the original spike
openshell-sandboxsplit intoopenshell-supervisor-process(SSH, process) andopenshell-supervisor-network(proxy, L7, TLS MITM)crates/openshell-sandbox/src/{ssh,l7}.rsreferences have movedopenshell-supervisor-process/Cargo.toml:24,openshell-server/Cargo.toml:109)openshell-server/src/ssh_tunnel.rsdeleted (#1029, ForwardTcp routing)openshell-driver-db-credstoredeploy/docker/Dockerfile.imagessplit into per-artifact DockerfilesCorrected code reference table
Cargo.toml:38-39tokio-rustls/rustlspinned to theringfeatureCargo.toml:41rcgen 0.13withcryptofeature (ring backend)Cargo.toml:90reqwestwithrustls-tls-native-rootsCargo.toml:97tokio-tungstenitewithrustls-tls-native-rootsCargo.toml:126sqlxwithtls-rustls-ring-native-roots— the feature name now pins ring explicitlyCargo.toml:105jsonwebtoken 10withaws_lc_rs(non-FIPS build)Cargo.toml:107ring 0.17as a direct workspace dependencyCargo.toml:129kubewithrustls-tlsopenshell-server/src/cli.rs:222ring::default_provider().install_default()openshell-cli/src/main.rs:2154ring::default_provider().install_default()openshell-sandbox/src/main.rs:522,:568ring::default_provider().install_default()— two sites now (debug-rpc path and main supervisor path)openshell-sdk/src/transport.rs:186ring::default_provider()— new fifth binary-facing site, the SDK did not exist as a separate crate in Aprilopenshell-server/src/tls.rs:21use rustls::crypto::ring::sign;— a direct ring import, not just a provider install. Needs editing even after a provider swapopenshell-server/src/tls.rs:280,:285ServerConfig::builder()(no cipher suite or kx customization)openshell-cli/src/tls.rs:220ClientConfig::builder()openshell-cli/src/tls.rs:268,openshell-sdk/src/transport.rs:186supported_verify_schemes()reads ring'ssignature_verification_algorithmsdirectlyopenshell-supervisor-network/src/l7/tls.rs:156ServerConfigopenshell-supervisor-network/src/l7/tls.rs:210ClientConfigopenshell-supervisor-network/src/l7/tls.rs:44,:56self_signed(rcgen/ring)openshell-supervisor-network/src/l7/tls.rs:116,:123signed_by(rcgen/ring)openshell-supervisor-process/src/ssh.rs:52PrivateKey::random(&mut rng, Algorithm::Ed25519)— host key still hardcoded Ed25519openshell-supervisor-process/src/ssh.rs:54russh::server::Config { auth_rejection_time, ..Default::default() }—preferredstill unsetopenshell-server/src/grpc/sandbox.rs:1780exec_ssh_client_config()—russh::client::Configwith..Default::default(),preferredunsetopenshell-bootstrap/src/pki.rs:55,:75,:93rcgen::KeyPair::generate()(ECDSA P-256 via ring)deploy/docker/Dockerfile.gateway:22gcr.io/distroless/cc-debian13:nonroot(was Ubuntu)deploy/docker/Dockerfile.supervisor:22alpine:3.22, built for the musl targetdeploy/docker/Dockerfile.ci:9nvcr.io/nvidia/base/ubuntu:noble-20251013Rows to delete from the issue body
sandbox/src/ssh.rs:322— NSSH1 HMAC-SHA256 via RustCryptoserver/src/ssh_tunnel.rs:320— server-side NSSH1 HMACThe NSSH1 handshake no longer exists. SSH auth is now unconditional:
auth_noneandauth_publickeyboth returnAuth::Accept(openshell-supervisor-process/src/ssh.rs:285,:289), with trust established by the unix socket (SO_PEERCRED, seessh.rs:88) or the abstract socket in sidecar topology. The "HMAC switch" work item in Phase 1 is therefore moot.This actually strengthens the Phase 2 deferral argument in the issue body: the SSH layer carries no authentication of its own and operates entirely inside the mTLS boundary, so it is even more clearly defense-in-depth than the original framing assumed.
Leftover:
openshell-server/Cargo.toml:102still declareshmac = "0.12"with no remaining.rsusage — a dead dependency from the removed handshake, worth dropping regardless of FIPS.New findings not in the original investigation
1. Credential encryption at rest uses ring — highest audit sensitivity of anything here.
openshell-driver-db-credstoredid not exist in April. It performs envelope encryption of stored credentials:src/lib.rs:29-30use ring::aead::{AES_256_GCM, Aad, LessSafeKey, Nonce, UnboundKey}andring::rand::{SecureRandom, SystemRandom}src/lib.rs:40const ALGORITHM: &str = "AES-256-GCM"src/lib.rs:594SystemRandomsrc/lib.rs:663src/lib.rs:726,:742seal_in_place_append_tag/open_in_placesrc/lib.rs:907random_bytes_status()— all key and nonce material from ring's RNGsrc/lib.rs:915key_id()— SHA-256 via RustCryptosha2AES-256-GCM and SHA-256 are both approved algorithms, and the envelope construction is sound. But this is encryption of secrets at rest through an unvalidated module with an unvalidated DRBG — typically the first thing an auditor looks at, and a harder conversation than negotiated TLS suites. It should be its own row in the algorithm inventory and arguably belongs in Phase 1 alongside TLS rather than being deferred.
2. Two rustls major versions, so two ring linkages.
Cargo.lock:5583has rustls 0.21.12 (viahyper-rustls 0.24.2, pulled by the AWS SDK atCargo.toml:93-94) alongside 0.23.38 atCargo.lock:5595. The 0.21 leg predates the pluggableCryptoProviderAPI, so a feature flag on the workspacerustlsentry will not reach it. Either the AWS SDK dependency needs its TLS backend unified onto 0.23, or the FIPS build has to account for a second unvalidated ring copy.3. Ed25519 is load-bearing for gateway-issued tokens, not just SSH host keys. Sandbox JWTs are signed and verified with EdDSA (
openshell-server/src/auth/sandbox_jwt.rs:117, validation pinned toAlgorithm::EdDSAat:177and:181-182), keyed by an Ed25519 pair fromopenshell-bootstrap/src/jwt.rs:42(PKCS_ED25519) with a SHA-256 key ID atjwt.rs:61. Ed25519 is approved only under FIPS 186-5 and only when the validated module's certificate covers EdDSA. This needs a decision — pin to ES256 in FIPS mode, or confirm coverage in the target aws-lc-fips certificate. It is a wire-format change either way, so it deserves a line item. (OIDC verification is RS256 atauth/oidc.rs:324, which is fine.)4. SDK crypto surfaces.
sdk/go, module added since April) sets onlyMinVersion: tls.VersionTLS12atopenshell/v1/internal/grpc/conn.go:50and:68-69, with nothing wired forGOFIPS140. Since the go.mod is ongo 1.24.0/ toolchaingo1.26.4,GOFIPS140=v1.0.0gives a CMVP-validated module for essentially build-flag cost — the cheapest FIPS win anywhere in the repo.Neither is mentioned in the issue body.
5. Base images are further from FIPS than in April. The original noted Ubuntu (not UBI, no FIPS OpenSSL). It is now distroless Debian for the gateway and statically linked musl/Alpine for the supervisor. The musl static build cannot dynamically link a system OpenSSL FIPS provider at all, so the "system OpenSSL" alternative (Alternative 1 in the issue) is now foreclosed for the supervisor unless that target changes. A UBI-based FIPS image variant would need a separate glibc build of the supervisor.
6. RNG.
rand::rng()(userspace ChaCha12, OS-seeded) generates the SSH host key atopenshell-supervisor-process/src/ssh.rs:51and persistence IDs atopenshell-server/src/persistence/mod.rs:143. A FIPS posture wants a validated DRBG for key material.7. SHA-1 is present but defensible.
openshell-supervisor-network/src/l7/rest.rs:2274uses SHA-1 inwebsocket_accept_for_key()for the RFC 6455Sec-WebSocket-Acceptvalue. Protocol-mandated and not a security function, so it is permissible as a non-approved algorithm used for a non-security purpose — but it will trip automated scanners and needs that exemption written down rather than discovered during an audit.8. Test-only RSA, for the record.
rsa 0.9atopenshell-server/Cargo.toml:131is a[dev-dependencies]entry, used only to mint RS256 test keys (auth/oidc.rs:545). It is not in the shipped dependency graph and is not a FIPS concern.Suggested amendments to the plan
openshell-driver-db-credstore) into Phase 1. Same aws-lc-rs swap, and it is the most audit-exposed surface in the codebase.GOFIPS140=v1.0.0build as a standalone, low-cost item, and document the Python SDK's BoringSSL gap as out of scope.Question 6 in the original risk list ("verify aws-lc-rs 1.16.2 references a validated AWS-LC build matching CMVP #4631") still needs answering against the current pin:
Cargo.lock:310is now aws-lc-rs 1.16.3 on aws-lc-sys 0.40.0 (Cargo.lock:321), pulled transitively byjsonwebtoken,russh,quinn-proto, andrustls-webpki. There is noaws-lc-fips-sysanywhere in the tree — the FIPS variant is a distinct sys crate, so none of those consumers are in a validated configuration today.rdwj commentedon Aug 11, 2026
Retraction -- previous comment was wrong
My earlier response (now deleted) incorrectly flagged most of @mrunalp's findings as hallucinated. The error was mine: I verified against our fork's stale
main, which was ~200 commits behind upstream. After rebasing to upstreammainat3e191558, every one of the reviewer's 39 code references checks out.Apologies for the noise. The issue body has been updated to incorporate all 7 of the reviewer's suggested amendments:
openshell-driver-db-credstore) moved into Phase 1GOFIPS140and Python SDK BoringSSL gap documentedAll code references, line numbers, and the algorithm inventory table have been updated to match
c825b1f8.Lesson learned: always
git fetch upstream && git rebase upstream/mainbefore verifying code references against an external review.github-actions commentedon Aug 28, 2026
This issue has had no activity for 14 days and is now marked stale. It may be closed in 7 days if there is no further activity. Comment or remove the state:stale label to keep it open.