oa_gateway_testing/tls.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
//! Self-signed certificates for TLS tests.
//!
//! Generated fresh per test run rather than checked into the repository:
//! nothing expires, and no key material — not even a throwaway one — lives
//! in a public repo's git history.
use oa_gateway_adapter::tls::{client_tls_from_pem, server_tls_from_pem, ClientTls, ServerTls};
/// A self-signed certificate and its key, in memory.
///
/// The certificate is its own authority, so trusting it as a peer's leaf
/// certificate and trusting it as a CA amount to the same thing here.
pub struct TestCerts {
pub cert_pem: String,
pub key_pem: String,
}
/// Generates a self-signed certificate valid for `names` (hostnames or IP
/// addresses).
///
/// # Panics
///
/// Panics if certificate generation fails, which does not happen for a
/// well-formed name list.
#[must_use]
pub fn self_signed(names: &[&str]) -> TestCerts {
let names: Vec<String> = names.iter().map(|s| (*s).to_string()).collect();
let rcgen::CertifiedKey { cert, key_pair } =
rcgen::generate_simple_self_signed(names).expect("self-signed cert generation");
TestCerts {
cert_pem: cert.pem(),
key_pem: key_pair.serialize_pem(),
}
}
/// A [`ServerTls`] presenting `certs`.
///
/// # Panics
///
/// Panics if `certs` does not parse, which does not happen for a pair
/// from [`self_signed`].
#[must_use]
pub fn server_tls(certs: &TestCerts) -> ServerTls {
server_tls_from_pem(
"test",
certs.cert_pem.as_bytes(),
certs.key_pem.as_bytes(),
None,
)
.expect("test certificate should parse")
}
/// A [`ClientTls`] that trusts `certs` and checks the peer's certificate
/// against `server_name`.
///
/// # Panics
///
/// Panics if `certs` does not parse or `server_name` is not a usable DNS
/// name or IP address.
#[must_use]
pub fn client_tls(certs: &TestCerts, server_name: &str) -> ClientTls {
client_tls_from_pem(
"test",
Some(certs.cert_pem.as_bytes()),
server_name,
None,
None,
)
.expect("test certificate and server name should be usable")
}
/// A [`ClientTls`] that trusts a *different* self-signed authority than
/// whatever the server under test presents — for a test asserting that an
/// untrusted certificate is rejected.
///
/// # Panics
///
/// Panics if `server_name` is not a usable DNS name or IP address.
#[must_use]
pub fn untrusted_client_tls(server_name: &str) -> ClientTls {
let other = self_signed(&[server_name]);
client_tls(&other, server_name)
}
/// A certificate authority for issuing certificates in mutual-TLS tests —
/// `self_signed`'s single cert/key pair can't produce this, since mTLS
/// tests need a CA distinct from the leaf certificates it issues.
pub struct TestCa {
pub ca_cert_pem: String,
cert: rcgen::Certificate,
key_pair: rcgen::KeyPair,
}
/// Generates a self-signed CA for issuing certificates via [`issue`].
///
/// # Panics
///
/// Panics if certificate generation fails.
#[must_use]
pub fn test_ca() -> TestCa {
let key_pair = rcgen::KeyPair::generate().expect("CA key generation");
let mut params = rcgen::CertificateParams::new(Vec::<String>::new()).expect("CA cert params");
params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
let cert = params
.self_signed(&key_pair)
.expect("self-signed CA certificate");
TestCa {
ca_cert_pem: cert.pem(),
cert,
key_pair,
}
}
/// Issues a certificate for `names`, signed by `ca` — for a peer that must
/// present a certificate `ca` (or a [`server_tls_with_client_ca`] built
/// from it) will accept.
///
/// # Panics
///
/// Panics if certificate generation fails.
#[must_use]
pub fn issue(ca: &TestCa, names: &[&str]) -> TestCerts {
let names: Vec<String> = names.iter().map(|s| (*s).to_string()).collect();
let key_pair = rcgen::KeyPair::generate().expect("certificate key generation");
let params = rcgen::CertificateParams::new(names).expect("certificate params");
let cert = params
.signed_by(&key_pair, &ca.cert, &ca.key_pair)
.expect("CA-signed certificate");
TestCerts {
cert_pem: cert.pem(),
key_pem: key_pair.serialize_pem(),
}
}
/// As [`server_tls`], but also requires and verifies a client certificate
/// issued by `client_ca`.
///
/// # Panics
///
/// Panics if `certs`/`client_ca` does not parse.
#[must_use]
pub fn server_tls_with_client_ca(certs: &TestCerts, client_ca: &TestCa) -> ServerTls {
server_tls_from_pem(
"test",
certs.cert_pem.as_bytes(),
certs.key_pem.as_bytes(),
Some(client_ca.ca_cert_pem.as_bytes()),
)
.expect("test certificate and client CA should parse")
}
/// A [`ClientTls`] that trusts `certs` and presents a certificate issued by
/// `client_ca` — for a peer requiring mutual TLS.
///
/// # Panics
///
/// Panics if `certs`/`client_ca`/`client_certs` does not parse, or
/// `server_name` is not a usable DNS name or IP address.
#[must_use]
pub fn client_tls_with_client_cert(
certs: &TestCerts,
server_name: &str,
client_certs: &TestCerts,
) -> ClientTls {
client_tls_from_pem(
"test",
Some(certs.cert_pem.as_bytes()),
server_name,
Some(client_certs.cert_pem.as_bytes()),
Some(client_certs.key_pem.as_bytes()),
)
.expect("test certificate, server name, and client certificate should be usable")
}