FIPS
Overview
OpenSSL’s FIPS support is delivered by the FIPS provider — a separate shared library that implements a validated cryptographic module. Native-ossl exposes two distinct levels of FIPS integration:
- Running code in FIPS mode — ordinary application code that uses the FIPS provider for its cryptography. No special feature flag is required.
- Implementing a FIPS provider — writing a new FIPS provider in Rust that
hooks into OpenSSL’s internal provider API. This requires the
fips-providerCargo feature and non-public OpenSSL headers.
This guide covers level 1. Level 2 is a future capability gated behind the
fips-provider feature.
Checking Whether the FIPS Provider Is Active
#![allow(unused)]
fn main() {
use native_ossl::fips;
if fips::is_running(None) {
println!("FIPS provider is loaded in the default library context");
}
}
fips::is_running wraps OSSL_PROVIDER_available. It returns true if the
FIPS provider is currently loaded and available in the specified library context.
Signature
#![allow(unused)]
fn main() {
pub fn is_running(libctx: Option<&Arc<LibCtx>>) -> bool
}
Pass None to query the default (process-wide) library context. Pass
Some(ctx) to query a specific isolated LibCtx:
#![allow(unused)]
fn main() {
use std::sync::Arc;
use native_ossl::lib_ctx::LibCtx;
use native_ossl::fips;
let ctx = Arc::new(LibCtx::new()?);
// Load FIPS and base providers into the isolated context.
// ...
if fips::is_running(Some(&ctx)) {
// The FIPS provider is active in this isolated context.
}
}
is_running is always compiled — it requires no feature flag. It is safe to
call even when the FIPS provider is not installed; it simply returns false.
Loading the FIPS Provider
is_running only reports whether the provider is already loaded. To load it,
use LibCtx and Provider:
#![allow(unused)]
fn main() {
use std::sync::Arc;
use native_ossl::lib_ctx::{LibCtx, Provider};
use native_ossl::fips;
let ctx = Arc::new(LibCtx::new()?);
let _fips = Provider::load(&ctx, c"fips")?;
let _base = Provider::load(&ctx, c"base")?;
assert!(fips::is_running(Some(&ctx)));
}
Loading the base provider alongside fips is required — base supplies
encoding, decoding, and other non-cryptographic primitives that the FIPS
provider does not implement.
Using FIPS-Approved Algorithms
Once the FIPS provider is loaded into a LibCtx, fetch algorithms from that
context to ensure FIPS-approved implementations are used:
#![allow(unused)]
fn main() {
use native_ossl::digest::DigestAlg;
use native_ossl::pkey::Pkey;
// Fetch SHA-256 from the FIPS context — guaranteed to use the FIPS provider.
let sha256 = DigestAlg::fetch_in(&ctx, c"SHA2-256", None)?;
// Load a key within the FIPS context.
let key = Pkey::<native_ossl::pkey::Private>::from_pem_in(&ctx, pem_bytes)?;
}
Property strings can further restrict algorithm selection:
#![allow(unused)]
fn main() {
// Require fips=yes explicitly on the algorithm fetch.
let sha256 = DigestAlg::fetch_in(&ctx, c"SHA2-256", Some(c"fips=yes"))?;
}
FIPS Mode vs. Implementing a FIPS Provider
These two use cases are often confused:
| Scenario | Description | Feature required |
|---|---|---|
| Running in FIPS mode | Application uses the FIPS provider for cryptography | None |
| Implementing a FIPS provider | Writing a new provider that exposes a FIPS-validated module | fips-provider |
Most applications fall into the first category. They load the FIPS provider,
verify it is active with fips::is_running, and then use normal DigestAlg,
Pkey, and other types — the FIPS provider enforces algorithm restrictions
transparently.
The fips-provider Feature
The fips-provider Cargo feature is declared in both native-ossl and
native-ossl-sys. It is intended for code that implements a FIPS provider
from Rust — accessing OpenSSL’s internal provider vtable, PROV_CTX helpers,
and ossl_prov_is_running.
[dependencies]
native-ossl = { version = "0.1", features = ["fips-provider"] }
This feature is not needed for ordinary application code that runs in FIPS mode. Enable it only if you are writing a FIPS provider implementation.
What fips-provider provides
Enabling this feature performs a second bindgen pass against the non-public
OpenSSL headers (pointed to by OPENSSL_SOURCE_DIR) and exposes:
ProviderSignatureCtx— vtable-based signature context that callsEVP_SIGNATUREfunction pointers directly, bypassingEVP_DigestSign*(which would cause a circular provider dependency inside a FIPS module).prov_ctx_new/prov_ctx_free/prov_ctx_set_handle/prov_ctx_set_libctx/prov_ctx_get_libctx— wrappers aroundossl_prov_ctx_*.check_state_ok/set_error_state—ossl_prov_is_runningandossl_set_error_statefor provider self-test reporting.Pkey<Private>::keydata()— reads thevoid *keydatapointer from anEVP_PKEY, needed to pass provider-side key material to vtable functions.ProvCtx/OSSL_CORE_HANDLE— type aliases re-exported fromnative_ossl_sys::fips_internal.
These APIs require the OpenSSL source tree because they are declared in
non-public headers (crypto/evp.h, prov/provider_ctx.h, internal/provider.h).
Set OPENSSL_SOURCE_DIR to the root of the OpenSSL source tree before building:
OPENSSL_SOURCE_DIR=~/src/openssl cargo build --features fips-provider
Platform note: The
fips-providerfeature is currently verified for x86_64 Linux only. The hand-writtenEVP_SIGNATUREvtable layout andEVP_PKEY::keydataoffset are confirmed against OpenSSL 3.5.x on x86_64; compile-timeoffsetofassertions for other architectures are planned.
Testing with the FIPS Provider
Install the FIPS provider for your system:
# Fedora / RHEL
sudo dnf install openssl-fips-provider
# Verify it is loadable
openssl list -providers -provider fips
Run FIPS-related tests:
cargo test
No feature flag is required for FIPS tests. Tests that require the FIPS provider
call fips::is_running at runtime and skip themselves if the provider is not
installed. The fips-provider Cargo feature is unrelated — it is for writing
a FIPS provider implementation, not for using one.