mirror of https://github.com/ctz/rustls
Test FFDHE support against OpenSSL
This commit adds a new test crate `openssl-tests` that includes tests of FFDHE kx and validation of baked-in FFDHE parameters
This commit is contained in:
parent
c8c56a7aef
commit
8c29d91ed3
|
@ -390,3 +390,24 @@ jobs:
|
||||||
- name: run cargo-check-external-types for rustls/
|
- name: run cargo-check-external-types for rustls/
|
||||||
working-directory: rustls/
|
working-directory: rustls/
|
||||||
run: cargo check-external-types
|
run: cargo check-external-types
|
||||||
|
|
||||||
|
openssl-tests:
|
||||||
|
name: Run openssl-tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Install stable toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- name: openssl version
|
||||||
|
run: openssl version
|
||||||
|
|
||||||
|
- name: cargo test (in openssl-tests/)
|
||||||
|
working-directory: openssl-tests/
|
||||||
|
run: cargo test --locked -- --include-ignored
|
||||||
|
env:
|
||||||
|
RUST_BACKTRACE: 1
|
||||||
|
|
|
@ -115,6 +115,26 @@ version = "1.0.79"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asn1"
|
||||||
|
version = "0.15.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae3ecbce89a22627b5e8e6e11d69715617138290289e385cde773b1fe50befdb"
|
||||||
|
dependencies = [
|
||||||
|
"asn1_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asn1_derive"
|
||||||
|
version = "0.15.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "861af988fac460ac69a09f41e6217a8fb9178797b76fcc9478444be6a59be19c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-attributes"
|
name = "async-attributes"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
|
@ -883,6 +903,21 @@ version = "1.0.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||||
|
dependencies = [
|
||||||
|
"foreign-types-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types-shared"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
@ -1588,6 +1623,44 @@ version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl"
|
||||||
|
version = "0.10.62"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.2",
|
||||||
|
"cfg-if",
|
||||||
|
"foreign-types",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"openssl-macros",
|
||||||
|
"openssl-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-sys"
|
||||||
|
version = "0.9.98"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "p256"
|
name = "p256"
|
||||||
version = "0.13.2"
|
version = "0.13.2"
|
||||||
|
@ -1711,6 +1784,12 @@ dependencies = [
|
||||||
"spki",
|
"spki",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "platforms"
|
name = "platforms"
|
||||||
version = "3.3.0"
|
version = "3.3.0"
|
||||||
|
@ -2105,6 +2184,20 @@ dependencies = [
|
||||||
"webpki-roots 0.26.0",
|
"webpki-roots 0.26.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-openssl-tests"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"asn1",
|
||||||
|
"base64",
|
||||||
|
"num-bigint",
|
||||||
|
"once_cell",
|
||||||
|
"openssl",
|
||||||
|
"rustls 0.23.0-alpha.0",
|
||||||
|
"rustls-pemfile 2.0.0",
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-pemfile"
|
name = "rustls-pemfile"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@ -2589,6 +2682,12 @@ version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7cdbaf5e132e593e9fc1de6a15bbec912395b11fb9719e061cf64f804524c503"
|
checksum = "7cdbaf5e132e593e9fc1de6a15bbec912395b11fb9719e061cf64f804524c503"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
members = [
|
members = [
|
||||||
# CI benchmarks
|
# CI benchmarks
|
||||||
"ci-bench",
|
"ci-bench",
|
||||||
|
# Tests that require OpenSSL
|
||||||
|
"openssl-tests",
|
||||||
# Network-based tests
|
# Network-based tests
|
||||||
"connect-tests",
|
"connect-tests",
|
||||||
# tests and example code
|
# tests and example code
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
description = "Rustls tests that require OpenSSL"
|
||||||
|
edition = "2021"
|
||||||
|
license = "Apache-2.0 OR ISC OR MIT"
|
||||||
|
name = "rustls-openssl-tests"
|
||||||
|
publish = false
|
||||||
|
version = "0.0.1"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
asn1 = "0.15"
|
||||||
|
base64 = "0.21"
|
||||||
|
num-bigint = "0.4.4"
|
||||||
|
once_cell = "1.19"
|
||||||
|
rustls = {path = "../rustls"}
|
||||||
|
rustls-pemfile = "2"
|
||||||
|
rustls-pki-types = "1.0"
|
||||||
|
openssl = "0.10"
|
|
@ -0,0 +1,88 @@
|
||||||
|
use num_bigint::BigUint;
|
||||||
|
use rustls::crypto::{
|
||||||
|
ActiveKeyExchange, CipherSuiteCommon, KeyExchangeAlgorithm, SharedSecret, SupportedKxGroup,
|
||||||
|
};
|
||||||
|
use rustls::ffdhe_groups::FfdheGroup;
|
||||||
|
use rustls::{CipherSuite, NamedGroup, SupportedCipherSuite, Tls12CipherSuite};
|
||||||
|
|
||||||
|
/// The (test-only) TLS1.2 ciphersuite TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
|
||||||
|
pub static TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite =
|
||||||
|
SupportedCipherSuite::Tls12(&TLS12_DHE_RSA_WITH_AES_128_GCM_SHA256);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FfdheKxGroup(pub NamedGroup);
|
||||||
|
|
||||||
|
impl SupportedKxGroup for FfdheKxGroup {
|
||||||
|
fn start(&self) -> Result<Box<dyn ActiveKeyExchange>, rustls::Error> {
|
||||||
|
let mut x = vec![0; 64];
|
||||||
|
rustls::crypto::ring::default_provider()
|
||||||
|
.secure_random
|
||||||
|
.fill(&mut x)?;
|
||||||
|
let x = BigUint::from_bytes_be(&x);
|
||||||
|
|
||||||
|
let group = FfdheGroup::from_named_group(self.0).unwrap();
|
||||||
|
let p = BigUint::from_bytes_be(group.p);
|
||||||
|
let g = BigUint::from_bytes_be(group.g);
|
||||||
|
|
||||||
|
let x_pub = g.modpow(&x, &p);
|
||||||
|
let x_pub = to_bytes_be_with_len(x_pub, group.p.len());
|
||||||
|
|
||||||
|
Ok(Box::new(ActiveFfdheKx {
|
||||||
|
x_pub,
|
||||||
|
x,
|
||||||
|
p,
|
||||||
|
group,
|
||||||
|
named_group: self.0,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> NamedGroup {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static TLS12_DHE_RSA_WITH_AES_128_GCM_SHA256: Tls12CipherSuite =
|
||||||
|
match &rustls::crypto::ring::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 {
|
||||||
|
SupportedCipherSuite::Tls12(provider) => Tls12CipherSuite {
|
||||||
|
common: CipherSuiteCommon {
|
||||||
|
suite: CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
..provider.common
|
||||||
|
},
|
||||||
|
kx: KeyExchangeAlgorithm::DHE,
|
||||||
|
..**provider
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ActiveFfdheKx {
|
||||||
|
x_pub: Vec<u8>,
|
||||||
|
x: BigUint,
|
||||||
|
p: BigUint,
|
||||||
|
group: FfdheGroup<'static>,
|
||||||
|
named_group: NamedGroup,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveKeyExchange for ActiveFfdheKx {
|
||||||
|
fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, rustls::Error> {
|
||||||
|
let peer_pub = BigUint::from_bytes_be(peer_pub_key);
|
||||||
|
let secret = peer_pub.modpow(&self.x, &self.p);
|
||||||
|
let secret = to_bytes_be_with_len(secret, self.group.p.len());
|
||||||
|
|
||||||
|
Ok(SharedSecret::from(&secret[..]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pub_key(&self) -> &[u8] {
|
||||||
|
&self.x_pub
|
||||||
|
}
|
||||||
|
|
||||||
|
fn group(&self) -> NamedGroup {
|
||||||
|
self.named_group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_bytes_be_with_len(n: BigUint, len_bytes: usize) -> Vec<u8> {
|
||||||
|
let mut bytes = n.to_bytes_le();
|
||||||
|
bytes.resize(len_bytes, 0);
|
||||||
|
bytes.reverse();
|
||||||
|
bytes
|
||||||
|
}
|
|
@ -0,0 +1,233 @@
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::{BufReader, Read, Write};
|
||||||
|
use std::net::{TcpListener, TcpStream};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::{str, thread};
|
||||||
|
|
||||||
|
use rustls::crypto::ring::default_provider;
|
||||||
|
use rustls::crypto::CryptoProvider;
|
||||||
|
use rustls::version::{TLS12, TLS13};
|
||||||
|
use rustls::{ClientConfig, RootCertStore, ServerConfig, SupportedProtocolVersion};
|
||||||
|
use rustls_pemfile::Item;
|
||||||
|
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
|
||||||
|
|
||||||
|
use crate::ffdhe::{self, FfdheKxGroup};
|
||||||
|
use crate::utils::verify_openssl3_available;
|
||||||
|
|
||||||
|
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rustls_server_with_ffdhe_kx_tls13() {
|
||||||
|
test_rustls_server_with_ffdhe_kx(&TLS13, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rustls_server_with_ffdhe_kx_tls12() {
|
||||||
|
test_rustls_server_with_ffdhe_kx(&TLS12, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_rustls_server_with_ffdhe_kx(
|
||||||
|
protocol_version: &'static SupportedProtocolVersion,
|
||||||
|
iters: usize,
|
||||||
|
) {
|
||||||
|
verify_openssl3_available();
|
||||||
|
|
||||||
|
let message = "Hello from rustls!\n";
|
||||||
|
|
||||||
|
let listener = std::net::TcpListener::bind(("localhost", 0)).unwrap();
|
||||||
|
let port = listener.local_addr().unwrap().port();
|
||||||
|
|
||||||
|
let server_thread = std::thread::spawn(move || {
|
||||||
|
let config = Arc::new(server_config_with_ffdhe_kx(protocol_version));
|
||||||
|
for _ in 0..iters {
|
||||||
|
let mut server = rustls::ServerConnection::new(config.clone()).unwrap();
|
||||||
|
let (mut tcp_stream, _addr) = listener.accept().unwrap();
|
||||||
|
server
|
||||||
|
.writer()
|
||||||
|
.write_all(message.as_bytes())
|
||||||
|
.unwrap();
|
||||||
|
server
|
||||||
|
.complete_io(&mut tcp_stream)
|
||||||
|
.unwrap();
|
||||||
|
tcp_stream.flush().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut connector = openssl::ssl::SslConnector::builder(SslMethod::tls()).unwrap();
|
||||||
|
connector
|
||||||
|
.set_ca_file(CA_PEM_FILE)
|
||||||
|
.unwrap();
|
||||||
|
connector
|
||||||
|
.set_groups_list("ffdhe2048")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let connector = connector.build();
|
||||||
|
|
||||||
|
for _iter in 0..iters {
|
||||||
|
let stream = TcpStream::connect(("localhost", port)).unwrap();
|
||||||
|
let mut stream = connector
|
||||||
|
.connect("testserver.com", stream)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut buf = String::new();
|
||||||
|
stream.read_to_string(&mut buf).unwrap();
|
||||||
|
assert_eq!(buf, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
server_thread.join().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rustls_client_with_ffdhe_kx() {
|
||||||
|
test_rustls_client_with_ffdhe_kx(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_rustls_client_with_ffdhe_kx(iters: usize) {
|
||||||
|
verify_openssl3_available();
|
||||||
|
|
||||||
|
let message = "Hello from rustls!\n";
|
||||||
|
|
||||||
|
println!("crate openssl version: {}", openssl::version::version());
|
||||||
|
|
||||||
|
let mut acceptor = SslAcceptor::mozilla_modern_v5(SslMethod::tls()).unwrap();
|
||||||
|
acceptor
|
||||||
|
.set_groups_list("ffdhe2048")
|
||||||
|
.unwrap();
|
||||||
|
acceptor
|
||||||
|
.set_private_key_file(PRIV_KEY_FILE, SslFiletype::PEM)
|
||||||
|
.unwrap();
|
||||||
|
acceptor
|
||||||
|
.set_certificate_chain_file(CERT_CHAIN_FILE)
|
||||||
|
.unwrap();
|
||||||
|
acceptor.check_private_key().unwrap();
|
||||||
|
let acceptor = Arc::new(acceptor.build());
|
||||||
|
|
||||||
|
let listener = TcpListener::bind(("localhost", 0)).unwrap();
|
||||||
|
let port = listener.local_addr().unwrap().port();
|
||||||
|
|
||||||
|
let server_thread = std::thread::spawn(move || {
|
||||||
|
for stream in listener.incoming().take(iters) {
|
||||||
|
match stream {
|
||||||
|
Ok(stream) => {
|
||||||
|
let acceptor = acceptor.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut stream = acceptor.accept(stream).unwrap();
|
||||||
|
let mut buf = String::new();
|
||||||
|
stream.read_to_string(&mut buf).unwrap();
|
||||||
|
assert_eq!(buf, message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
panic!("openssl connection failed: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// client:
|
||||||
|
for _ in 0..iters {
|
||||||
|
let mut tcp_stream = std::net::TcpStream::connect(("localhost", port)).unwrap();
|
||||||
|
let mut client = rustls::client::ClientConnection::new(
|
||||||
|
client_config_with_ffdhe_kx().into(),
|
||||||
|
"localhost".try_into().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
client
|
||||||
|
.writer()
|
||||||
|
.write_all(message.as_bytes())
|
||||||
|
.unwrap();
|
||||||
|
client
|
||||||
|
.complete_io(&mut tcp_stream)
|
||||||
|
.unwrap();
|
||||||
|
client.send_close_notify();
|
||||||
|
client
|
||||||
|
.write_tls(&mut tcp_stream)
|
||||||
|
.unwrap();
|
||||||
|
tcp_stream.flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
server_thread.join().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_config_with_ffdhe_kx() -> ClientConfig {
|
||||||
|
ClientConfig::builder_with_provider(ffdhe_provider().into())
|
||||||
|
// OpenSSL 3 does not support RFC 7919 with TLS 1.2: https://github.com/openssl/openssl/issues/10971
|
||||||
|
.with_protocol_versions(&[&TLS13])
|
||||||
|
.unwrap()
|
||||||
|
.with_root_certificates(root_ca())
|
||||||
|
.with_no_client_auth()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLS 1.2 requires stripping leading zeros of the shared secret,
|
||||||
|
// While TLS 1.3 requires the shared secret to be padded with zeros.
|
||||||
|
// The chance of getting a shared secret with the first byte being zero is 1 in 256,
|
||||||
|
// so we repeat the tests to have a high chance of getting a kx with this property.
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn rustls_client_with_ffdhe_kx_repeated() {
|
||||||
|
test_rustls_client_with_ffdhe_kx(512);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn rustls_server_with_ffdhe_tls13_repeated() {
|
||||||
|
test_rustls_server_with_ffdhe_kx(&TLS13, 512)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn rustls_server_with_ffdhe_tls12_repeated() {
|
||||||
|
test_rustls_server_with_ffdhe_kx(&TLS12, 512);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn root_ca() -> RootCertStore {
|
||||||
|
let mut res = RootCertStore::empty();
|
||||||
|
res.add_parsable_certificates([CertificateDer::from(fs::read(CA_FILE).unwrap())]);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_certs() -> Vec<CertificateDer<'static>> {
|
||||||
|
let mut reader = BufReader::new(File::open(CERT_CHAIN_FILE).unwrap());
|
||||||
|
rustls_pemfile::certs(&mut reader)
|
||||||
|
.map(|c| c.unwrap())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_private_key() -> PrivateKeyDer<'static> {
|
||||||
|
let mut reader = BufReader::new(File::open(PRIV_KEY_FILE).unwrap());
|
||||||
|
|
||||||
|
match rustls_pemfile::read_one(&mut reader)
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
Item::Pkcs1Key(key) => key.into(),
|
||||||
|
Item::Pkcs8Key(key) => key.into(),
|
||||||
|
Item::Sec1Key(key) => key.into(),
|
||||||
|
_ => panic!("no key in key file {PRIV_KEY_FILE}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ffdhe_provider() -> CryptoProvider {
|
||||||
|
CryptoProvider {
|
||||||
|
cipher_suites: vec![
|
||||||
|
ffdhe::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
rustls::crypto::ring::cipher_suite::TLS13_AES_128_GCM_SHA256,
|
||||||
|
],
|
||||||
|
kx_groups: vec![&FfdheKxGroup(rustls::NamedGroup::FFDHE2048)],
|
||||||
|
..default_provider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn server_config_with_ffdhe_kx(protocol: &'static SupportedProtocolVersion) -> ServerConfig {
|
||||||
|
ServerConfig::builder_with_provider(ffdhe_provider().into())
|
||||||
|
.with_protocol_versions(&[protocol])
|
||||||
|
.unwrap()
|
||||||
|
.with_no_client_auth()
|
||||||
|
.with_single_cert(load_certs(), load_private_key())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
const CERT_CHAIN_FILE: &str = "../test-ca/rsa/end.fullchain";
|
||||||
|
const PRIV_KEY_FILE: &str = "../test-ca/rsa/end.key";
|
||||||
|
const CA_FILE: &str = "../test-ca/rsa/ca.der";
|
||||||
|
const CA_PEM_FILE: &str = "../test-ca/rsa/ca.cert";
|
|
@ -0,0 +1,6 @@
|
||||||
|
#![cfg(test)]
|
||||||
|
|
||||||
|
mod ffdhe;
|
||||||
|
mod ffdhe_kx_with_openssl;
|
||||||
|
mod utils;
|
||||||
|
mod validate_ffdhe_params;
|
|
@ -0,0 +1,48 @@
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
pub fn verify_openssl3_available() {
|
||||||
|
static VERIFIED: Lazy<()> = Lazy::new(verify_openssl3_available_internal);
|
||||||
|
*VERIFIED
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If OpenSSL 3 is not avaialble, panics with a helpful message
|
||||||
|
fn verify_openssl3_available_internal() {
|
||||||
|
let openssl_output = std::process::Command::new("openssl")
|
||||||
|
.args(["version"])
|
||||||
|
.output();
|
||||||
|
match openssl_output {
|
||||||
|
Ok(output) if !output.status.success() => {
|
||||||
|
panic!(
|
||||||
|
"OpenSSL exited with an error status: {}\n{}",
|
||||||
|
output.status,
|
||||||
|
std::str::from_utf8(&output.stderr).unwrap_or_default()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(output) => {
|
||||||
|
let version_str = std::str::from_utf8(&output.stdout).unwrap();
|
||||||
|
let parts = version_str
|
||||||
|
.split(' ')
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
assert_eq!(
|
||||||
|
parts.first().copied(),
|
||||||
|
Some("OpenSSL"),
|
||||||
|
"Unknown version response from OpenSSL: {version_str}"
|
||||||
|
);
|
||||||
|
let version = parts.get(1);
|
||||||
|
let major_version = version
|
||||||
|
.and_then(|v| v.split('.').next())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
panic!("Unexpected version response from OpenSSL: {version_str}")
|
||||||
|
});
|
||||||
|
assert!(
|
||||||
|
major_version
|
||||||
|
.parse::<usize>()
|
||||||
|
.is_ok_and(|v| v >= 3),
|
||||||
|
"OpenSSL 3+ is required for the tests here. The installed version is {version:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
panic!("OpenSSL 3+ needs to be installed and in PATH.\nThe error encountered: {e}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
use base64::prelude::*;
|
||||||
|
use rustls::ffdhe_groups::FfdheGroup;
|
||||||
|
use rustls::NamedGroup;
|
||||||
|
|
||||||
|
use crate::utils::verify_openssl3_available;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ffdhe_params_correct() {
|
||||||
|
use NamedGroup::*;
|
||||||
|
|
||||||
|
verify_openssl3_available();
|
||||||
|
|
||||||
|
let groups = [FFDHE2048, FFDHE3072, FFDHE4096, FFDHE6144, FFDHE8192];
|
||||||
|
for group in groups {
|
||||||
|
println!("testing {group:?}");
|
||||||
|
test_ffdhe_params_correct(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_ffdhe_params_correct(group: NamedGroup) {
|
||||||
|
let (p, g) = get_ffdhe_params_from_openssl(group);
|
||||||
|
let openssl_params = FfdheGroup::from_params_trimming_leading_zeros(&p, &g);
|
||||||
|
let rustls_params = FfdheGroup::from_named_group(group).unwrap();
|
||||||
|
assert_eq!(rustls_params.named_group(), Some(group));
|
||||||
|
|
||||||
|
assert_eq!(rustls_params, openssl_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get FFDHE parameters `(p, g)` for the given `ffdhe_group` from OpenSSL
|
||||||
|
fn get_ffdhe_params_from_openssl(ffdhe_group: NamedGroup) -> (Vec<u8>, Vec<u8>) {
|
||||||
|
let group = match ffdhe_group {
|
||||||
|
NamedGroup::FFDHE2048 => "group:ffdhe2048",
|
||||||
|
NamedGroup::FFDHE3072 => "group:ffdhe3072",
|
||||||
|
NamedGroup::FFDHE4096 => "group:ffdhe4096",
|
||||||
|
NamedGroup::FFDHE6144 => "group:ffdhe6144",
|
||||||
|
NamedGroup::FFDHE8192 => "group:ffdhe8192",
|
||||||
|
_ => panic!("not an ffdhe group: {ffdhe_group:?}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let openssl_output = std::process::Command::new("openssl")
|
||||||
|
.args([
|
||||||
|
"genpkey",
|
||||||
|
"-genparam",
|
||||||
|
"-algorithm",
|
||||||
|
"DH",
|
||||||
|
"-text",
|
||||||
|
"-pkeyopt",
|
||||||
|
group,
|
||||||
|
])
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
parse_dh_params_pem(&openssl_output.stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse PEM-encoded DH parameters, returning `(p, g)`
|
||||||
|
fn parse_dh_params_pem(data: &[u8]) -> (Vec<u8>, Vec<u8>) {
|
||||||
|
let output_str = std::str::from_utf8(data).unwrap();
|
||||||
|
let output_str_lines = output_str.lines().collect::<Vec<_>>();
|
||||||
|
assert_eq!(output_str_lines[0], "-----BEGIN DH PARAMETERS-----");
|
||||||
|
|
||||||
|
let last_line = output_str_lines
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_i, l)| **l == "-----END DH PARAMETERS-----")
|
||||||
|
.unwrap()
|
||||||
|
.0;
|
||||||
|
|
||||||
|
let stripped = &output_str_lines[1..last_line];
|
||||||
|
|
||||||
|
let base64_encoded = stripped
|
||||||
|
.iter()
|
||||||
|
.fold(String::new(), |acc, l| acc + l);
|
||||||
|
|
||||||
|
let base64_decoded = BASE64_STANDARD
|
||||||
|
.decode(base64_encoded)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let res: asn1::ParseResult<_> = asn1::parse(&base64_decoded, |d| {
|
||||||
|
d.read_element::<asn1::Sequence>()?
|
||||||
|
.parse(|d| {
|
||||||
|
let p = d.read_element::<asn1::BigUint>()?;
|
||||||
|
let g = d.read_element::<asn1::BigUint>()?;
|
||||||
|
Ok((p, g))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let res = res.unwrap();
|
||||||
|
(res.0.as_bytes().to_vec(), res.1.as_bytes().to_vec())
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
use crate::NamedGroup;
|
use crate::NamedGroup;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
/// Parameters of an FFDHE group, with Big-endian byte order
|
/// Parameters of an FFDHE group, with Big-endian byte order
|
||||||
pub struct FfdheGroup<'a> {
|
pub struct FfdheGroup<'a> {
|
||||||
pub p: &'a [u8],
|
pub p: &'a [u8],
|
||||||
|
|
Loading…
Reference in New Issue