diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6de223a4..0292effa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,9 @@ jobs: - name: Run tests no features run: cargo test --no-default-features + - name: Run tests with fips feature + run: cargo test --features fips + wasm: name: Run tests in wasm runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index e3059967..1455b099 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ base64 = "0.21.0" # For PEM decoding pem = { version = "3", optional = true } simple_asn1 = { version = "0.6", optional = true } +aws-lc-rs = { version = "1.6.2", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] ring = { version = "0.17.4", features = ["std"] } @@ -50,6 +51,7 @@ criterion = { version = "0.4", default-features = false } [features] default = ["use_pem"] use_pem = ["pem", "simple_asn1"] +fips = ["aws-lc-rs"] [[bench]] name = "jwt" diff --git a/README.md b/README.md index ccd4e759..1b8c6180 100644 --- a/README.md +++ b/README.md @@ -166,3 +166,7 @@ you can add some leeway to the `iat`, `exp`, and `nbf` validation by setting the Last but not least, you will need to set the algorithm(s) allowed for this token if you are not using `HS256`. Look at `examples/validation.rs` for a full working example. + +## FIPS compliance + +This library exports a feature that replaces `ring` with `aws_lc_rs` to achieve FIPS compliance. The name of the feature is `fips`. \ No newline at end of file diff --git a/src/crypto/ecdsa.rs b/src/crypto/ecdsa.rs index 99b92fc6..b0644b03 100644 --- a/src/crypto/ecdsa.rs +++ b/src/crypto/ecdsa.rs @@ -1,3 +1,9 @@ +#[cfg(feature = "fips")] +use aws_lc_rs as ring; + +#[cfg(not(feature = "fips"))] +use ring; + use ring::{rand, signature}; use crate::algorithms::Algorithm; @@ -26,6 +32,7 @@ pub(crate) fn alg_to_ec_signing(alg: Algorithm) -> &'static signature::EcdsaSign /// The actual ECDSA signing + encoding /// The key needs to be in PKCS8 format +#[cfg(not(feature = "fips"))] pub fn sign( alg: &'static signature::EcdsaSigningAlgorithm, key: &[u8], @@ -36,3 +43,17 @@ pub fn sign( let out = signing_key.sign(&rng, message)?; Ok(b64_encode(out)) } + +/// The actual ECDSA signing + encoding +/// The key needs to be in PKCS8 format +#[cfg(feature = "fips")] +pub fn sign( + alg: &'static signature::EcdsaSigningAlgorithm, + key: &[u8], + message: &[u8], +) -> Result { + let rng = rand::SystemRandom::new(); + let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, key)?; + let out = signing_key.sign(&rng, message)?; + Ok(b64_encode(out)) +} diff --git a/src/crypto/eddsa.rs b/src/crypto/eddsa.rs index 7c185347..5ca21f03 100644 --- a/src/crypto/eddsa.rs +++ b/src/crypto/eddsa.rs @@ -1,15 +1,19 @@ -use ring::signature; +#[cfg(feature = "fips")] +use aws_lc_rs as ring; + +#[cfg(not(feature = "fips"))] +use ring; use crate::algorithms::Algorithm; use crate::errors::Result; use crate::serialization::b64_encode; /// Only used internally when signing or validating EdDSA, to map from our enum to the Ring EdDSAParameters structs. -pub(crate) fn alg_to_ec_verification(alg: Algorithm) -> &'static signature::EdDSAParameters { +pub(crate) fn alg_to_ec_verification(alg: Algorithm) -> &'static ring::signature::EdDSAParameters { // To support additional key subtypes, like Ed448, we would need to match on the JWK's ("crv") // parameter. match alg { - Algorithm::EdDSA => &signature::ED25519, + Algorithm::EdDSA => &ring::signature::ED25519, _ => unreachable!("Tried to get EdDSA alg for a non-EdDSA algorithm"), } } @@ -17,7 +21,7 @@ pub(crate) fn alg_to_ec_verification(alg: Algorithm) -> &'static signature::EdDS /// The actual EdDSA signing + encoding /// The key needs to be in PKCS8 format pub fn sign(key: &[u8], message: &[u8]) -> Result { - let signing_key = signature::Ed25519KeyPair::from_pkcs8_maybe_unchecked(key)?; + let signing_key = ring::signature::Ed25519KeyPair::from_pkcs8_maybe_unchecked(key)?; let out = signing_key.sign(message); Ok(b64_encode(out)) } diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index c2957dc8..8a1fa08b 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,3 +1,9 @@ +#[cfg(feature = "fips")] +use aws_lc_rs as ring; + +#[cfg(not(feature = "fips"))] +use ring; + use ring::constant_time::verify_slices_are_equal; use ring::{hmac, signature}; diff --git a/src/crypto/rsa.rs b/src/crypto/rsa.rs index 4c97db3c..2a24537b 100644 --- a/src/crypto/rsa.rs +++ b/src/crypto/rsa.rs @@ -1,3 +1,9 @@ +#[cfg(feature = "fips")] +use aws_lc_rs as ring; + +#[cfg(not(feature = "fips"))] +use ring; + use ring::{rand, signature}; use crate::algorithms::Algorithm; @@ -41,7 +47,8 @@ pub(crate) fn sign( let key_pair = signature::RsaKeyPair::from_der(key) .map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?; - let mut signature = vec![0; key_pair.public().modulus_len()]; + let mut signature = get_signature(&key_pair); + let rng = rand::SystemRandom::new(); key_pair.sign(alg, &rng, message, &mut signature).map_err(|_| ErrorKind::RsaFailedSigning)?; @@ -60,3 +67,13 @@ pub(crate) fn verify_from_components( let res = pubkey.verify(alg, message, &signature_bytes); Ok(res.is_ok()) } + +#[cfg(feature = "fips")] +fn get_signature(key_pair: &signature::RsaKeyPair) -> Vec { + vec![0; key_pair.public_modulus_len()] +} + +#[cfg(not(feature = "fips"))] +fn get_signature(key_pair: &signature::RsaKeyPair) -> Vec { + vec![0; key_pair.public().modulus_len()] +} diff --git a/src/errors.rs b/src/errors.rs index 2edd7df5..3cc3e815 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,3 +1,8 @@ +#[cfg(feature = "fips")] +use aws_lc_rs as ring; + +#[cfg(not(feature = "fips"))] +use ring; use std::error::Error as StdError; use std::fmt; use std::result; @@ -77,7 +82,7 @@ pub enum ErrorKind { /// Some of the text was invalid UTF-8 Utf8(::std::string::FromUtf8Error), /// Something unspecified went wrong with crypto - Crypto(::ring::error::Unspecified), + Crypto(ring::error::Unspecified), } impl StdError for Error { @@ -159,14 +164,14 @@ impl From<::std::string::FromUtf8Error> for Error { } } -impl From<::ring::error::Unspecified> for Error { - fn from(err: ::ring::error::Unspecified) -> Error { +impl From for Error { + fn from(err: ring::error::Unspecified) -> Error { new_error(ErrorKind::Crypto(err)) } } -impl From<::ring::error::KeyRejected> for Error { - fn from(_err: ::ring::error::KeyRejected) -> Error { +impl From for Error { + fn from(_err: ring::error::KeyRejected) -> Error { new_error(ErrorKind::InvalidEcdsaKey) } }