---
status: amended
date: 2026-05-13
decision-makers: Constructor Fabric Steering Committee
---

# macOS FIPS 140-3 via custom rustls CryptoProvider over Apple corecrypto

**ID**: `cpt-toolkit-adr-macos-fips-corecrypto-provider`

> **ADR sequence note.** This directory ([`docs/security/fips/adrs/`](.))
> holds **FIPS-scoped ADRs only**, numbered 0001-0005 as a self-contained
> sequence. Gears Toolkit-level ADRs (unrelated to the FIPS posture) live
> separately under [`docs/adrs/toolkit/`](../../../adrs/toolkit/) with
> their own 0001-0003 sequence. The split co-locates the FIPS audit
> surface with the rest of the FIPS PRD; the two ADR locations are
> independent.

> **Amendment (2026-05-13):** The "client-side TLS only / server-side
> `KeyProvider` is a rejecting stub" scope has been superseded by
> [ADR 0004](0004-macos-server-side-tls-via-corecrypto.md). The provider
> now also supports server-side TLS and mTLS client-cert signing via the
> same corecrypto-backed path. P-521 + SHA-512 is in scope (added on both
> sign and verify sides for parity with the Windows `rustls-cng-crypto`
> provider). Every other scope statement in this ADR — preserved rustls
> state machine, no server-side `KeyProvider` for production HTTPS
> termination, AES-128/256-GCM only, no ChaCha20/x25519/ED25519 — still
> stands.

## Table of Contents

<!-- generated by `cypilot toc` -->

## Context and Problem Statement

cf-gears needs a FIPS 140-3 claim for outbound TLS on Linux, macOS, and Windows. The existing `aws-lc-rs` FIPS v2 module is validated **only on Linux** (`x86_64` / `aarch64`); on macOS its Operational Environment does not apply. Continuing to use `aws-lc-rs/fips` on macOS would mean the binary ships FIPS-validated code that is **not** valid for the running OS — a defective claim.

Apple ships its own FIPS 140-3 validated module (`corecrypto`) inside macOS, validated per OS release. The question is **how to route rustls through corecrypto** on macOS while keeping a uniform rustls-based TLS stack on Linux and Windows. Windows is addressed by a separate decision in ADR 0003.

## Decision Drivers

* Need a valid FIPS 140-3 claim on macOS for outbound TLS in `cf-gears-example-server`.
* Keep `rustls` as the single TLS state machine across all OSes — same `HttpsConnector` type, same client code path in `toolkit-http`.
* Align with the existing pattern for Windows (`rustls-cng-crypto`, also a rustls `CryptoProvider`).
* Avoid maintaining two TLS implementations (`rustls` + `native-tls`) in production code.
* Avoid an unbounded compliance audit surface: the crypto path on each OS must be obviously traceable to one validated module.

## Considered Options

* Option A — `native-tls` / `hyper-tls` on macOS (Secure Transport)
* Option B — Custom rustls `CryptoProvider` backed by Apple `corecrypto` (this ADR)
* Option C — Declare FIPS Linux-only; document macOS as non-FIPS

## Decision Outcome

Chosen option: **Option B — custom rustls `CryptoProvider`**, packaged as `cf-gears-rustls-corecrypto-provider`.

Justification:

* `rustls` is preserved on every OS. `toolkit-http`'s connector type is the same everywhere, eliminating two parallel TLS state machines and the corresponding cfg-soup in client code.
* The pattern is **directly symmetric** to the Windows plan: `rustls-cng-crypto` on Windows, `rustls-corecrypto-provider` on macOS, `aws-lc-rs/fips` on Linux. One mental model.
* Apple's `corecrypto` is the only TLS-relevant FIPS-validated module on macOS. Our provider funnels every TLS primitive (AES-GCM, SHA-2, HMAC, HKDF, ECDH, ECDSA, RSA-PSS, random) through Apple `SecKey` / `CommonCrypto` FFI — runtime-verifiable via `vmmap`/`otool`.
* Option A (`native-tls`) is rejected because Secure Transport is deprecated by Apple since macOS 10.15; its long-term availability is uncertain and its cipher-suite control surface is poorer.
* Option C is rejected because the product has explicit cross-OS FIPS requirements; "Linux only" is not acceptable.

### Consequences

* cf-gears owns and maintains a ~1500 LOC crypto-plumbing crate (`libs/rustls-corecrypto-provider`). Every primitive must be unit-tested against NIST CAVS / RFC vectors.
* TLS 1.2 + TLS 1.3 with FIPS-approved cipher suites only: AES-128/256-GCM, P-256/P-384 ECDHE, ECDSA + RSA-PSS + RSA PKCS#1 v1.5. ChaCha20, x25519, Ed25519 are **out of scope** — partly by FIPS design (ChaCha20 / x25519 / Ed25519 are not on the FIPS-Approved list per FIPS 186-5 / SP 800-186), and partly by Apple's public-API surface: `SecKey`'s `kSecAttrKeyType` exposes only NIST curves (`ECSECPrimeRandom` covers P-192/256/384/521; no Curve25519 / Edwards25519). The curve crypto for those algorithms exists inside corecrypto but is not reachable through the public Security.framework API, so even a non-FIPS variant of this provider could not offer them without leaving the rustls + corecrypto data path. Peers that demand X25519-only key-exchange will fail to handshake against this provider — see ADR 0001 amendment for the interop matrix and the rustls-side fallback options.
* The FIPS claim on macOS depends on the running OS version being covered by the current Apple `corecrypto` CMVP certificate; this must be re-verified each macOS release (CI/release-checklist task, not a one-off).
* ~~The provider only supports client-side TLS. Server-side `KeyProvider` is a rejecting stub — acceptable because cf-gears terminates HTTPS at the reverse proxy in production.~~ **Superseded by [ADR 0004](0004-macos-server-side-tls-via-corecrypto.md)**: the provider now supports server-side TLS and mTLS via a corecrypto-backed `KeyProvider` for RSA + ECDSA P-256/P-384/P-521. Reverse-proxy termination remains the standard production posture, but is no longer the only option.
* If `cargo` ever adds native target-conditional feature activation, the per-OS provider dispatch in `toolkit::bootstrap::init_crypto_provider` and `toolkit_http::tls::get_crypto_provider` could be simplified, but the provider crate itself remains the right abstraction.

### Confirmation

* Unit-level: `cargo test -p cf-gears-rustls-corecrypto-provider` (113 tests; ≥95% line coverage; NIST CAVS / RFC 4231 / RFC 5869 vectors).
* Integration: `tests/handshake_smoke.rs` performs real TLS 1.2 + TLS 1.3 handshakes against `openssl s_server`.
* Wire-level: `examples/cf-gears-fips-probe` against `https://www.howsmyssl.com/a/check` must report only FIPS-approved cipher suites and named groups in `given_cipher_suites` / `given_named_groups`.
* Runtime: `vmmap <pid>` of a cf-gears binary doing outbound TLS must show `/usr/lib/system/libcorecrypto.dylib` loaded.

## Pros and Cons of the Options

### Option A — `native-tls` / `hyper-tls` on macOS

* Good, because Secure Transport is itself a wrapper over Apple `corecrypto`, so the FIPS data path is intrinsic.
* Good, because `native-tls` is a mature off-the-shelf crate.
* Bad, because it introduces a second TLS implementation alongside `rustls` — different connector types, divergent code paths in `toolkit-http`.
* Bad, because Secure Transport is deprecated by Apple (since macOS 10.15); long-term availability uncertain.
* Bad, because `native-tls` exposes no API to restrict cipher suites or named groups — Apple decides defaults, complicating any strict FIPS profile.
* Bad, because diverges from the Windows plan (`rustls-cng-crypto`), losing architectural symmetry.

### Option B — Custom rustls `CryptoProvider` over `corecrypto` (chosen)

* Good, because `rustls` is preserved on every OS; one TLS state machine, one connector type.
* Good, because architecturally symmetric to `rustls-cng-crypto` (Windows) and `aws-lc-rs/fips` (Linux) — clean mental model.
* Good, because FIPS-approved cipher suite / named-group set is enforced **by us** at provider construction time, not at Apple's discretion.
* Good, because the Apple APIs used (`SecKey*`, `CommonCrypto`) are **not** deprecated, unlike Secure Transport.
* Neutral, because there is no existing public Rust crate for this (`rustls-corecrypto-provider` did not exist on crates.io as of 2026-05); we wrote ours.
* Bad, because cf-gears now owns ~1500 LOC of crypto-plumbing code; maintenance and audit responsibility falls on us.
* Bad, because every primitive needs careful constant-time / zero-on-drop discipline (mitigated by thin FFI wrappers — Apple corecrypto handles the constant-time guarantees internally).

### Option C — Declare FIPS Linux-only

* Good, because zero implementation cost.
* Bad, because cross-OS FIPS is a stated product requirement; this is non-conformance.
* Bad, because users running cf-gears-example-server on macOS in deployments that require FIPS would have to switch OS — not a viable customer ask.

## More Information

* Crate: [`libs/rustls-corecrypto-provider`](../../../../libs/rustls-corecrypto-provider) — see its `README.md` for scope, primitives, and validation procedure.
* Wire-level smoke: [`examples/cf-gears-fips-probe`](../../../../examples/cf-gears-fips-probe) — see its `README.md` for the four-layer verification chain.
* Reference for the pattern: [`microsoft/rustls-symcrypt`](https://github.com/microsoft/rustls-symcrypt) — the same architectural shape, backed by Windows SymCrypt instead of Apple corecrypto.
* Apple `corecrypto` CMVP certificate search: <https://csrc.nist.gov/projects/cryptographic-module-validation-program/validated-modules/search>.

## Traceability

- **DESIGN**: [`libs/toolkit/src/bootstrap/crypto.rs`](../../../../libs/toolkit/src/bootstrap/crypto.rs), [`libs/toolkit-http/src/tls.rs`](../../../../libs/toolkit-http/src/tls.rs)
- **Related ADR**: [`0002-fips-feature-target-conditional-shim`](0002-fips-feature-target-conditional-shim.md) — explains how the FIPS `Cargo` feature picks the corecrypto path on macOS without poisoning the build with aws-lc-fips.
- **Related ADR**: [`0003-windows-fips-via-rustls-cng-crypto`](0003-windows-fips-via-rustls-cng-crypto.md) — symmetric decision for Windows, routing FIPS through CNG via `rustls-cng-crypto` instead of waiting for Microsoft's `rustls-symcrypt` CMVP validation.

This decision directly addresses:

* `cpt-toolkit-nfr-fips-cross-os` — Valid FIPS 140-3 claim on Linux **and** macOS.
* `cpt-toolkit-design-tls-uniformity` — Single rustls-based TLS stack across all supported OSes.
