use std::sync::Arc;
use rustls::{
RootCertStore, SignatureScheme,
crypto::CryptoProvider,
pki_types::{CertificateDer, UnixTime, pem::PemObject},
server::{VerifierBuilderError, WebPkiClientVerifier, danger::ClientCertVerifier},
};
use thiserror::Error;
use webpki::EndEntityCert;
use crate::x509::{
errors::X509SignatureVerificationError,
raw_x509_signature::{RawX509Signature, X509SignatureScheme},
x509_verify::RawX509Verifier,
};
#[derive(Error, Debug)]
pub enum RustX509VerifyError {
#[error("failed to parse certificate")]
CertificateParse(rustls::pki_types::pem::Error),
#[error("failed to build verifier {0}")]
VerifierBuilder(VerifierBuilderError),
#[error("failed to add certificate to root store {0}")]
Store(rustls::Error),
}
#[derive(Debug, Clone)]
pub struct RustRawX509Verifier {
verifier: Arc<dyn ClientCertVerifier>,
}
impl RustRawX509Verifier {
pub fn new_from_pem_data(ca_certs_pem: &str) -> Result<Self, RustX509VerifyError> {
let mut root_store = RootCertStore::empty();
for result in CertificateDer::pem_slice_iter(ca_certs_pem.as_bytes()) {
root_store
.add(result.map_err(RustX509VerifyError::CertificateParse)?)
.map_err(RustX509VerifyError::Store)?;
}
let verifier = WebPkiClientVerifier::builder(Arc::new(root_store))
.build()
.map_err(RustX509VerifyError::VerifierBuilder)?;
Ok(Self { verifier })
}
}
impl RawX509Verifier for RustRawX509Verifier {
fn verify(
&self,
message: &[u8],
sig: &RawX509Signature,
) -> Result<(), X509SignatureVerificationError> {
let mut cert_iter = CertificateDer::pem_slice_iter(sig.certificate_chain.as_bytes());
let Some(Ok(leaf_cert)) = cert_iter.next() else {
tracing::warn!("Missing or invalid leaf certificate");
return Err(X509SignatureVerificationError::MissingOrInvalidFirstCertificate);
};
let Ok(intermediate_certs): Result<Vec<_>, _> = cert_iter.collect() else {
tracing::warn!("Invalid certificate in the chain");
return Err(X509SignatureVerificationError::InvalidCertificateInChain);
};
if self
.verifier
.verify_client_cert(&leaf_cert, intermediate_certs.as_ref(), UnixTime::now())
.is_err()
{
return Err(X509SignatureVerificationError::CertificateExpired);
}
let Some(provider) = CryptoProvider::get_default() else {
tracing::error!("Unable to get default rustls crypto provider");
return Err(X509SignatureVerificationError::MissingCryptoProvider);
};
let rustls_signature_scheme = match sig.signature_scheme {
X509SignatureScheme::RsaPssSha512 => SignatureScheme::RSA_PSS_SHA512,
};
let Some(alg) = provider
.signature_verification_algorithms
.mapping
.iter()
.filter(|item| item.0 == rustls_signature_scheme)
.filter_map(|item| item.1.first().copied())
.next()
else {
tracing::warn!("Signature scheme {:?} not supported", sig.signature_scheme);
return Err(X509SignatureVerificationError::UnsupportedSignatureScheme(
sig.signature_scheme.clone(),
));
};
let cert = EndEntityCert::try_from(&leaf_cert)
.inspect_err(|_| tracing::warn!("Unable to parse certificate"))
.map_err(|e| X509SignatureVerificationError::Custom(e.into()))?;
cert.verify_signature(alg, message, sig.signature_bytes.as_slice())
.inspect_err(|e| tracing::warn!("Signature verification failed: {e}"))
.map_err(|e| X509SignatureVerificationError::Custom(e.into()))
}
}
#[cfg(test)]
mod tests {
use assert_matches2::assert_let;
use matrix_sdk_test::async_test;
use crate::x509::{
RawX509Signer, RawX509Verifier, X509SignatureVerificationError,
rust_raw_x509_signer::RustRawX509Signer, rust_raw_x509_verifier::RustRawX509Verifier,
tests::cert_and_key_with_email_in_subject_distinguished_name,
};
#[async_test]
async fn test_can_verify() {
let (cert, signing_key) =
cert_and_key_with_email_in_subject_distinguished_name("alice@localhost");
let cert_pem = cert.pem();
let key_pem = signing_key.serialize_pem();
let x509_sign = RustRawX509Signer::new_from_pem_data(&cert_pem, &key_pem).unwrap();
let sig = x509_sign.sign(b"hello world".to_vec()).await.unwrap();
let x509_verify = RustRawX509Verifier::new_from_pem_data(&cert_pem).unwrap();
assert_let!(Ok(()) = x509_verify.verify(b"hello world", &sig));
assert_let!(
Err(X509SignatureVerificationError::Custom(e)) =
x509_verify.verify(b"Hello World", &sig)
);
assert_let!(
webpki::Error::InvalidSignatureForPublicKey = *e.downcast::<webpki::Error>().unwrap()
);
let sig_with_bad_certificate_chain = {
let mut sig = sig;
sig.certificate_chain = "".to_owned();
sig
};
assert_let!(Err(_) = x509_verify.verify(b"hello world", &sig_with_bad_certificate_chain));
}
}