Skip to main content

snap_tun/
cert_validator.rs

1// Copyright 2025 Anapaya Systems
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//   http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! Module containing an implementation of a certificate validator.
15
16use anapaya_quinn::rustls::{self, client::danger::ServerCertVerified};
17use x509_cert::{Certificate, der::Decode, spki::ObjectIdentifier};
18
19/// Validation only succeeds if the subject's Ed25519 public key matches the
20/// configured public key.
21///
22/// The intended use case is verifying the identity of a remote server for which
23/// the public key is known and the server presents a self-signed certificate
24/// containing an Ed25519 public key.
25///
26/// Other than the server's public key, all parameters of the server's
27/// certificate are ignored.
28#[derive(Debug)]
29pub struct Ed25519ServerCertValidator(pub [u8; 32]);
30
31// OID for Ed25519 (1.3.101.112)
32const ED25519_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.112");
33
34impl rustls::client::danger::ServerCertVerifier for Ed25519ServerCertValidator {
35    fn verify_server_cert(
36        &self,
37        end_entity: &rustls::pki_types::CertificateDer<'_>,
38        _intermediates: &[rustls::pki_types::CertificateDer<'_>],
39        _server_name: &rustls::pki_types::ServerName<'_>,
40        _ocsp_response: &[u8],
41        _now: rustls::pki_types::UnixTime,
42    ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
43        verify_ed25519_public_key(&self.0, end_entity)
44    }
45
46    fn verify_tls12_signature(
47        &self,
48        _message: &[u8],
49        _cert: &rustls::pki_types::CertificateDer<'_>,
50        _dss: &rustls::DigitallySignedStruct,
51    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
52        Err(rustls::Error::PeerIncompatible(
53            rustls::PeerIncompatible::Tls12NotOffered,
54        ))
55    }
56
57    fn verify_tls13_signature(
58        &self,
59        message: &[u8],
60        cert: &rustls::pki_types::CertificateDer<'_>,
61        dss: &rustls::DigitallySignedStruct,
62    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
63        let provider = rustls::crypto::ring::default_provider();
64        let all_algs = provider.signature_verification_algorithms;
65
66        if dss.scheme != rustls::SignatureScheme::ED25519 {
67            return Err(rustls::Error::PeerIncompatible(
68                rustls::PeerIncompatible::NoSignatureSchemesInCommon,
69            ));
70        }
71
72        rustls::crypto::verify_tls13_signature(message, cert, dss, &all_algs)
73    }
74
75    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
76        vec![rustls::SignatureScheme::ED25519]
77    }
78}
79
80fn verify_ed25519_public_key(
81    expected_pubkey: &[u8; 32],
82    end_entity: &[u8],
83) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
84    let certificate = Certificate::from_der(end_entity)
85        .map_err(|e| rustls::Error::General(format!("failed to parse certificate: {e}")))?;
86    let spki = &certificate.tbs_certificate.subject_public_key_info;
87
88    if spki.algorithm.oid != ED25519_OID {
89        return Err(rustls::Error::General(
90            "server's public key algorithm is not Ed25519.".to_string(),
91        ));
92    }
93
94    let public_key_bytes = match spki.subject_public_key.as_bytes() {
95        Some(bytes) => bytes,
96        None => {
97            return Err(rustls::Error::General(
98                "failed to extract public key bytes from certificate.".to_string(),
99            ));
100        }
101    };
102
103    if expected_pubkey.as_slice() != public_key_bytes {
104        return Err(rustls::Error::General(
105            "server's public key did not match the expected pinned public key.".to_string(),
106        ));
107    }
108    Ok(ServerCertVerified::assertion())
109}
110
111#[cfg(test)]
112mod tests {
113    use anapaya_quinn::rustls::pki_types::CertificateDer;
114    use ed25519_dalek::pkcs8::EncodePrivateKey;
115
116    use super::verify_ed25519_public_key;
117
118    #[test]
119    fn verify_ed25519_certificate_succeeds() {
120        let seed = [84u8; 32];
121        let dalek_keypair = ed25519_dalek::SigningKey::from_bytes(&seed);
122
123        let expected_pubkey = *dalek_keypair.verifying_key().as_bytes();
124
125        let kp = ed25519_dalek::pkcs8::KeypairBytes {
126            secret_key: *dalek_keypair.as_bytes(),
127            public_key: Some(ed25519_dalek::pkcs8::PublicKeyBytes(
128                *dalek_keypair.verifying_key().as_bytes(),
129            )),
130        };
131        let pkcs8 = kp.to_pkcs8_der().unwrap();
132        let pem = pem::Pem::new("PRIVATE KEY", pkcs8.as_bytes());
133        let pem_str = pem::encode(&pem);
134        let key_pair = rcgen::KeyPair::from_pem(&pem_str).unwrap();
135
136        // Prepare certificate parameters.
137        let cert = rcgen::CertificateParams::new(vec!["test".into()])
138            .unwrap()
139            .self_signed(&key_pair)
140            .unwrap();
141
142        let cert_der = CertificateDer::from(cert.der().to_vec());
143
144        assert!(verify_ed25519_public_key(&expected_pubkey, &cert_der).is_ok());
145    }
146}