Open mleonhard opened 4 years ago
Here's an example of using x509-signature in a TLS client that does certificate pinning.
# Cargo.toml
[package]
edition = "2018"
name = "example"
version = "0.1.0"
[dependencies]
log = "0.4"
pem = "0.8"
rustls = {version = "0.18", features = ["dangerous_configuration"]}
tokio = {version = "0.2", features = ["full"]}
tokio-rustls = "0.14"
webpki = "0.21"
x509-signature = {version = "0.5", features = ["rustls", "webpki"]}
// src/main.rs
// This program shows how to use TLS with certificate pinning.
use log::trace;
use std::println;
use std::sync::Arc;
/// AcceptSpecificCertsVerifier implements certificate pinning.
///
/// It accepts a pre-configured set of certificates.
/// It ignores subject alternate names and all other certificate
/// properties like expiration date and certificate type.
///
/// It accepts only X.509 v3 certificates because of
/// "QUESTION Can we verify version 1 x509 cert and integrating with `Rustls`"
/// https://github.com/paritytech/x509-signature/issues/2
/// Therefore, it cannot be used to work around:
/// - "Handshake fails with BadDER" https://github.com/ctz/rustls/issues/127
/// - "V1 certificates are rejected with `BadDER` instead of `UnsupportedCertVersion`"
/// https://github.com/briansmith/webpki/issues/53
/// - "Certificates missing X509v3 Extensions field and/or subjectAltName
/// are rejected with unclear BadDER error."
/// https://github.com/briansmith/webpki/issues/90
///
/// The rustls library has an open issue to add something like this:
/// "Implement support for certificate pinning" https://github.com/ctz/rustls/issues/227
struct AcceptSpecificCertsVerifier {
certs: Vec<rustls::Certificate>,
}
impl rustls::ServerCertVerifier for AcceptSpecificCertsVerifier {
fn verify_server_cert(
&self,
_roots: &rustls::RootCertStore,
presented_certs: &[rustls::Certificate],
_dns_name: webpki::DNSNameRef,
_ocsp_response: &[u8],
) -> Result<rustls::ServerCertVerified, rustls::TLSError> {
let presented_cert = &presented_certs[0];
for cert in &self.certs {
if presented_cert == cert {
return Ok(rustls::ServerCertVerified::assertion());
}
}
return Err(rustls::TLSError::WebPKIError(webpki::Error::UnknownIssuer));
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &rustls::Certificate,
dss: &rustls::internal::msgs::handshake::DigitallySignedStruct,
) -> Result<rustls::HandshakeSignatureValid, rustls::TLSError> {
let x509_cert = x509_signature::parse_certificate(&cert.0).map_err(
|x509_err: x509_signature::Error| {
trace!("error parsing certificate: {}", x509_err);
rustls::TLSError::WebPKIError(x509_err)
},
)?;
x509_cert
.check_tls12_signature(dss.scheme, message, &dss.sig.0)
.map_err(|x509_err: x509_signature::Error| {
trace!("error checking signature: {}", x509_err);
rustls::TLSError::WebPKIError(x509_err)
})?;
Ok(rustls::HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &rustls::Certificate,
dss: &rustls::internal::msgs::handshake::DigitallySignedStruct,
) -> Result<rustls::HandshakeSignatureValid, rustls::TLSError> {
let x509_cert = x509_signature::parse_certificate(&cert.0).map_err(
|x509_err: x509_signature::Error| {
trace!("error parsing certificate: {}", x509_err);
rustls::TLSError::WebPKIError(x509_err)
},
)?;
x509_cert
.check_tls13_signature(dss.scheme, message, &dss.sig.0)
.map_err(|x509_err: x509_signature::Error| {
trace!("error checking signature: {}", x509_err);
rustls::TLSError::WebPKIError(x509_err)
})?;
Ok(rustls::HandshakeSignatureValid::assertion())
}
}
fn arbitrary_dns_name() -> webpki::DNSName {
webpki::DNSNameRef::try_from_ascii_str("arbitrary1")
.unwrap()
.to_owned()
}
async fn async_main() -> () {
let cert_pem_bytes = tokio::fs::read("localhost.cert").await.unwrap();
let cert_pem = pem::parse(cert_pem_bytes).unwrap();
assert_eq!(cert_pem.tag, "CERTIFICATE");
let cert = rustls::Certificate(cert_pem.contents);
let key_pem_bytes = tokio::fs::read("localhost.key").await.unwrap();
let key_pem = pem::parse(key_pem_bytes).unwrap();
let key = rustls::PrivateKey(key_pem.contents);
assert_eq!(key_pem.tag, "PRIVATE KEY");
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 1690));
let mut listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
println!(
"INFO server listening on {}",
listener.local_addr().unwrap()
);
let mut server_config = rustls::ServerConfig::new(rustls::NoClientAuth::new());
server_config
.set_single_cert(vec![cert.clone()], key)
.unwrap();
let server_config_arc = Arc::new(server_config);
tokio::spawn(async move {
let tls_acceptor = tokio_rustls::TlsAcceptor::from(server_config_arc);
loop {
let (tcp_stream, _addr) = listener.accept().await.unwrap();
let mut tls_stream = tls_acceptor.accept(tcp_stream).await.unwrap();
use tokio::io::AsyncWriteExt;
if let Err(e) = tls_stream.write_all(b"response").await {
println!("WARN server write error: {:?}", e);
return;
}
}
});
let mut client_config = rustls::ClientConfig::new();
client_config
.dangerous()
.set_certificate_verifier(Arc::new(AcceptSpecificCertsVerifier { certs: vec![cert] }));
let tls_connector = tokio_rustls::TlsConnector::from(Arc::new(client_config));
let tcp_stream = tokio::net::TcpStream::connect("127.0.0.1:1690")
.await
.unwrap();
let mut tls_stream = tls_connector
.connect(arbitrary_dns_name().as_ref(), tcp_stream)
.await
.unwrap();
use tokio::io::AsyncReadExt;
let mut buf = String::new();
if let Err(e) = tls_stream.read_to_string(&mut buf).await {
println!("WARN client read error: {:?}", e);
return;
}
println!("INFO client read {:?}", buf);
}
pub fn main() {
let mut runtime = tokio::runtime::Builder::new()
.threaded_scheduler()
.enable_all()
.build()
.unwrap();
runtime.block_on(async_main());
runtime.shutdown_background();
}
# openssl.cfg
[req]
distinguished_name=dn
x509_extensions=ext
[ dn ]
CN=localhost
[ ext ]
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
IP.1 = 127.0.0.1
IP.2 = ::1
$ openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -out localhost.cert -keyout localhost.key -subj '/CN=localhost' -config openssl.cfg
Generating a 2048 bit RSA private key
.......................................+++
............+++
writing new private key to 'localhost.key'
-----
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/example`
INFO server listening on 127.0.0.1:1690
INFO client read "response"
How about adding some example code?