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/
|
||||
working-directory: rustls/
|
||||
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"
|
||||
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]]
|
||||
name = "async-attributes"
|
||||
version = "1.1.2"
|
||||
|
@ -883,6 +903,21 @@ version = "1.0.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
|
@ -1588,6 +1623,44 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "p256"
|
||||
version = "0.13.2"
|
||||
|
@ -1711,6 +1784,12 @@ dependencies = [
|
|||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
|
||||
|
||||
[[package]]
|
||||
name = "platforms"
|
||||
version = "3.3.0"
|
||||
|
@ -2105,6 +2184,20 @@ dependencies = [
|
|||
"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]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.4"
|
||||
|
@ -2589,6 +2682,12 @@ version = "1.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cdbaf5e132e593e9fc1de6a15bbec912395b11fb9719e061cf64f804524c503"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
members = [
|
||||
# CI benchmarks
|
||||
"ci-bench",
|
||||
# Tests that require OpenSSL
|
||||
"openssl-tests",
|
||||
# Network-based tests
|
||||
"connect-tests",
|
||||
# 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;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
/// Parameters of an FFDHE group, with Big-endian byte order
|
||||
pub struct FfdheGroup<'a> {
|
||||
pub p: &'a [u8],
|
||||
|
|
Loading…
Reference in New Issue