From 2d3ef2702c92b81166320ec4ffb13d211cd5bb62 Mon Sep 17 00:00:00 2001 From: Dom Dwyer Date: Wed, 11 Feb 2026 17:38:53 +0100 Subject: [PATCH 1/8] feat: support "verify" with FIPS crypto backend Allow verifying cryptographic signatures using the AWS-LC's FIPS crypto backend. This change allows this library to be used in FedRAMP / US Gov deployments, which have a hard requirement on using FIPS-approved crypto modules only. The "verify-aws-fips" feature flag is functionally identical to using "verify-aws", but it selects the FIPS backend in aws-lc-rs. --- .github/workflows/rust.yml | 12 +++ Cargo.lock | 190 +++++++++++++++++++++++++++++++++-- Cargo.toml | 5 +- README.md | 2 +- examples/print-cert.rs | 6 +- src/certificate.rs | 21 +++- src/certification_request.rs | 21 +++- src/lib.rs | 19 +++- src/revocation_list.rs | 27 ++++- src/verify.rs | 11 +- tests/readcrl.rs | 6 +- tests/readcsr.rs | 6 +- tests/verify.rs | 8 +- 13 files changed, 297 insertions(+), 37 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 57458d5..fccbbe3 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -71,6 +71,7 @@ jobs: - --all-features - --features=verify - --features=verify-aws + - --features=verify-aws-fips - --features=validate steps: - uses: actions/checkout@v4 @@ -78,6 +79,17 @@ jobs: uses: dtolnay/rust-toolchain@stable - run: cargo test --locked ${{ matrix.features }} + test_fips: + name: verify-aws-fips dependency on FIPS backend only + needs: check-all-features + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + - run: "if cargo tree --features=verify-aws-fips -i aws-lc-sys; then false; else [ $? -eq 101 ]; fi" + - run: "cargo tree --features=verify-aws-fips -i aws-lc-fips-sys" + fmt: name: Rustfmt runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index 828fc71..7eec35d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "asn1-rs" version = "0.8.0-beta.1" @@ -13,7 +22,7 @@ dependencies = [ "bitvec", "deranged", "displaydoc", - "nom", + "nom 8.0.0", "num-bigint", "num-traits", "rusticata-macros", @@ -50,14 +59,29 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-fips-sys" +version = "0.13.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b00953a69b2cfb471d13d72538d2e66930832340b0f31deadd404b48c573c5" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", + "regex", +] + [[package]] name = "aws-lc-rs" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" dependencies = [ + "aws-lc-fips-sys", "aws-lc-sys", - "untrusted 0.7.1", "zeroize", ] @@ -73,6 +97,26 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.105", +] + [[package]] name = "bitflags" version = "2.9.1" @@ -102,12 +146,32 @@ dependencies = [ "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "cmake" version = "0.1.54" @@ -149,6 +213,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + [[package]] name = "fs_extra" version = "1.3.0" @@ -184,12 +254,27 @@ dependencies = [ "wasi 0.14.2+wasi-0.2.4", ] +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + [[package]] name = "hex-literal" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -212,12 +297,44 @@ version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nom" version = "8.0.0" @@ -270,12 +387,28 @@ dependencies = [ "asn1-rs", ] +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.105", +] + [[package]] name = "proc-macro2" version = "1.0.97" @@ -306,6 +439,35 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + [[package]] name = "ring" version = "0.17.14" @@ -316,17 +478,23 @@ dependencies = [ "cfg-if", "getrandom 0.2.16", "libc", - "untrusted 0.9.0", + "untrusted", "windows-sys", ] +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + [[package]] name = "rusticata-macros" version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40620447734539bcc6c64e9c66fed2410e17b46986733f1090c922d85f7105b" dependencies = [ - "nom", + "nom 8.0.0", ] [[package]] @@ -460,12 +628,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -487,6 +649,12 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.52.0" @@ -586,7 +754,7 @@ dependencies = [ "aws-lc-rs", "data-encoding", "hex-literal", - "nom", + "nom 8.0.0", "oid-registry", "ring", "rusticata-macros", diff --git a/Cargo.toml b/Cargo.toml index d53c3a6..072161f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,13 +38,14 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = [] -verify-aws = ["aws-lc-rs"] +verify-aws = ["aws-lc-rs/aws-lc-sys"] # Non-FIPS backend +verify-aws-fips = ["aws-lc-rs/fips"] # FIPS crypto backend verify = ["ring"] validate = [] [dependencies] -aws-lc-rs = { version = "1.0", optional = true } asn1-rs = { version = "0.8.0-beta.1", features=["bigint", "datetime"] } +aws-lc-rs = { version = "1.0", optional = true, default-features = false, features = ["alloc"] } data-encoding = "2.2.1" nom = "8.0" oid-registry = { version="0.9.0-beta.1", features=["crypto", "x509", "x962"] } diff --git a/README.md b/README.md index 796d450..ffd3c93 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ See also `examples/print-cert.rs`. ```rust /// Cryptographic signature verification: returns true if certificate was signed by issuer -#[cfg(any(feature = "verify", feature = "verify-aws"))] +#[cfg(any(feature = "verify", feature = "verify-aws", feature = "verify-aws-fips"))] pub fn check_signature(cert: &X509Certificate<'_>, issuer: &X509Certificate<'_>) -> bool { let issuer_public_key = issuer.public_key(); cert diff --git a/examples/print-cert.rs b/examples/print-cert.rs index b7fd7b8..aa41423 100644 --- a/examples/print-cert.rs +++ b/examples/print-cert.rs @@ -215,7 +215,11 @@ fn print_x509_info(x509: &X509Certificate) -> io::Result<()> { { println!("Unknown (feature 'validate' not enabled)"); } - #[cfg(any(feature = "verify", feature = "verify-aws"))] + #[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-aws-fips" + ))] { print!("Signature verification: "); if x509.subject() == x509.issuer() { diff --git a/src/certificate.rs b/src/certificate.rs index e901c4d..cef3396 100644 --- a/src/certificate.rs +++ b/src/certificate.rs @@ -10,7 +10,11 @@ use crate::x509::{ X509Name, X509Version, }; -#[cfg(any(feature = "verify", feature = "verify-aws"))] +#[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-aws-fips" +))] use crate::verify::verify_signature; use asn1_rs::{ Alias, BerError, BigUint, BitString, DerParser, Error, FromDer, Header, Input, @@ -100,8 +104,19 @@ impl<'a> X509Certificate<'a> { /// It is usually an intermediate authority. /// /// Not all algorithms are supported, this function is limited to what `ring` supports. - #[cfg(any(feature = "verify", feature = "verify-aws"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "verify", feature = "verify-aws"))))] + #[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-aws-fips" + ))] + #[cfg_attr( + docsrs, + doc(cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-aws-fips" + ))) + )] pub fn verify_signature( &self, public_key: Option<&SubjectPublicKeyInfo>, diff --git a/src/certification_request.rs b/src/certification_request.rs index 4829bf5..b6e4e16 100644 --- a/src/certification_request.rs +++ b/src/certification_request.rs @@ -5,7 +5,11 @@ use crate::x509::{ parse_signature_value, AlgorithmIdentifier, SubjectPublicKeyInfo, X509Name, X509Version, }; -#[cfg(any(feature = "verify", feature = "verify-aws"))] +#[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-aws-fips" +))] use crate::verify::verify_signature; use asn1_rs::{ BitString, DerParser, FromDer, Header, Input, Oid, OptTaggedImplicit, Sequence, Tag, Tagged, @@ -83,8 +87,19 @@ impl<'a> X509CertificationRequest<'a> { /// /// Uses the public key contained in the CSR, which must be the one of the entity /// requesting the certification for this verification to succeed. - #[cfg(any(feature = "verify", feature = "verify-aws"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "verify", feature = "verify-aws"))))] + #[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-aws-fips" + ))] + #[cfg_attr( + docsrs, + doc(cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-aws-fips" + ))) + )] pub fn verify_signature(&self) -> Result<(), X509Error> { let spki = &self.certification_request_info.subject_pki; verify_signature( diff --git a/src/lib.rs b/src/lib.rs index 091b533..a1b72c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,10 +99,10 @@ //! to `X509Certificate`. //! //! ```rust -//! # #[cfg(any(feature = "verify", feature = "verify-aws"))] +//! # #[cfg(any(feature = "verify", feature = "verify-aws", feature = "verify-aws-fips"))] //! # use x509_parser::certificate::X509Certificate; //! /// Cryptographic signature verification: returns true if certificate was signed by issuer -//! #[cfg(any(feature = "verify", feature = "verify-aws"))] +//! #[cfg(any(feature = "verify", feature = "verify-aws", feature = "verify-aws-fips"))] //! pub fn check_signature(cert: &X509Certificate<'_>, issuer: &X509Certificate<'_>) -> bool { //! let issuer_public_key = issuer.public_key(); //! cert @@ -162,8 +162,19 @@ pub mod time; #[cfg(feature = "validate")] #[cfg_attr(docsrs, doc(cfg(feature = "validate")))] pub mod validate; -#[cfg(any(feature = "verify", feature = "verify-aws"))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "verify", feature = "verify-aws"))))] +#[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-aws-fips" +))] +#[cfg_attr( + docsrs, + doc(cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-aws-fips" + ))) +)] pub mod verify; pub mod visitor; pub mod x509; diff --git a/src/revocation_list.rs b/src/revocation_list.rs index c20af6b..693e79d 100644 --- a/src/revocation_list.rs +++ b/src/revocation_list.rs @@ -5,9 +5,17 @@ use crate::x509::{ format_serial, parse_serial, AlgorithmIdentifier, ReasonCode, X509Name, X509Version, }; -#[cfg(any(feature = "verify", feature = "verify-aws"))] +#[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-aws-fips" +))] use crate::verify::verify_signature; -#[cfg(any(feature = "verify", feature = "verify-aws"))] +#[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-aws-fips" +))] use crate::x509::SubjectPublicKeyInfo; use asn1_rs::num_bigint::BigUint; use asn1_rs::{BitString, DerParser, FromDer, Header, Input, Sequence, Tag, Tagged}; @@ -137,8 +145,19 @@ impl<'a> CertificateRevocationList<'a> { /// `public_key` is the public key of the **signer**. /// /// Not all algorithms are supported, this function is limited to what `ring` supports. - #[cfg(any(feature = "verify", feature = "verify-aws"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "verify", feature = "verify-aws"))))] + #[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-aws-fips" + ))] + #[cfg_attr( + docsrs, + doc(cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-aws-fips" + ))) + )] pub fn verify_signature(&self, public_key: &SubjectPublicKeyInfo) -> Result<(), X509Error> { verify_signature( public_key, diff --git a/src/verify.rs b/src/verify.rs index f8a66bb..4e28d3d 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -11,9 +11,12 @@ use oid_registry::{ // Since the `signature` object is similar in ring and in aws-lc-rs, we just use simple logic // to determine which one to use. // If both verify and verify-aws features are enabled, aws will be used. -#[cfg(feature = "verify-aws")] +#[cfg(any(feature = "verify-aws", feature = "verify-aws-fips"))] use aws_lc_rs::signature; -#[cfg(all(feature = "verify", not(feature = "verify-aws")))] +#[cfg(all( + feature = "verify", + not(any(feature = "verify-aws", feature = "verify-aws-fips")) +))] use ring::signature; /// Verify the cryptographic signature of the raw data (can be a certificate, a CRL or a CSR). @@ -92,7 +95,7 @@ fn get_ec_curve_sha( _ => None, } } else { - #[cfg(feature = "verify-aws")] + #[cfg(any(feature = "verify-aws", feature = "verify-aws-fips"))] { if curve_oid == OID_NIST_EC_P521 { match sha_len { @@ -105,7 +108,7 @@ fn get_ec_curve_sha( None } } - #[cfg(not(feature = "verify-aws"))] + #[cfg(not(any(feature = "verify-aws", feature = "verify-aws-fips")))] { None } diff --git a/tests/readcrl.rs b/tests/readcrl.rs index cd483a9..206d60d 100644 --- a/tests/readcrl.rs +++ b/tests/readcrl.rs @@ -1,6 +1,10 @@ use x509_parser::prelude::*; -#[cfg(any(feature = "verify", feature = "verify-aws"))] +#[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-aws-fips" +))] #[test] fn read_crl_verify() { const CA_DATA: &[u8] = include_bytes!("../assets/ca_minimalcrl.der"); diff --git a/tests/readcsr.rs b/tests/readcsr.rs index b8013e0..f30f45f 100644 --- a/tests/readcsr.rs +++ b/tests/readcsr.rs @@ -167,7 +167,11 @@ fn test_iter_raw_values() { ); } -#[cfg(any(feature = "verify", feature = "verify-aws"))] +#[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-aws-fips" +))] #[test] fn read_csr_verify() { let pem = pem::parse_x509_pem(CSR_DATA).unwrap().1; diff --git a/tests/verify.rs b/tests/verify.rs index 867d4ea..b2e1faf 100644 --- a/tests/verify.rs +++ b/tests/verify.rs @@ -1,4 +1,8 @@ -#![cfg(any(feature = "verify", feature = "verify-aws"))] +#![cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-aws-fips" +))] use x509_parser::parse_x509_certificate; @@ -70,7 +74,7 @@ fn test_signature_verification_rsa_pss_sha512() { static P521_SELF_SIGNED_DER: &[u8] = include_bytes!("../assets/p521-selfsigned.der"); -#[cfg(feature = "verify-aws")] +#[cfg(any(feature = "verify-aws", feature = "verify-aws-fips"))] #[test] fn test_signature_verification_p521() { let (_, x509_ca) = From a2d25a9041e0dc314512c094ce88c25cd5bec8f9 Mon Sep 17 00:00:00 2001 From: Dom Dwyer Date: Tue, 24 Feb 2026 17:44:07 +0100 Subject: [PATCH 2/8] docs: CI run comments for FIPS crate detection Clarify what is checked and why. --- .github/workflows/rust.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index fccbbe3..984fd05 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -5,7 +5,7 @@ on: pull_request: merge_group: schedule: - - cron: '0 18 * * *' + - cron: "0 18 * * *" jobs: check: @@ -87,7 +87,11 @@ jobs: - uses: actions/checkout@v4 - name: Install stable toolchain uses: dtolnay/rust-toolchain@stable + # Ensure the non-FIPS aws-lc-sys crate is not part of the dependency tree. + # + # "cargo tree" exits 101 when queried for a crate that's not in the tree. - run: "if cargo tree --features=verify-aws-fips -i aws-lc-sys; then false; else [ $? -eq 101 ]; fi" + # Ensure the optional FIPS crypto module is depended. - run: "cargo tree --features=verify-aws-fips -i aws-lc-fips-sys" fmt: From a22aad7f06de8692b619665075da6580af6a1717 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Sun, 2 Aug 2026 14:50:55 -0400 Subject: [PATCH 3/8] verify: gate P-521 items to AWS-LC features --- src/verify.rs | 5 ++++- tests/verify.rs | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/verify.rs b/src/verify.rs index 4e28d3d..13f0bbb 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -2,11 +2,14 @@ use crate::prelude::*; use crate::signature_algorithm::RsaSsaPssParams; use asn1_rs::{Any, BitString, DerParser}; use oid_registry::{ - OID_EC_P256, OID_NIST_EC_P384, OID_NIST_EC_P521, OID_NIST_HASH_SHA256, OID_NIST_HASH_SHA384, + OID_EC_P256, OID_NIST_EC_P384, OID_NIST_HASH_SHA256, OID_NIST_HASH_SHA384, OID_NIST_HASH_SHA512, OID_PKCS1_RSASSAPSS, OID_PKCS1_SHA1WITHRSA, OID_PKCS1_SHA256WITHRSA, OID_PKCS1_SHA384WITHRSA, OID_PKCS1_SHA512WITHRSA, OID_SHA1_WITH_RSA, OID_SIG_ECDSA_WITH_SHA256, OID_SIG_ECDSA_WITH_SHA384, OID_SIG_ECDSA_WITH_SHA512, OID_SIG_ED25519, }; +// P-521 is only supported by aws-lc-rs +#[cfg(any(feature = "verify-aws", feature = "verify-aws-fips"))] +use oid_registry::OID_NIST_EC_P521; // Since the `signature` object is similar in ring and in aws-lc-rs, we just use simple logic // to determine which one to use. diff --git a/tests/verify.rs b/tests/verify.rs index b2e1faf..6790394 100644 --- a/tests/verify.rs +++ b/tests/verify.rs @@ -72,6 +72,7 @@ fn test_signature_verification_rsa_pss_sha512() { assert!(res.is_ok()); } +#[cfg(any(feature = "verify-aws", feature = "verify-aws-fips"))] static P521_SELF_SIGNED_DER: &[u8] = include_bytes!("../assets/p521-selfsigned.der"); #[cfg(any(feature = "verify-aws", feature = "verify-aws-fips"))] From 53a2bce017d6320194f1530e14c470cce7a7247b Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Sun, 2 Aug 2026 14:55:43 -0400 Subject: [PATCH 4/8] docs: exclude verify-aws-fips from docs.rs features The docs.rs hosts don't meet the build reqs for the aws-lc-rs FIPS feature, and FIPS is a niche configuration. --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 072161f..4bf77f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,8 @@ include = [ ] [package.metadata.docs.rs] -all-features = true +# all features except verify-aws-fips (aws-lc-fips-sys cannot build on docs.rs) +features = ["validate", "verify", "verify-aws"] rustdoc-args = ["--cfg", "docsrs"] [features] From 874cd24797bc2254ea49c7517a0d71bb8caa56fc Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Sun, 2 Aug 2026 14:57:54 -0400 Subject: [PATCH 5/8] ci: check feature powerset with cargo-hack This lets us keep comprehensive feature coverage along with an understanding of some flags being mutually exclusive in their intended use (e.g. fips and non-fips aws-lc-rs) and so don't make much sense to test together. --- .github/workflows/rust.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 984fd05..abc531c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -50,6 +50,17 @@ jobs: toolchain: ${{ matrix.rust }} - run: RUSTFLAGS="-D warnings" cargo check --locked --all-targets --all-features + feature-powerset: + name: Check feature powerset + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: taiki-e/install-action@cargo-hack + # The two aws-lc-rs backends are never used together outside of + # --all-features builds, so skip those combinations. + - run: RUSTFLAGS="-D warnings" cargo hack check --locked --feature-powerset --mutually-exclusive-features verify-aws,verify-aws-fips + test: name: Test Suite needs: check From a31fb385f4b1a86f73e7af2257bd7d3a01801fe0 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Sun, 2 Aug 2026 15:05:35 -0400 Subject: [PATCH 6/8] docs: document the verify-aws-fips feature --- README.md | 11 ++++++++--- src/lib.rs | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ffd3c93..8134f3d 100644 --- a/README.md +++ b/README.md @@ -114,9 +114,14 @@ pub fn check_signature(cert: &X509Certificate<'_>, issuer: &X509Certificate<'_>) - The `verify-aws` feature offers the same support for signature verification, but based on `aws-lc-rs` instead of `ring`. -- _Note_: if both `verify` and `verify-aws` features are enabled (which happens when using - `--all-features`), the verification will use `aws-lc-rs`. It also has the side-effect of - having a dependency on `ring`, even if it is not used. +- The `verify-aws-fips` feature is identical to `verify-aws`, but selects the FIPS-approved + crypto module of `aws-lc-rs`. Building it has additional requirements (CMake, Go, and an + executable temporary directory), see the + [aws-lc-rs requirements](https://aws.github.io/aws-lc-rs/requirements/index.html). + +- _Note_: if the `verify` feature is enabled together with `verify-aws` or `verify-aws-fips` + (which happens when using `--all-features`), the verification will use `aws-lc-rs`. It also + has the side-effect of having a dependency on `ring`, even if it is not used. - The `validate` feature adds methods to run more validation functions on the certificate structure and values using the [`Validate`](https://docs.rs/x509-parser/latest/x509_parser/validate/trait.Validate.html) trait. diff --git a/src/lib.rs b/src/lib.rs index a1b72c2..58ccfad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,9 +114,14 @@ //! - The `verify-aws` feature offers the same support for signature verification, but based on //! `aws-lc-rs` instead of `ring`. //! -//! - _Note_: if both `verify` and `verify-aws` features are enabled (which happens when using -//! `--all-features`), the verification will use `aws-lc-rs`. It also has the side-effect of -//! having a dependency on `ring`, even if it is not used. +//! - The `verify-aws-fips` feature is identical to `verify-aws`, but selects the FIPS-approved +//! crypto module of `aws-lc-rs`. Building it has additional requirements (CMake, Go, and an +//! executable temporary directory), see the +//! [aws-lc-rs requirements](https://aws.github.io/aws-lc-rs/requirements/index.html). +//! +//! - _Note_: if the `verify` feature is enabled together with `verify-aws` or `verify-aws-fips` +//! (which happens when using `--all-features`), the verification will use `aws-lc-rs`. It also +//! has the side-effect of having a dependency on `ring`, even if it is not used. //! //! - The `validate` feature adds methods to run more validation functions on the certificate structure //! and values using the [`Validate`](crate::validate::Validate) trait. From 807dc8617acc1a71f1872b792b28659c8a3bbc6b Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Sun, 2 Aug 2026 15:06:12 -0400 Subject: [PATCH 7/8] docs: add CHANGELOG entry for verify-aws-fips --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44c6716..a995d4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Added/Changed/Fixed +- Add new feature `verify-aws-fips` to use the FIPS-approved crypto module of `aws-lc-rs` (#233) + ### Thanks ## 0.18.0 From c877541378d74283922adaad1996c80141bad6ef Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Sun, 2 Aug 2026 15:18:36 -0400 Subject: [PATCH 8/8] ci: consolidate redundant all-features runs Try to avoid re-testing the same combination of things multiple times in CI. --- .github/workflows/rust.yml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index abc531c..ade2e31 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -40,7 +40,6 @@ jobs: strategy: matrix: rust: - - stable - 1.85 - nightly steps: @@ -50,6 +49,9 @@ jobs: toolchain: ${{ matrix.rust }} - run: RUSTFLAGS="-D warnings" cargo check --locked --all-targets --all-features + # Also covers `check-all-features` on stable: the powerset includes every + # combination except pairing the two aws-lc-rs backends, and the + # `--all-features` pairing is exercised by the `test_features` job. feature-powerset: name: Check feature powerset runs-on: ubuntu-latest @@ -59,16 +61,7 @@ jobs: - uses: taiki-e/install-action@cargo-hack # The two aws-lc-rs backends are never used together outside of # --all-features builds, so skip those combinations. - - run: RUSTFLAGS="-D warnings" cargo hack check --locked --feature-powerset --mutually-exclusive-features verify-aws,verify-aws-fips - - test: - name: Test Suite - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - run: cargo test --locked --all-features + - run: RUSTFLAGS="-D warnings" cargo hack check --locked --all-targets --feature-powerset --mutually-exclusive-features verify-aws,verify-aws-fips test_features: name: Test suite (with features)