diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ece80d..04ddac3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,38 +15,29 @@ jobs: name: Test runs-on: ubuntu-latest steps: - - name: Check out repository - uses: actions/checkout@v4 - - name: Install toolchain - uses: dtolnay/rust-toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@v1 with: toolchain: stable - - name: Cache build artifacts - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@v2 - name: cargo test - run: cargo test + run: cargo test --features vendored # https://github.com/rust-lang/cargo/issues/6669 - name: cargo test --doc - run: cargo test --doc + run: cargo test --doc --features vendored lint: name: Lint runs-on: ubuntu-latest steps: - - name: Check out repository - uses: actions/checkout@v4 - - name: Install toolchain - uses: dtolnay/rust-toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@v1 with: toolchain: stable components: rustfmt, clippy - - name: Cache build artifacts - uses: Swatinem/rust-cache@v2 - - name: cargo fmt (check) - run: cargo fmt -- --check -l - - name: cargo clippy (warnings) - run: cargo clippy --all-targets -- -D warnings - - name: cargo clippy --no-default-features (warnings) - run: cargo clippy --no-default-features --all-targets -- -D warnings + - uses: Swatinem/rust-cache@v2 + - run: cargo fmt -- --check -l + - run: cargo clippy --features vendored --all-targets -- -D warnings + - run: cargo clippy --features vendored --no-default-features --all-targets -- -D warnings test-fips-1-1-1: name: Test using FIPS openssl 1.1.1 @@ -56,14 +47,11 @@ jobs: steps: - name: Install dependencies run: dnf install -y gcc openssl-devel - - name: Check out repository - uses: actions/checkout@v4 - - name: Install toolchain - uses: dtolnay/rust-toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@v1 with: toolchain: stable - - name: Cache build artifacts - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@v2 # Use single thread on FIPS to avoid running out of entropy - name: Run cargo test --features fips run: cargo test --tests --features fips -- --test-threads=1 @@ -76,36 +64,27 @@ jobs: steps: - name: Install dependencies run: dnf install -y gcc openssl-devel - - name: Check out repository - uses: actions/checkout@v4 - - name: Install toolchain - uses: dtolnay/rust-toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@v1 with: toolchain: stable - - name: Cache build artifacts - uses: Swatinem/rust-cache@v2 - - name: Run cargo test --features fips - run: cargo test --tests --features fips -- --test-threads=1 + - uses: Swatinem/rust-cache@v2 + - run: cargo test --tests --features fips -- --test-threads=1 coverage: name: Coverage runs-on: ubuntu-latest steps: - - name: Check out repository - uses: actions/checkout@v4 - - name: Install toolchain - uses: dtolnay/rust-toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@v1 with: toolchain: stable components: llvm-tools - - name: Cache build artifacts - uses: Swatinem/rust-cache@v2 - - name: Install cargo-llvm-cov - uses: taiki-e/install-action@cargo-llvm-cov + - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@cargo-llvm-cov - name: Generate coverage run: cargo llvm-cov --lcov --output-path lcov.info - - name: Report to codecov.io - uses: codecov/codecov-action@v5 + - uses: codecov/codecov-action@v5 with: files: lcov.info token: ${{ secrets.CODECOV_TOKEN }} diff --git a/Cargo.toml b/Cargo.toml index aad328e..b716582 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "rustls-openssl" authors = ["Tom Fay "] -version = "0.2.1" -edition = "2021" +version = "0.3.0" +edition = "2024" license = "MIT" description = "Rustls crypto provider for OpenSSL" homepage = "https://github.com/tofay/rustls-openssl" @@ -18,9 +18,11 @@ rustls = { version = "0.23.20", default-features = false } zeroize = "1.8.1" [features] -default = ["tls12"] +default = ["tls12", "prefer-post-quantum"] fips = [] tls12 = ["rustls/tls12"] +prefer-post-quantum = [] +vendored = ["openssl/vendored"] [dev-dependencies] hex = "0.4.3" diff --git a/build.rs b/build.rs index 832ca7c..238dedb 100644 --- a/build.rs +++ b/build.rs @@ -8,6 +8,7 @@ fn main() { println!("cargo:rustc-check-cfg=cfg(chacha)"); println!("cargo:rustc-check-cfg=cfg(fips_module)"); println!("cargo:rustc-check-cfg=cfg(ossl320)"); + println!("cargo:rustc-check-cfg=cfg(ossl350)"); // Determine whether to work around https://github.com/openssl/openssl/issues/23448 // according to the OpenSSL version println!("cargo:rustc-check-cfg=cfg(bugged_add_hkdf_info)"); @@ -25,6 +26,10 @@ fn main() { if version >= 0x3_02_00_00_0 { println!("cargo:rustc-cfg=ossl320"); } + + if version >= 0x3_05_00_00_0 { + println!("cargo:rustc-cfg=ossl350"); + } } // Enable the `chacha` cfg if the `OPENSSL_NO_CHACHA` OpenSSL config is not set. diff --git a/src/aead.rs b/src/aead.rs index 7233c13..7bb0d15 100644 --- a/src/aead.rs +++ b/src/aead.rs @@ -1,7 +1,7 @@ use openssl::cipher::{Cipher, CipherRef}; use openssl::cipher_ctx::CipherCtx; -use rustls::crypto::cipher::NONCE_LEN; use rustls::Error; +use rustls::crypto::cipher::NONCE_LEN; #[derive(Debug, Clone, Copy)] pub(crate) enum Algorithm { @@ -88,7 +88,7 @@ impl Algorithm { #[cfg(test)] mod test { - use wycheproof::{aead::TestFlag, TestResult}; + use wycheproof::{TestResult, aead::TestFlag}; fn test_aead(alg: super::Algorithm) { let test_name = match alg { diff --git a/src/hkdf.rs b/src/hkdf.rs index ef07b50..5bc1809 100644 --- a/src/hkdf.rs +++ b/src/hkdf.rs @@ -134,7 +134,7 @@ impl Drop for HkdfExpander { #[cfg(test)] mod test { use rustls::crypto::tls13::Hkdf; - use wycheproof::{hkdf::TestName, TestResult}; + use wycheproof::{TestResult, hkdf::TestName}; fn test_hkdf(hkdf: &dyn Hkdf, test_name: TestName) { let test_set = wycheproof::hkdf::TestSet::load(test_name).unwrap(); diff --git a/src/kx.rs b/src/kx_group/ec.rs similarity index 56% rename from src/kx.rs rename to src/kx_group/ec.rs index 597c8f0..c595d29 100644 --- a/src/kx.rs +++ b/src/kx_group/ec.rs @@ -3,25 +3,10 @@ use openssl::derive::Deriver; use openssl::ec::{EcGroup, EcKey, EcPoint, PointConversionForm}; use openssl::error::ErrorStack; use openssl::nid::Nid; -#[cfg(not(feature = "fips"))] -use openssl::pkey::Id; use openssl::pkey::{PKey, Private, Public}; use rustls::crypto::{ActiveKeyExchange, SharedSecret, SupportedKxGroup}; use rustls::{Error, NamedGroup}; -/// [Supported KeyExchange groups](SupportedKxGroup). -/// * [SECP384R1] -/// * [SECP256R1] -/// * [X25519] -/// -/// If the `fips` feature is enabled, only [SECP384R1] and [SECP256R1] are available. -pub const ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[ - SECP256R1, - SECP384R1, - #[cfg(not(feature = "fips"))] - X25519, -]; - /// `KXGroup`'s that use `openssl::ec` module with Nid's for key exchange. #[derive(Debug)] struct EcKxGroup { @@ -36,21 +21,6 @@ struct EcKeyExchange { pub_key: Vec, } -#[cfg(not(feature = "fips"))] -/// `KXGroup`` for X25519 -#[derive(Debug)] -struct X25519KxGroup {} - -#[cfg(not(feature = "fips"))] -#[derive(Debug)] -struct X25519KeyExchange { - private_key: PKey, - public_key: Vec, -} - -#[cfg(not(feature = "fips"))] -/// X25519 key exchange group as registered with [IANA](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8). -pub const X25519: &dyn SupportedKxGroup = &X25519KxGroup {}; /// secp256r1 key exchange group as registered with [IANA](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8) pub const SECP256R1: &dyn SupportedKxGroup = &EcKxGroup { name: NamedGroup::secp256r1, @@ -132,67 +102,22 @@ impl ActiveKeyExchange for EcKeyExchange { } } -#[cfg(not(feature = "fips"))] -impl SupportedKxGroup for X25519KxGroup { - fn start(&self) -> Result, Error> { - PKey::generate_x25519() - .and_then(|private_key| { - let public_key = private_key.raw_public_key()?; - Ok(Box::new(X25519KeyExchange { - private_key, - public_key, - }) as Box) - }) - .map_err(|e| Error::General(format!("OpenSSL error: {e}"))) - } - - fn name(&self) -> NamedGroup { - NamedGroup::X25519 - } -} - -#[cfg(not(feature = "fips"))] -impl ActiveKeyExchange for X25519KeyExchange { - fn complete(self: Box, peer_pub_key: &[u8]) -> Result { - PKey::public_key_from_raw_bytes(peer_pub_key, Id::X25519) - .and_then(|peer_pub_key| { - let mut deriver = Deriver::new(&self.private_key)?; - deriver.set_peer(&peer_pub_key)?; - let secret = deriver.derive_to_vec()?; - Ok(SharedSecret::from(secret.as_slice())) - }) - .map_err(|e| Error::General(format!("OpenSSL error: {e}"))) - } - - fn pub_key(&self) -> &[u8] { - &self.public_key - } - - fn group(&self) -> NamedGroup { - NamedGroup::X25519 - } -} - #[cfg(test)] mod test { use openssl::{ bn::BigNum, ec::{EcGroup, EcKey, EcPoint}, nid::Nid, - pkey::{Id, PKey}, }; - use rustls::{crypto::ActiveKeyExchange, NamedGroup}; - use wycheproof::{ecdh::TestName, TestResult}; + use rustls::{NamedGroup, crypto::ActiveKeyExchange}; + use wycheproof::{TestResult, ecdh::TestName}; - use crate::kx::EcKeyExchange; - - #[cfg(not(feature = "fips"))] - use super::X25519KeyExchange; + use super::EcKeyExchange; #[rstest::rstest] #[case::secp256r1(TestName::EcdhSecp256r1, NamedGroup::secp256r1, Nid::X9_62_PRIME256V1)] #[case::secp384r1(TestName::EcdhSecp384r1, NamedGroup::secp384r1, Nid::SECP384R1)] - fn ec(#[case] test_name: TestName, #[case] rustls_group: NamedGroup, #[case] nid: Nid) { + fn test_ec_kx(#[case] test_name: TestName, #[case] rustls_group: NamedGroup, #[case] nid: Nid) { let test_set = wycheproof::ecdh::TestSet::load(test_name).unwrap(); let ctx = openssl::bn::BigNumContext::new().unwrap(); @@ -231,45 +156,4 @@ mod test { } } } - - #[cfg(not(feature = "fips"))] - #[test] - fn x25519() { - let test_set = wycheproof::xdh::TestSet::load(wycheproof::xdh::TestName::X25519).unwrap(); - for test_group in &test_set.test_groups { - for test in &test_group.tests { - let kx = X25519KeyExchange { - private_key: PKey::private_key_from_raw_bytes(&test.private_key, Id::X25519) - .unwrap(), - public_key: Vec::new(), - }; - - let res = Box::new(kx).complete(&test.public_key); - - // OpenSSL does not support producing a zero shared secret - let zero_shared_secret = test - .flags - .contains(&wycheproof::xdh::TestFlag::ZeroSharedSecret); - - match (&test.result, zero_shared_secret) { - (TestResult::Acceptable, false) | (TestResult::Valid, _) => match res { - Ok(sharedsecret) => { - assert_eq!( - sharedsecret.secret_bytes(), - &test.shared_secret[..], - "Derived incorrect secret: {:?}", - test - ); - } - Err(e) => { - panic!("Test failed: {:?}. Error {:?}", test, e); - } - }, - _ => { - assert!(res.is_err(), "Expected error: {:?}", test); - } - } - } - } - } } diff --git a/src/kx_group/kem.rs b/src/kx_group/kem.rs new file mode 100644 index 0000000..82d20b5 --- /dev/null +++ b/src/kx_group/kem.rs @@ -0,0 +1,204 @@ +//! Key Encapsulation Mechanism (KEM) key exchange groups. +use crate::openssl_internal::kem::{PKeyRefExt, PkeyCtxExt, PkeyCtxRefKemExt, PkeyExt}; +use openssl::derive::Deriver; +use openssl::pkey::{Id, PKey, Private}; +use openssl::pkey_ctx::PkeyCtx; +use rustls::crypto::{ActiveKeyExchange, CompletedKeyExchange, SharedSecret, SupportedKxGroup}; +use rustls::{Error, NamedGroup, ProtocolVersion}; +use zeroize::Zeroize; + +/// This is the [MLKEM] key exchange. +/// +/// [MLKEM]: https://datatracker.ietf.org/doc/draft-connolly-tls-mlkem-key-agreement +pub const MLKEM768: &dyn SupportedKxGroup = &KxGroup { + named_group: NamedGroup::MLKEM768, + algorithm_name: b"mlkem768\0", +}; + +/// This is the [X25519MLKEM768] key exchange. +/// +/// [X25519MLKEM768]: +pub const X25519MLKEM768: &dyn SupportedKxGroup = &X25519HybridKxGroup(KxGroup { + named_group: NamedGroup::X25519MLKEM768, + algorithm_name: b"X25519MLKEM768\0", +}); + +/// A key exchange group based on a key encapsulation mechanism. +#[derive(Debug, Copy, Clone)] +struct KxGroup { + named_group: NamedGroup, + algorithm_name: &'static [u8], +} + +struct KeyExchange { + priv_key: PKey, + pub_key: Vec, + group: KxGroup, +} + +impl KxGroup { + /// [KxGroup::start] but returns a concrete `KeyExchange` instead of a trait object. + fn start_internal(&self) -> Result { + PkeyCtx::<()>::new_from_name(self.algorithm_name) + .and_then(|mut pkey_ctx| { + pkey_ctx.keygen_init()?; + let priv_key = pkey_ctx.keygen()?; + const OSSL_PKEY_PARAM_ENCODED_PUB_KEY: &[u8] = b"encoded-pub-key\0"; + let pub_key = priv_key.get_octet_string_param(OSSL_PKEY_PARAM_ENCODED_PUB_KEY)?; + Ok(KeyExchange { + priv_key, + pub_key, + group: *self, + }) + }) + .map_err(|e| Error::General(format!("OpenSSL keygen error: {e}"))) + } +} + +impl SupportedKxGroup for KxGroup { + fn start(&self) -> Result, Error> { + self.start_internal() + .map(|kx| Box::new(kx) as Box) + } + + fn name(&self) -> NamedGroup { + self.named_group + } + + fn usable_for_version(&self, version: ProtocolVersion) -> bool { + version == ProtocolVersion::TLSv1_3 + } + + fn ffdhe_group(&self) -> Option> { + None + } + + fn start_and_complete( + &self, + peer_pub_key: &[u8], + ) -> Result { + PKey::from_encoded_public_key(peer_pub_key, self.algorithm_name) + .and_then(|key| { + let mut ctx = PkeyCtx::new(&key)?; + ctx.encapsulate_init()?; + let (out, secret) = ctx.encapsulate_to_vec()?; + Ok(CompletedKeyExchange { + group: self.named_group, + pub_key: out, + secret: SharedSecret::from(secret.as_slice()), + }) + }) + .map_err(|e| Error::General(format!("OpenSSL encapsulation error: {e}"))) + } + + fn fips(&self) -> bool { + crate::fips::enabled() + } +} + +impl ActiveKeyExchange for KeyExchange { + fn complete(self: Box, peer_pub_key: &[u8]) -> Result { + PkeyCtx::new(&self.priv_key) + .and_then(|ctx| { + ctx.decapsulate_init()?; + let secret = ctx.decapsulate_to_vec(peer_pub_key)?; + Ok(SharedSecret::from(secret.as_slice())) + }) + .map_err(|e| Error::General(format!("OpenSSL decapsulation error: {e}"))) + } + + fn pub_key(&self) -> &[u8] { + &self.pub_key + } + + fn group(&self) -> NamedGroup { + self.group.named_group + } +} + +#[derive(Debug, Copy, Clone)] +struct X25519HybridKxGroup(KxGroup); + +struct X25519HybridKeyExchange { + inner: KeyExchange, + classical_pub_key: Vec, +} + +impl SupportedKxGroup for X25519HybridKxGroup { + fn start(&self) -> Result, Error> { + self.0.start_internal().map(|inner| { + let pub_key = inner.pub_key(); + let classical_pub_key = pub_key[pub_key.len() - 32..].to_vec(); + Box::new(X25519HybridKeyExchange { + inner, + classical_pub_key, + }) as Box + }) + } + + fn name(&self) -> NamedGroup { + self.0.named_group + } + + fn usable_for_version(&self, version: ProtocolVersion) -> bool { + self.0.usable_for_version(version) + } + + fn ffdhe_group(&self) -> Option> { + None + } + + fn start_and_complete(&self, peer_pub_key: &[u8]) -> Result { + self.0.start_and_complete(peer_pub_key) + } + + fn fips(&self) -> bool { + crate::fips::enabled() + } +} + +impl ActiveKeyExchange for X25519HybridKeyExchange { + fn complete(self: Box, peer_pub_key: &[u8]) -> Result { + Box::new(self.inner).complete(peer_pub_key) + } + + fn pub_key(&self) -> &[u8] { + &self.inner.pub_key + } + + fn group(&self) -> NamedGroup { + self.inner.group.named_group + } + + fn hybrid_component(&self) -> Option<(NamedGroup, &[u8])> { + Some((NamedGroup::X25519, &self.classical_pub_key)) + } + + fn complete_hybrid_component( + self: Box, + peer_pub_key: &[u8], + ) -> Result { + PKey::public_key_from_raw_bytes(peer_pub_key, Id::X25519) + .and_then(|peer_pub_key| { + // does openssl provide a way to get the classical private key like liboqs? + // // get the private part of the key + // const OQS_HYBRID_PKEY_PARAM_CLASSICAL_PRIV_KEY: &[u8] = + // b"hybrid_classical_priv\0"; + // let mut private_bytes = self + // .priv_key + // .get_octet_string_param(OQS_HYBRID_PKEY_PARAM_CLASSICAL_PRIV_KEY)?; + let mut private_bytes = self.inner.priv_key.raw_private_key()?; + let priv_key = PKey::private_key_from_raw_bytes( + &private_bytes[private_bytes.len() - 32..], + Id::X25519, + )?; + private_bytes.zeroize(); + + let mut deriver = Deriver::new(&priv_key)?; + deriver.set_peer(&peer_pub_key)?; + let secret = deriver.derive_to_vec()?; + Ok(SharedSecret::from(secret.as_slice())) + }) + .map_err(|e| Error::General(format!("OpenSSL error: {e}"))) + } +} diff --git a/src/kx_group/mod.rs b/src/kx_group/mod.rs new file mode 100644 index 0000000..fc78a0a --- /dev/null +++ b/src/kx_group/mod.rs @@ -0,0 +1,56 @@ +//! Key exchange groups using OpenSSL +use rustls::crypto::SupportedKxGroup; + +mod ec; +pub use ec::{SECP256R1, SECP384R1}; + +#[cfg(not(feature = "fips"))] +mod x25519; +#[cfg(not(feature = "fips"))] +pub use x25519::X25519; + +#[cfg(ossl350)] +mod kem; +#[cfg(ossl350)] +pub use kem::{MLKEM768, X25519MLKEM768}; + +/// Key exchanges enabled by default by this provider: +/// * [X25519MLKEM768] (OpenSSL 3.5+) +/// * [X25519] (if fips feature not enabled) +/// * [SECP384R1] +/// * [SECP256R1] +/// +/// If the `prefer-post-quantum` feature is enabled, X25519MLKEM768 will +/// be the first group offered, otherwise it will be the last. +pub static DEFAULT_KX_GROUPS: &[&dyn SupportedKxGroup] = &[ + #[cfg(all(ossl350, feature = "prefer-post-quantum"))] + X25519MLKEM768, + #[cfg(not(feature = "fips"))] + X25519, + SECP256R1, + SECP384R1, + #[cfg(all(ossl350, not(feature = "prefer-post-quantum")))] + X25519MLKEM768, +]; + +/// All key exchanges supported by this provider: +/// * [X25519MLKEM768] (OpenSSL 3.5+) +/// * [X25519] (if fips feature not enabled) +/// * [SECP384R1] +/// * [SECP256R1] +/// * [MLKEM768] (OpenSSL 3.5+) +/// +/// If the `prefer-post-quantum` feature is enabled, X25519MLKEM768 will +/// be the first group offered, otherwise it will be the last. +pub static ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[ + #[cfg(all(ossl350, feature = "prefer-post-quantum"))] + X25519MLKEM768, + #[cfg(not(feature = "fips"))] + X25519, + SECP256R1, + SECP384R1, + #[cfg(all(ossl350, not(feature = "prefer-post-quantum")))] + X25519MLKEM768, + #[cfg(ossl350)] + MLKEM768, +]; diff --git a/src/kx_group/x25519.rs b/src/kx_group/x25519.rs new file mode 100644 index 0000000..b4f7835 --- /dev/null +++ b/src/kx_group/x25519.rs @@ -0,0 +1,106 @@ +use openssl::derive::Deriver; +use openssl::pkey::Id; +use openssl::pkey::{PKey, Private}; +use rustls::crypto::{ActiveKeyExchange, SharedSecret, SupportedKxGroup}; +use rustls::{Error, NamedGroup}; + +/// `KXGroup`` for X25519 +#[derive(Debug)] +struct X25519KxGroup {} + +#[derive(Debug)] +struct X25519KeyExchange { + private_key: PKey, + public_key: Vec, +} + +/// X25519 key exchange group as registered with [IANA](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8). +pub const X25519: &dyn SupportedKxGroup = &X25519KxGroup {}; + +impl SupportedKxGroup for X25519KxGroup { + fn start(&self) -> Result, Error> { + PKey::generate_x25519() + .and_then(|private_key| { + let public_key = private_key.raw_public_key()?; + Ok(Box::new(X25519KeyExchange { + private_key, + public_key, + }) as Box) + }) + .map_err(|e| Error::General(format!("OpenSSL error: {e}"))) + } + + fn name(&self) -> NamedGroup { + NamedGroup::X25519 + } +} + +impl ActiveKeyExchange for X25519KeyExchange { + fn complete(self: Box, peer_pub_key: &[u8]) -> Result { + PKey::public_key_from_raw_bytes(peer_pub_key, Id::X25519) + .and_then(|peer_pub_key| { + let mut deriver = Deriver::new(&self.private_key)?; + deriver.set_peer(&peer_pub_key)?; + let secret = deriver.derive_to_vec()?; + Ok(SharedSecret::from(secret.as_slice())) + }) + .map_err(|e| Error::General(format!("OpenSSL error: {e}"))) + } + + fn pub_key(&self) -> &[u8] { + &self.public_key + } + + fn group(&self) -> NamedGroup { + NamedGroup::X25519 + } +} + +#[cfg(test)] +mod test { + use openssl::pkey::{Id, PKey}; + use rustls::crypto::ActiveKeyExchange; + use wycheproof::TestResult; + + use super::X25519KeyExchange; + + #[test] + fn x25519() { + let test_set = wycheproof::xdh::TestSet::load(wycheproof::xdh::TestName::X25519).unwrap(); + for test_group in &test_set.test_groups { + for test in &test_group.tests { + let kx = X25519KeyExchange { + private_key: PKey::private_key_from_raw_bytes(&test.private_key, Id::X25519) + .unwrap(), + public_key: Vec::new(), + }; + + let res = Box::new(kx).complete(&test.public_key); + + // OpenSSL does not support producing a zero shared secret + let zero_shared_secret = test + .flags + .contains(&wycheproof::xdh::TestFlag::ZeroSharedSecret); + + match (&test.result, zero_shared_secret) { + (TestResult::Acceptable, false) | (TestResult::Valid, _) => match res { + Ok(sharedsecret) => { + assert_eq!( + sharedsecret.secret_bytes(), + &test.shared_secret[..], + "Derived incorrect secret: {:?}", + test + ); + } + Err(e) => { + panic!("Test failed: {:?}. Error {:?}", test, e); + } + }, + _ => { + assert!(res.is_err(), "Expected error: {:?}", test); + } + } + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 33eb20a..330bf67 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,11 +29,15 @@ //! //! In descending order of preference: //! +//! * X25519MLKEM768 (OpenSSL 3.5+) //! * SECP384R1 //! * SECP256R1 //! * X25519 +//! * MLKEM768 (OpenSSL 3.5+) //! //! If the `fips` feature is enabled then X25519 will not be available. +//! If the `prefer-post-quantum` feature is enabled, X25519MLKEM768 will be the first group offered, otherwise it will be the last. +//! MLKEM768 is not offered by default, but can be used by specifying it in the `custom_provider()` function. //! //! ## Usage //! @@ -42,7 +46,7 @@ //! ```toml //! [dependencies] //! rustls = { version = "0.23.0", features = ["tls12", "std"], default-features = false } -//! rustls_openssl = "0.1.0" +//! rustls_openssl = "0.3.0" //! ``` //! //! ### Configuration @@ -52,17 +56,19 @@ //! //! # Features //! - `tls12`: Enables TLS 1.2 cipher suites. Enabled by default. +//! - `prefer-post-quantum`: Enables X25519MLKEM768 as the first key exchange group. Enabled by default. //! - `fips`: Enabling this feature removes non-FIPS-approved cipher suites and key exchanges. Disabled by default. See [fips]. +//! - `vendored`: Enables vendored OpenSSL. Disabled by default. #![warn(missing_docs)] use openssl::rand::rand_priv_bytes; -use rustls::crypto::{CryptoProvider, GetRandomFailed, SupportedKxGroup}; use rustls::SupportedCipherSuite; +use rustls::crypto::{CryptoProvider, GetRandomFailed, SupportedKxGroup}; mod aead; mod hash; mod hkdf; mod hmac; -mod kx; +pub mod kx_group; mod openssl_internal; #[cfg(feature = "tls12")] mod prf; @@ -89,18 +95,10 @@ pub mod cipher_suite { pub use super::tls13::{TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384}; } -pub use kx::ALL_KX_GROUPS; - -pub mod kx_group { - //! Supported key exchange groups. - #[cfg(not(feature = "fips"))] - pub use super::kx::X25519; - pub use super::kx::{SECP256R1, SECP384R1}; -} pub use signer::KeyProvider; pub use verify::SUPPORTED_SIG_ALGS; -/// Returns an OpenSSL-based [CryptoProvider] using all available cipher suites ([ALL_CIPHER_SUITES]) and key exchange groups ([ALL_KX_GROUPS]). +/// Returns an OpenSSL-based [CryptoProvider] using default available cipher suites ([ALL_CIPHER_SUITES]) and key exchange groups ([ALL_KX_GROUPS]). /// /// Sample usage: /// ```rust @@ -124,7 +122,7 @@ pub use verify::SUPPORTED_SIG_ALGS; pub fn default_provider() -> CryptoProvider { CryptoProvider { cipher_suites: ALL_CIPHER_SUITES.to_vec(), - kx_groups: ALL_KX_GROUPS.to_vec(), + kx_groups: kx_group::DEFAULT_KX_GROUPS.to_vec(), signature_verification_algorithms: SUPPORTED_SIG_ALGS, secure_random: &SecureRandom, key_provider: &KeyProvider, diff --git a/src/openssl_internal/hpke.rs b/src/openssl_internal/hpke.rs index 529dbb8..703a3ab 100644 --- a/src/openssl_internal/hpke.rs +++ b/src/openssl_internal/hpke.rs @@ -2,7 +2,7 @@ #![allow(unused)] #![allow(non_camel_case_types)] use std::{ - ffi::{c_char, CString}, + ffi::{CString, c_char}, ptr, }; @@ -11,7 +11,7 @@ use openssl::{ error::ErrorStack, pkey::{PKey, PKeyRef, Private}, }; -use openssl_sys::{c_int, EVP_PKEY, OSSL_LIB_CTX}; +use openssl_sys::{EVP_PKEY, OSSL_LIB_CTX, c_int}; use super::cvt; @@ -62,16 +62,16 @@ const OSSL_HPKE_SUITE_DEFAULT: OSSL_HPKE_SUITE = OSSL_HPKE_SUITE { aead_id: OSSL_HPKE_AEAD_ID_AES_GCM_128, }; -extern "C" { - fn OSSL_HPKE_CTX_new( +unsafe extern "C" { + unsafe fn OSSL_HPKE_CTX_new( mode: c_int, suite: OSSL_HPKE_SUITE, role: c_int, libctx: *mut OSSL_LIB_CTX, propq: *const c_char, ) -> *mut OSSL_HPKE_CTX; - fn OSSL_HPKE_CTX_free(ctx: *mut OSSL_HPKE_CTX); - fn OSSL_HPKE_encap( + unsafe fn OSSL_HPKE_CTX_free(ctx: *mut OSSL_HPKE_CTX); + unsafe fn OSSL_HPKE_encap( ctx: *mut OSSL_HPKE_CTX, enc: *mut u8, enclen: *mut usize, @@ -80,7 +80,7 @@ extern "C" { info: *const u8, infolen: usize, ) -> c_int; - fn OSSL_HPKE_seal( + unsafe fn OSSL_HPKE_seal( ctx: *mut OSSL_HPKE_CTX, ct: *mut u8, ctlen: *mut usize, @@ -89,7 +89,7 @@ extern "C" { pt: *const u8, ptlen: usize, ) -> c_int; - fn OSSL_HPKE_keygen( + unsafe fn OSSL_HPKE_keygen( suite: OSSL_HPKE_SUITE, pub_: *mut u8, publen: *mut usize, @@ -99,7 +99,7 @@ extern "C" { libctx: *mut OSSL_LIB_CTX, propq: *const c_char, ) -> c_int; - fn OSSL_HPKE_decap( + unsafe fn OSSL_HPKE_decap( ctx: *mut OSSL_HPKE_CTX, enc: *const u8, enclen: usize, @@ -107,7 +107,7 @@ extern "C" { info: *const u8, infolen: usize, ) -> c_int; - fn OSSL_HPKE_open( + unsafe fn OSSL_HPKE_open( ctx: *mut OSSL_HPKE_CTX, pt: *mut u8, ptlen: *mut usize, @@ -116,27 +116,34 @@ extern "C" { ct: *const u8, ctlen: usize, ) -> c_int; - fn OSSL_HPKE_export( + unsafe fn OSSL_HPKE_export( ctx: *mut OSSL_HPKE_CTX, secret: *mut u8, secretlen: usize, label: *const u8, labellen: usize, ) -> c_int; - fn OSSL_HPKE_CTX_set1_authpriv(ctx: *mut OSSL_HPKE_CTX, priv_: *mut EVP_PKEY) -> c_int; - fn OSSL_HPKE_CTX_set1_authpub(ctx: *mut OSSL_HPKE_CTX, pub_: *const u8, publen: usize) - -> c_int; - fn OSSL_HPKE_CTX_set1_psk( + unsafe fn OSSL_HPKE_CTX_set1_authpriv(ctx: *mut OSSL_HPKE_CTX, priv_: *mut EVP_PKEY) -> c_int; + unsafe fn OSSL_HPKE_CTX_set1_authpub( + ctx: *mut OSSL_HPKE_CTX, + pub_: *const u8, + publen: usize, + ) -> c_int; + unsafe fn OSSL_HPKE_CTX_set1_psk( ctx: *mut OSSL_HPKE_CTX, pskid: *const c_char, psk: *const u8, psklen: usize, ) -> c_int; - fn OSSL_HPKE_CTX_set1_ikme(ctx: *mut OSSL_HPKE_CTX, ikme: *const u8, ikmelen: usize) -> c_int; - fn OSSL_HPKE_CTX_set_seq(ctx: *mut OSSL_HPKE_CTX, seq: u64) -> c_int; - fn OSSL_HPKE_CTX_get_seq(ctx: *mut OSSL_HPKE_CTX, seq: *mut u64) -> c_int; - fn OSSL_HPKE_suite_check(suite: OSSL_HPKE_SUITE) -> c_int; - fn OSSL_HPKE_get_grease_value( + unsafe fn OSSL_HPKE_CTX_set1_ikme( + ctx: *mut OSSL_HPKE_CTX, + ikme: *const u8, + ikmelen: usize, + ) -> c_int; + unsafe fn OSSL_HPKE_CTX_set_seq(ctx: *mut OSSL_HPKE_CTX, seq: u64) -> c_int; + unsafe fn OSSL_HPKE_CTX_get_seq(ctx: *mut OSSL_HPKE_CTX, seq: *mut u64) -> c_int; + unsafe fn OSSL_HPKE_suite_check(suite: OSSL_HPKE_SUITE) -> c_int; + unsafe fn OSSL_HPKE_get_grease_value( suite_in: *const OSSL_HPKE_SUITE, suite: *mut OSSL_HPKE_SUITE, enc: *mut u8, @@ -146,10 +153,10 @@ extern "C" { libctx: *mut OSSL_LIB_CTX, propq: *const c_char, ) -> c_int; - fn OSSL_HPKE_str2suite(str_: *const c_char, suite: *mut OSSL_HPKE_SUITE) -> c_int; - fn OSSL_HPKE_get_ciphertext_size(suite: OSSL_HPKE_SUITE, clearlen: usize) -> usize; - fn OSSL_HPKE_get_public_encap_size(suite: OSSL_HPKE_SUITE) -> usize; - fn OSSL_HPKE_get_recommended_ikmelen(suite: OSSL_HPKE_SUITE) -> usize; + unsafe fn OSSL_HPKE_str2suite(str_: *const c_char, suite: *mut OSSL_HPKE_SUITE) -> c_int; + unsafe fn OSSL_HPKE_get_ciphertext_size(suite: OSSL_HPKE_SUITE, clearlen: usize) -> usize; + unsafe fn OSSL_HPKE_get_public_encap_size(suite: OSSL_HPKE_SUITE) -> usize; + unsafe fn OSSL_HPKE_get_recommended_ikmelen(suite: OSSL_HPKE_SUITE) -> usize; } /// HPKE authentication modes. diff --git a/src/openssl_internal/kem.rs b/src/openssl_internal/kem.rs new file mode 100644 index 0000000..b6625c2 --- /dev/null +++ b/src/openssl_internal/kem.rs @@ -0,0 +1,242 @@ +//! OpenSSL kem bindings +use std::ffi::{c_char, c_uchar}; +use std::ptr; + +use foreign_types::{ForeignType, ForeignTypeRef}; +use openssl::{ + error::ErrorStack, + pkey::{PKey, PKeyRef, Public}, + pkey_ctx::{PkeyCtx, PkeyCtxRef}, +}; +use openssl_sys::{EVP_PKEY, EVP_PKEY_CTX, EVP_PKEY_new, OSSL_LIB_CTX, OSSL_PARAM, c_int}; + +use super::{cvt, cvt_p}; + +/// Extension trait for [`PkeyCtxRef`] to support key encapsulation mechanism (KEM) operations. +pub(crate) trait PkeyCtxRefKemExt { + /// Initializes the encapsulation operation. + fn encapsulate_init(&self) -> Result<(), ErrorStack>; + /// Returns the encapsulated key and the shared secret. + fn encapsulate_to_vec(&mut self) -> Result<(Vec, Vec), ErrorStack>; + /// Initializes the decapsulation operation. + fn decapsulate_init(&self) -> Result<(), ErrorStack>; + /// Returns the shared secret from the encapsulated key. + fn decapsulate_to_vec(&self, enc: &[u8]) -> Result, ErrorStack>; +} + +pub(crate) trait PkeyCtxExt: Sized { + /// Creates a new [`PkeyCtx`] from the algorithm name. + /// The algorithm name is a static, null-terminated, string that identifies the algorithm to use. + fn new_from_name(name: &'static [u8]) -> Result; +} + +pub(crate) trait PkeyExt: Sized { + /// Creates a new [`PKey`] from an encoded public key for the specified algorithm. + fn from_encoded_public_key( + encoded_public_key: &[u8], + algorithm_name: &'static [u8], + ) -> Result; +} + +pub(crate) trait PKeyRefExt { + /// Returns the octet string parameter for the specified key name. + fn get_octet_string_param(&self, key_name: &[u8]) -> Result, ErrorStack>; +} + +impl PkeyCtxRefKemExt for PkeyCtxRef { + fn encapsulate_init(&self) -> Result<(), ErrorStack> { + unsafe { + cvt(EVP_PKEY_encapsulate_init(self.as_ptr(), ptr::null()))?; + } + + Ok(()) + } + + fn encapsulate_to_vec(&mut self) -> Result<(Vec, Vec), ErrorStack> { + let mut out_len = 0; + let mut secret_len = 0; + + unsafe { + cvt(EVP_PKEY_encapsulate( + self.as_ptr(), + ptr::null_mut(), + &mut out_len, + ptr::null_mut(), + &mut secret_len, + ))?; + } + + let mut out = vec![0; out_len]; + let mut secret = vec![0; secret_len]; + + unsafe { + cvt(EVP_PKEY_encapsulate( + self.as_ptr(), + out.as_mut_ptr().cast(), + &mut out_len, + secret.as_mut_ptr().cast(), + &mut secret_len, + ))?; + } + + Ok((out, secret)) + } + + fn decapsulate_init(&self) -> Result<(), ErrorStack> { + unsafe { + cvt(EVP_PKEY_decapsulate_init(self.as_ptr(), ptr::null()))?; + } + + Ok(()) + } + + fn decapsulate_to_vec(&self, enc: &[u8]) -> Result, ErrorStack> { + let mut unwrapped_len = 0; + + unsafe { + cvt(EVP_PKEY_decapsulate( + self.as_ptr(), + ptr::null_mut(), + &mut unwrapped_len, + enc.as_ptr().cast(), + enc.len(), + ))?; + } + + let mut unwrapped = vec![0; unwrapped_len]; + + unsafe { + cvt(EVP_PKEY_decapsulate( + self.as_ptr(), + unwrapped.as_mut_ptr().cast(), + &mut unwrapped_len, + enc.as_ptr().cast(), + enc.len(), + ))?; + } + + Ok(unwrapped) + } +} + +impl PkeyCtxExt for PkeyCtx { + fn new_from_name(name: &'static [u8]) -> Result { + openssl_sys::init(); + unsafe { + let ptr = cvt_p(EVP_PKEY_CTX_new_from_name( + ptr::null_mut(), + name.as_ptr().cast(), + ptr::null(), + ))?; + Ok(PkeyCtx::from_ptr(ptr)) + } + } +} + +impl PkeyExt for PKey { + fn from_encoded_public_key( + encoded_public_key: &[u8], + algorithm_name: &'static [u8], + ) -> Result { + let ctx = PkeyCtx::<()>::new_from_name(algorithm_name)?; + unsafe { + let mut evp = cvt_p(EVP_PKEY_new())?; + cvt(EVP_PKEY_paramgen_init(ctx.as_ptr()))?; + cvt(EVP_PKEY_paramgen(ctx.as_ptr(), &mut evp))?; + cvt(EVP_PKEY_set1_encoded_public_key( + evp, + encoded_public_key.as_ptr(), + encoded_public_key.len(), + ))?; + Ok(PKey::from_ptr(evp)) + } + } +} + +impl PKeyRefExt for PKeyRef { + fn get_octet_string_param(&self, key_name: &[u8]) -> Result, ErrorStack> { + let mut out_len = 0; + unsafe { + cvt(EVP_PKEY_get_octet_string_param( + self.as_ptr(), + key_name.as_ptr().cast(), + ptr::null_mut(), + 0, + &mut out_len, + )) + .unwrap(); + } + + let mut out = vec![0; out_len]; + unsafe { + cvt(EVP_PKEY_get_octet_string_param( + self.as_ptr(), + key_name.as_ptr().cast(), + out.as_mut_ptr(), + out_len, + &mut out_len, + ))?; + } + Ok(out) + } +} + +unsafe extern "C" { + pub fn EVP_PKEY_encapsulate_init(ctx: *mut EVP_PKEY_CTX, params: *const OSSL_PARAM) -> c_int; +} + +unsafe extern "C" { + pub fn EVP_PKEY_encapsulate( + ctx: *mut EVP_PKEY_CTX, + wrappedkey: *mut c_uchar, + wrappedkeylen: *mut usize, + genkey: *mut c_uchar, + genkeylen: *mut usize, + ) -> c_int; +} +unsafe extern "C" { + pub unsafe fn EVP_PKEY_decapsulate_init( + ctx: *mut EVP_PKEY_CTX, + params: *const OSSL_PARAM, + ) -> c_int; +} +unsafe extern "C" { + pub unsafe fn EVP_PKEY_decapsulate( + ctx: *mut EVP_PKEY_CTX, + unwrapped: *mut c_uchar, + unwrappedlen: *mut usize, + wrapped: *const c_uchar, + wrappedlen: usize, + ) -> c_int; +} + +unsafe extern "C" { + pub unsafe fn EVP_PKEY_CTX_new_from_name( + libctx: *mut OSSL_LIB_CTX, + name: *const c_char, + propquery: *const c_char, + ) -> *mut EVP_PKEY_CTX; +} + +unsafe extern "C" { + pub unsafe fn EVP_PKEY_get_octet_string_param( + pkey: *const EVP_PKEY, + key_name: *const c_char, + buf: *mut c_uchar, + max_buf_sz: usize, + out_sz: *mut usize, + ) -> c_int; +} +unsafe extern "C" { + pub unsafe fn EVP_PKEY_set1_encoded_public_key( + pkey: *mut EVP_PKEY, + pub_: *const c_uchar, + publen: usize, + ) -> c_int; +} +unsafe extern "C" { + pub unsafe fn EVP_PKEY_paramgen_init(ctx: *mut EVP_PKEY_CTX) -> c_int; +} +unsafe extern "C" { + pub unsafe fn EVP_PKEY_paramgen(ctx: *mut EVP_PKEY_CTX, ppkey: *mut *mut EVP_PKEY) -> c_int; +} diff --git a/src/openssl_internal/mod.rs b/src/openssl_internal/mod.rs index 385db26..765619a 100644 --- a/src/openssl_internal/mod.rs +++ b/src/openssl_internal/mod.rs @@ -4,9 +4,12 @@ use openssl_sys::c_int; #[cfg(ossl320)] mod hpke; +#[cfg(ossl350)] +pub(crate) mod kem; #[cfg(feature = "tls12")] pub(crate) mod prf; +#[inline] pub(crate) fn cvt(r: c_int) -> Result { if r <= 0 { Err(ErrorStack::get()) @@ -14,3 +17,13 @@ pub(crate) fn cvt(r: c_int) -> Result { Ok(r) } } + +#[cfg(ossl320)] +#[inline] +fn cvt_p(r: *mut T) -> Result<*mut T, ErrorStack> { + if r.is_null() { + Err(ErrorStack::get()) + } else { + Ok(r) + } +} diff --git a/src/openssl_internal/prf.rs b/src/openssl_internal/prf.rs index d3d117a..71fb834 100644 --- a/src/openssl_internal/prf.rs +++ b/src/openssl_internal/prf.rs @@ -3,7 +3,7 @@ use core::ffi::c_void; use foreign_types::ForeignTypeRef; use openssl::{error::ErrorStack, md::MdRef, pkey_ctx::PkeyCtxRef}; -use openssl_sys::{c_int, EVP_MD, EVP_PKEY_ALG_CTRL, EVP_PKEY_CTX, EVP_PKEY_OP_DERIVE}; +use openssl_sys::{EVP_MD, EVP_PKEY_ALG_CTRL, EVP_PKEY_CTX, EVP_PKEY_OP_DERIVE, c_int}; use super::cvt; @@ -24,14 +24,16 @@ const EVP_PKEY_CTRL_TLS_SEED: c_int = EVP_PKEY_ALG_CTRL + 2; #[allow(non_snake_case)] unsafe fn EVP_PKEY_CTX_set_tls1_prf_md(ctx: *mut EVP_PKEY_CTX, md: *const EVP_MD) -> c_int { - EVP_PKEY_CTX_ctrl( - ctx, - -1, - EVP_PKEY_OP_DERIVE, - EVP_PKEY_CTRL_TLS_MD, - 0, - md as *mut c_void, - ) + unsafe { + EVP_PKEY_CTX_ctrl( + ctx, + -1, + EVP_PKEY_OP_DERIVE, + EVP_PKEY_CTRL_TLS_MD, + 0, + md as *mut c_void, + ) + } } #[allow(non_snake_case)] @@ -40,14 +42,16 @@ unsafe fn EVP_PKEY_CTX_set1_tls1_prf_secret( sec: *const u8, seclen: c_int, ) -> c_int { - EVP_PKEY_CTX_ctrl( - ctx, - -1, - EVP_PKEY_OP_DERIVE, - EVP_PKEY_CTRL_TLS_SECRET, - seclen, - sec as *mut c_void, - ) + unsafe { + EVP_PKEY_CTX_ctrl( + ctx, + -1, + EVP_PKEY_OP_DERIVE, + EVP_PKEY_CTRL_TLS_SECRET, + seclen, + sec as *mut c_void, + ) + } } #[allow(non_snake_case)] @@ -56,14 +60,16 @@ unsafe fn EVP_PKEY_CTX_add1_tls1_prf_seed( seed: *const u8, seedlen: c_int, ) -> c_int { - EVP_PKEY_CTX_ctrl( - ctx, - -1, - EVP_PKEY_OP_DERIVE, - EVP_PKEY_CTRL_TLS_SEED, - seedlen, - seed as *mut c_void, - ) + unsafe { + EVP_PKEY_CTX_ctrl( + ctx, + -1, + EVP_PKEY_OP_DERIVE, + EVP_PKEY_CTRL_TLS_SEED, + seedlen, + seed as *mut c_void, + ) + } } pub(crate) fn set_tls1_prf_secret( diff --git a/src/quic.rs b/src/quic.rs index c03c429..cb1da4f 100644 --- a/src/quic.rs +++ b/src/quic.rs @@ -1,8 +1,9 @@ use crate::aead; -use openssl::symm::{encrypt, Cipher}; +use openssl::symm::{Cipher, encrypt}; use rustls::{ + Error, crypto::cipher::{AeadKey, Iv, Nonce}, - quic, Error, + quic, }; pub(crate) struct KeyBuilder { @@ -218,8 +219,8 @@ impl HeaderProtectionKey { #[cfg(test)] mod test { use rustls::{ - quic::{Keys, Version}, Side, + quic::{Keys, Version}, }; use super::super::tls13::TLS13_AES_128_GCM_SHA256_INTERNAL; diff --git a/src/tls12.rs b/src/tls12.rs index f96d4cf..96d771e 100644 --- a/src/tls12.rs +++ b/src/tls12.rs @@ -2,12 +2,12 @@ use crate::aead::{self, TAG_LEN}; use crate::hash::{SHA256, SHA384}; use crate::prf::Prf; use crate::signer::RSA_SCHEMES; +use rustls::crypto::KeyExchangeAlgorithm; use rustls::crypto::cipher::{ - make_tls12_aad, AeadKey, InboundOpaqueMessage, InboundPlainMessage, Iv, KeyBlockShape, - MessageDecrypter, MessageEncrypter, Nonce, OutboundOpaqueMessage, OutboundPlainMessage, - PrefixedPayload, Tls12AeadAlgorithm, UnsupportedOperationError, NONCE_LEN, + AeadKey, InboundOpaqueMessage, InboundPlainMessage, Iv, KeyBlockShape, MessageDecrypter, + MessageEncrypter, NONCE_LEN, Nonce, OutboundOpaqueMessage, OutboundPlainMessage, + PrefixedPayload, Tls12AeadAlgorithm, UnsupportedOperationError, make_tls12_aad, }; -use rustls::crypto::KeyExchangeAlgorithm; use rustls::{ CipherSuite, CipherSuiteCommon, ConnectionTrafficSecrets, Error, SignatureScheme, SupportedCipherSuite, Tls12CipherSuite, diff --git a/src/tls13.rs b/src/tls13.rs index cb4a5d1..a90fb65 100644 --- a/src/tls13.rs +++ b/src/tls13.rs @@ -2,12 +2,12 @@ use crate::aead; use crate::hash::{SHA256, SHA384}; use crate::hkdf::Hkdf; use crate::quic; +use rustls::crypto::CipherSuiteCommon; use rustls::crypto::cipher::{ - make_tls13_aad, AeadKey, InboundOpaqueMessage, InboundPlainMessage, Iv, MessageDecrypter, - MessageEncrypter, Nonce, OutboundOpaqueMessage, OutboundPlainMessage, PrefixedPayload, - Tls13AeadAlgorithm, UnsupportedOperationError, + AeadKey, InboundOpaqueMessage, InboundPlainMessage, Iv, MessageDecrypter, MessageEncrypter, + Nonce, OutboundOpaqueMessage, OutboundPlainMessage, PrefixedPayload, Tls13AeadAlgorithm, + UnsupportedOperationError, make_tls13_aad, }; -use rustls::crypto::CipherSuiteCommon; use rustls::{ CipherSuite, ConnectionTrafficSecrets, Error, SupportedCipherSuite, Tls13CipherSuite, }; diff --git a/src/verify.rs b/src/verify.rs index e552776..a540872 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -10,9 +10,9 @@ use openssl::{ }; use rustls::pki_types::alg_id; use rustls::{ + SignatureScheme, crypto::WebPkiSupportedAlgorithms, pki_types::{AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm}, - SignatureScheme, }; /// A [WebPkiSupportedAlgorithms] value defining the supported signature algorithms. diff --git a/tests/it.rs b/tests/it.rs index 314909c..c7ea4c0 100644 --- a/tests/it.rs +++ b/tests/it.rs @@ -180,12 +180,34 @@ fn test_client_and_server( #[case] expected: CipherSuite, ) { // Run against a server using our default provider - let (port, certificate) = start_server(alg); + let (port, certificate) = start_server(alg, None); let provider = custom_provider(vec![suite], vec![group]); let actual_suite = test_with_provider(provider, port, vec![certificate]); assert_eq!(actual_suite, expected); } +#[cfg(ossl350)] +#[test] +fn test_classical_completion() { + // Run against a server that only supports the classical component + let provider = custom_provider( + rustls_openssl::ALL_CIPHER_SUITES.to_vec(), + vec![rustls_openssl::kx_group::X25519], + ); + + let (port, certificate) = start_server(server::Alg::PKCS_ECDSA_P256_SHA256, Some(provider)); + let provider = custom_provider( + vec![rustls_openssl::cipher_suite::TLS13_AES_256_GCM_SHA384], + // specifying both, with the hybrid first, causes rustls to reuse the classical component from the hybrid + vec![ + rustls_openssl::kx_group::X25519MLKEM768, + rustls_openssl::kx_group::X25519, + ], + ); + let actual_suite = test_with_provider(provider, port, vec![certificate]); + assert_eq!(actual_suite, CipherSuite::TLS13_AES_256_GCM_SHA384); +} + #[rstest] #[cfg_attr( all(feature = "tls12", chacha, not(feature = "fips")), @@ -269,7 +291,7 @@ fn test_to_internet( /// Test that the default provider returns the highest priority cipher suite #[test] fn test_default_client() { - let (port, certificate) = start_server(server::Alg::PKCS_RSA_SHA512); + let (port, certificate) = start_server(server::Alg::PKCS_RSA_SHA512, None); let actual_suite = test_with_provider(default_provider(), port, vec![certificate]); assert_eq!(actual_suite, CipherSuite::TLS13_AES_256_GCM_SHA384); } @@ -420,9 +442,10 @@ fn sign_and_verify( .map(|(_k, v)| *v) .expect("verifying provider supports this scheme"); assert!(!algs.is_empty()); - assert!(algs - .iter() - .any(|alg| { alg.verify_signature(pub_key, data, &signature).is_ok() })); + assert!( + algs.iter() + .any(|alg| { alg.verify_signature(pub_key, data, &signature).is_ok() }) + ); } #[cfg(feature = "fips")] diff --git a/tests/server.rs b/tests/server.rs index 709e53e..dd7f3cf 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -4,9 +4,10 @@ use std::sync::Arc; use openssl::pkey::PKey; use rcgen::SignatureAlgorithm; +use rustls::ServerConfig; +use rustls::crypto::CryptoProvider; use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; use rustls::server::Acceptor; -use rustls::ServerConfig; /// Algorithm to use for the server keypair. Required to workaround /// https://github.com/openssl/openssl/issues/10468 and rcgen::SignatureAlgorithm not @@ -26,7 +27,7 @@ pub enum Alg { /// The server will handle a single connection. /// /// Returns the port the server is listening on and the CA certificate used to sign the server certificate. -pub fn start_server(alg: Alg) -> (u16, CertificateDer<'static>) { +pub fn start_server(alg: Alg, provider: Option) -> (u16, CertificateDer<'static>) { #[cfg(feature = "fips")] { rustls_openssl::fips::enable(); @@ -34,7 +35,7 @@ pub fn start_server(alg: Alg) -> (u16, CertificateDer<'static>) { let pki = TestPki::for_algorithm(alg); let ca_cert_der = pki.ca_cert_der.clone(); - let server_config = pki.server_config(); + let server_config = pki.with_provider(provider.unwrap_or(rustls_openssl::default_provider())); let listener = std::net::TcpListener::bind("[::]:0").unwrap(); let port = listener.local_addr().unwrap().port(); @@ -121,14 +122,13 @@ impl TestPki { } } - fn server_config(self) -> Arc { - let mut server_config = - ServerConfig::builder_with_provider(rustls_openssl::default_provider().into()) - .with_safe_default_protocol_versions() - .unwrap() - .with_no_client_auth() - .with_single_cert(vec![self.server_cert_der], self.server_key_der) - .unwrap(); + fn with_provider(self, provider: CryptoProvider) -> Arc { + let mut server_config = ServerConfig::builder_with_provider(provider.into()) + .with_safe_default_protocol_versions() + .unwrap() + .with_no_client_auth() + .with_single_cert(vec![self.server_cert_der], self.server_key_der) + .unwrap(); server_config.key_log = Arc::new(rustls::KeyLogFile::new());