use std::sync::Arc;
use anyhow::Result;
use pkcs8::EncodePrivateKey;
use rustls::client::danger::HandshakeSignatureValid;
use rustls::crypto::{KeyProvider, WebPkiSupportedAlgorithms};
use rustls::pki_types::CertificateDer;
use rustls::sign::CertifiedKey;
use rustls::{DigitallySignedStruct, DistinguishedName, InconsistentKeys, SignatureScheme};
use rustls_pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer, ServerName, SubjectPublicKeyInfoDer};
use crate::types::PeerId;
pub(crate) fn generate_cert(
keypair: &ed25519::KeypairBytes,
key_provider: &dyn KeyProvider,
) -> Result<Arc<CertifiedKey>> {
let pkcs8 = keypair.to_pkcs8_der()?;
let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(pkcs8.as_bytes().to_vec()));
let server_private_key = key_provider.load_private_key(key_der)?;
let server_public_key = server_private_key
.public_key()
.ok_or(rustls::Error::InconsistentKeys(InconsistentKeys::Unknown))
.expect("cannot load public key");
let server_public_key_as_cert = CertificateDer::from(server_public_key.to_vec());
let certified_key = Arc::new(CertifiedKey::new(
vec![server_public_key_as_cert],
server_private_key,
));
Ok(certified_key)
}
pub(crate) fn peer_id_from_certificate(cert: &CertificateDer<'_>) -> Result<PeerId, rustls::Error> {
const PUBKEY_PREFIX: &[u8] = &[
0x30, 42, 0x30, 5, 6, 3, 43, 101, 112, 0x03, 33, 0, ];
fn parse_pubkey(pubkey: &[u8]) -> Option<PeerId> {
let pubkey: [u8; 32] = pubkey.strip_prefix(PUBKEY_PREFIX)?.try_into().ok()?;
tycho_crypto::ed25519::PublicKey::from_bytes(pubkey).map(PeerId::from)
}
parse_pubkey(cert.as_ref())
.ok_or_else(|| rustls::Error::InvalidCertificate(rustls::CertificateError::BadEncoding))
}
#[derive(Debug)]
pub(crate) struct CertVerifierWithPeerId {
peer_id: PeerId,
}
impl CertVerifierWithPeerId {
pub fn new(peer_id: &PeerId) -> Self {
Self { peer_id: *peer_id }
}
}
impl rustls::client::danger::ServerCertVerifier for CertVerifierWithPeerId {
fn verify_server_cert(
&self,
end_entity: &CertificateDer<'_>,
intermediates: &[CertificateDer<'_>],
_: &ServerName<'_>,
_: &[u8],
_: rustls::pki_types::UnixTime,
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
if !intermediates.is_empty() {
return Err(rustls::Error::InvalidCertificate(
rustls::CertificateError::BadEncoding,
));
}
let peer_id = peer_id_from_certificate(end_entity)?;
if peer_id != self.peer_id {
return Err(rustls::Error::InvalidCertificate(
rustls::CertificateError::Other(rustls::OtherError(Arc::new(
CertificatePeerIdMismatch,
))),
));
}
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_: &[u8],
_: &CertificateDer<'_>,
_: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
Err(tls12_unexpected())
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
rustls::crypto::verify_tls13_signature_with_raw_key(
message,
&SubjectPublicKeyInfoDer::from(cert.as_ref()),
dss,
&SUPPORTED_SIG_ALGS,
)
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
SUPPORTED_VERIFY_SCHEMES.to_vec()
}
fn requires_raw_public_keys(&self) -> bool {
true
}
}
#[derive(Debug)]
pub(crate) struct CertVerifier;
impl rustls::server::danger::ClientCertVerifier for CertVerifier {
fn offer_client_auth(&self) -> bool {
true
}
fn client_auth_mandatory(&self) -> bool {
true
}
fn verify_client_cert(
&self,
end_entity: &CertificateDer<'_>,
intermediates: &[CertificateDer<'_>],
_: rustls::pki_types::UnixTime,
) -> Result<rustls::server::danger::ClientCertVerified, rustls::Error> {
if !intermediates.is_empty() {
return Err(rustls::Error::InvalidCertificate(
rustls::CertificateError::BadEncoding,
));
}
peer_id_from_certificate(end_entity)
.map(|_| rustls::server::danger::ClientCertVerified::assertion())
}
fn root_hint_subjects(&self) -> &[DistinguishedName] {
&[]
}
fn verify_tls12_signature(
&self,
_: &[u8],
_: &CertificateDer<'_>,
_: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
Err(tls12_unexpected())
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
rustls::crypto::verify_tls13_signature_with_raw_key(
message,
&SubjectPublicKeyInfoDer::from(cert.as_ref()),
dss,
&SUPPORTED_SIG_ALGS,
)
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
SUPPORTED_VERIFY_SCHEMES.to_vec()
}
fn requires_raw_public_keys(&self) -> bool {
true
}
}
fn tls12_unexpected() -> rustls::Error {
rustls::Error::InvalidCertificate(rustls::CertificateError::BadEncoding)
}
pub static SUPPORTED_SIG_ALGS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms {
all: &[webpki::ring::ED25519],
mapping: &[(rustls::SignatureScheme::ED25519, &[webpki::ring::ED25519])],
};
static SUPPORTED_VERIFY_SCHEMES: &[SignatureScheme] = &[SignatureScheme::ED25519];
#[derive(thiserror::Error, Debug)]
#[error("certificate peer id mismatch")]
struct CertificatePeerIdMismatch;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gen_cert_works() {
let secret = rand::random::<tycho_crypto::ed25519::SecretKey>();
let public = tycho_crypto::ed25519::PublicKey::from(&secret);
let crypto = rustls::crypto::ring::default_provider();
let cert = generate_cert(
&ed25519::KeypairBytes {
secret_key: secret.to_bytes(),
public_key: None,
},
crypto.key_provider,
)
.unwrap();
let peer_id = peer_id_from_certificate(cert.end_entity_cert().unwrap()).unwrap();
assert_eq!(peer_id.as_bytes(), public.as_bytes());
}
}