iroh_net/tls/
certificate.rs

1//! X.509 certificate handling.
2//!
3//! This module handles generation, signing, and verification of certificates.
4//!
5//! Based on rust-libp2p/transports/tls/src/certificate.rs originally licensed under MIT by Parity
6//! Technologies (UK) Ltd.
7
8use std::sync::Arc;
9
10use der::{asn1::OctetStringRef, Decode, Encode, Sequence};
11use x509_parser::prelude::*;
12
13use crate::key::{PublicKey, SecretKey, Signature};
14
15/// The libp2p Public Key Extension is a X.509 extension
16/// with the Object Identifier 1.3.6.1.4.1.53594.1.1,
17/// allocated by IANA to the libp2p project at Protocol Labs.
18const P2P_EXT_OID: [u64; 9] = [1, 3, 6, 1, 4, 1, 53594, 1, 1];
19
20/// The peer signs the concatenation of the string `libp2p-tls-handshake:`
21/// and the public key that it used to generate the certificate carrying
22/// the libp2p Public Key Extension, using its private host key.
23/// This signature provides cryptographic proof that the peer was
24/// in possession of the private host key at the time the certificate was signed.
25const P2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:";
26
27// Certificates MUST use the NamedCurve encoding for elliptic curve parameters.
28// Similarly, hash functions with an output length less than 256 bits MUST NOT be used.
29static P2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_P256_SHA256;
30
31#[derive(Debug)]
32pub(crate) struct AlwaysResolvesCert(Arc<rustls::sign::CertifiedKey>);
33
34impl AlwaysResolvesCert {
35    pub(crate) fn new(
36        cert: rustls::pki_types::CertificateDer<'static>,
37        key: &rustls::pki_types::PrivateKeyDer<'_>,
38    ) -> Result<Self, rustls::Error> {
39        let certified_key = rustls::sign::CertifiedKey::new(
40            vec![cert],
41            rustls::crypto::ring::sign::any_ecdsa_type(key)?,
42        );
43        Ok(Self(Arc::new(certified_key)))
44    }
45}
46
47impl rustls::client::ResolvesClientCert for AlwaysResolvesCert {
48    fn resolve(
49        &self,
50        _root_hint_subjects: &[&[u8]],
51        _sigschemes: &[rustls::SignatureScheme],
52    ) -> Option<Arc<rustls::sign::CertifiedKey>> {
53        Some(Arc::clone(&self.0))
54    }
55
56    fn has_certs(&self) -> bool {
57        true
58    }
59}
60
61impl rustls::server::ResolvesServerCert for AlwaysResolvesCert {
62    fn resolve(
63        &self,
64        _client_hello: rustls::server::ClientHello<'_>,
65    ) -> Option<Arc<rustls::sign::CertifiedKey>> {
66        Some(Arc::clone(&self.0))
67    }
68}
69
70/// The public host key and the signature are ANS.1-encoded
71/// into the SignedKey data structure, which is carried  in the libp2p Public Key Extension.
72#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
73struct SignedKey<'a> {
74    public_key: OctetStringRef<'a>,
75    signature: OctetStringRef<'a>,
76}
77
78/// Generates a self-signed TLS certificate that includes a libp2p-specific
79/// certificate extension containing the public key of the given secret key.
80pub fn generate(
81    identity_secret_key: &SecretKey,
82) -> Result<
83    (
84        rustls::pki_types::CertificateDer<'static>,
85        rustls::pki_types::PrivateKeyDer<'static>,
86    ),
87    GenError,
88> {
89    // SecretKey used to sign the certificate.
90    // SHOULD NOT be related to the host's key.
91    // Endpoints MAY generate a new key and certificate
92    // for every connection attempt, or they MAY reuse the same key
93    // and certificate for multiple connections.
94    let certificate_keypair = rcgen::KeyPair::generate(P2P_SIGNATURE_ALGORITHM)?;
95    let rustls_key =
96        rustls::pki_types::PrivateKeyDer::try_from(certificate_keypair.serialize_der()).unwrap();
97    let certificate = {
98        let mut params = rcgen::CertificateParams::new(vec![]);
99        params.distinguished_name = rcgen::DistinguishedName::new();
100        params.custom_extensions.push(make_libp2p_extension(
101            identity_secret_key,
102            &certificate_keypair,
103        )?);
104        params.alg = P2P_SIGNATURE_ALGORITHM;
105        params.key_pair = Some(certificate_keypair);
106        rcgen::Certificate::from_params(params)?
107    };
108
109    let rustls_certificate = rustls::pki_types::CertificateDer::from(certificate.serialize_der()?);
110
111    Ok((rustls_certificate, rustls_key))
112}
113
114/// Attempts to parse the provided bytes as a [`P2pCertificate`].
115///
116/// For this to succeed, the certificate must contain the specified extension and the signature must
117/// match the embedded public key.
118pub fn parse<'a>(
119    certificate: &'a rustls::pki_types::CertificateDer<'_>,
120) -> Result<P2pCertificate<'a>, ParseError> {
121    let certificate = parse_unverified(certificate.as_ref())?;
122
123    certificate.verify()?;
124
125    Ok(certificate)
126}
127
128/// An X.509 certificate with a libp2p-specific extension
129/// is used to secure libp2p connections.
130#[derive(Debug)]
131pub struct P2pCertificate<'a> {
132    certificate: X509Certificate<'a>,
133    /// This is a specific libp2p Public Key Extension with two values:
134    /// * the public host key
135    /// * a signature performed using the private host key
136    extension: P2pExtension,
137}
138
139/// The contents of the specific libp2p extension, containing the public host key
140/// and a signature performed using the private host key.
141#[derive(Debug)]
142pub struct P2pExtension {
143    public_key: crate::key::PublicKey,
144    /// This signature provides cryptographic proof that the peer was
145    /// in possession of the private host key at the time the certificate was signed.
146    signature: crate::key::Signature,
147}
148
149/// An error that occurs during certificate generation.
150#[derive(Debug, thiserror::Error)]
151#[error(transparent)]
152pub struct GenError(#[from] rcgen::Error);
153
154/// An error that occurs during certificate parsing.
155#[derive(Debug, thiserror::Error)]
156#[error(transparent)]
157pub struct ParseError(#[from] pub(crate) webpki::Error);
158
159/// An error that occurs during signature verification.
160#[derive(Debug, thiserror::Error)]
161#[error(transparent)]
162pub struct VerificationError(#[from] pub(crate) webpki::Error);
163
164/// Internal function that only parses but does not verify the certificate.
165///
166/// Useful for testing but unsuitable for production.
167fn parse_unverified(der_input: &[u8]) -> Result<P2pCertificate, webpki::Error> {
168    let x509 = X509Certificate::from_der(der_input)
169        .map(|(_rest_input, x509)| x509)
170        .map_err(|_| webpki::Error::BadDer)?;
171
172    let p2p_ext_oid = der_parser::oid::Oid::from(&P2P_EXT_OID)
173        .expect("This is a valid OID of p2p extension; qed");
174
175    let mut libp2p_extension = None;
176
177    for ext in x509.extensions() {
178        let oid = &ext.oid;
179        if oid == &p2p_ext_oid && libp2p_extension.is_some() {
180            // The extension was already parsed
181            return Err(webpki::Error::BadDer);
182        }
183
184        if oid == &p2p_ext_oid {
185            let signed_key =
186                SignedKey::from_der(ext.value).map_err(|_| webpki::Error::ExtensionValueInvalid)?;
187            let public_key_raw = signed_key.public_key.as_bytes();
188            let public_key =
189                PublicKey::try_from(public_key_raw).map_err(|_| webpki::Error::UnknownIssuer)?;
190
191            let signature = Signature::from_slice(signed_key.signature.as_bytes())
192                .map_err(|_| webpki::Error::UnknownIssuer)?;
193            let ext = P2pExtension {
194                public_key,
195                signature,
196            };
197            libp2p_extension = Some(ext);
198            continue;
199        }
200
201        if ext.critical {
202            // Endpoints MUST abort the connection attempt if the certificate
203            // contains critical extensions that the endpoint does not understand.
204            return Err(webpki::Error::UnsupportedCriticalExtension);
205        }
206
207        // Implementations MUST ignore non-critical extensions with unknown OIDs.
208    }
209
210    // The certificate MUST contain the libp2p Public Key Extension.
211    // If this extension is missing, endpoints MUST abort the connection attempt.
212    let extension = libp2p_extension.ok_or(webpki::Error::BadDer)?;
213
214    let certificate = P2pCertificate {
215        certificate: x509,
216        extension,
217    };
218
219    Ok(certificate)
220}
221
222fn make_libp2p_extension(
223    identity_secret_key: &SecretKey,
224    certificate_keypair: &rcgen::KeyPair,
225) -> Result<rcgen::CustomExtension, rcgen::Error> {
226    // The peer signs the concatenation of the string `libp2p-tls-handshake:`
227    // and the public key that it used to generate the certificate carrying
228    // the libp2p Public Key Extension, using its private host key.
229    let signature = {
230        let mut msg = vec![];
231        msg.extend(P2P_SIGNING_PREFIX);
232        msg.extend(certificate_keypair.public_key_der());
233
234        identity_secret_key.sign(&msg)
235    };
236
237    let public_key = identity_secret_key.public();
238    let public_key_ref = OctetStringRef::new(&public_key.as_bytes()[..])
239        .map_err(|_| rcgen::Error::CouldNotParseKeyPair)?;
240    let signature = signature.to_bytes();
241    let signature_ref =
242        OctetStringRef::new(&signature).map_err(|_| rcgen::Error::CouldNotParseCertificate)?;
243    let key = SignedKey {
244        public_key: public_key_ref,
245        signature: signature_ref,
246    };
247
248    let mut extension_content = Vec::new();
249    key.encode_to_vec(&mut extension_content).expect("vec");
250
251    // This extension MAY be marked critical.
252    let mut ext = rcgen::CustomExtension::from_oid_content(&P2P_EXT_OID, extension_content);
253    ext.set_criticality(true);
254
255    Ok(ext)
256}
257
258impl P2pCertificate<'_> {
259    /// The [`PublicKey`] of the remote peer.
260    pub fn peer_id(&self) -> PublicKey {
261        self.extension.public_key
262    }
263
264    /// Verify the `signature` of the `message` signed by the secret key corresponding to the public key stored
265    /// in the certificate.
266    pub fn verify_signature(
267        &self,
268        signature_scheme: rustls::SignatureScheme,
269        message: &[u8],
270        signature: &[u8],
271    ) -> Result<(), VerificationError> {
272        let pk = self.public_key(signature_scheme)?;
273        pk.verify(message, signature)
274            .map_err(|_| webpki::Error::InvalidSignatureForPublicKey)?;
275
276        Ok(())
277    }
278
279    /// Get a [`ring::signature::UnparsedPublicKey`] for this `signature_scheme`.
280    /// Return `Error` if the `signature_scheme` does not match the public key signature
281    /// and hashing algorithm or if the `signature_scheme` is not supported.
282    fn public_key(
283        &self,
284        signature_scheme: rustls::SignatureScheme,
285    ) -> Result<ring::signature::UnparsedPublicKey<&[u8]>, webpki::Error> {
286        use ring::signature;
287        use rustls::SignatureScheme::*;
288
289        let current_signature_scheme = self.signature_scheme()?;
290        if signature_scheme != current_signature_scheme {
291            // This certificate was signed with a different signature scheme
292            return Err(webpki::Error::UnsupportedSignatureAlgorithmForPublicKey);
293        }
294
295        let verification_algorithm: &dyn signature::VerificationAlgorithm = match signature_scheme {
296            ECDSA_NISTP256_SHA256 => &signature::ECDSA_P256_SHA256_ASN1,
297            ECDSA_NISTP384_SHA384 => &signature::ECDSA_P384_SHA384_ASN1,
298            ECDSA_NISTP521_SHA512 => {
299                // See https://github.com/briansmith/ring/issues/824
300                return Err(webpki::Error::UnsupportedSignatureAlgorithm);
301            }
302            ED25519 => &signature::ED25519,
303            ED448 => {
304                // See https://github.com/briansmith/ring/issues/463
305                return Err(webpki::Error::UnsupportedSignatureAlgorithm);
306            }
307            // No support for RSA
308            RSA_PKCS1_SHA256 | RSA_PKCS1_SHA384 | RSA_PKCS1_SHA512 | RSA_PSS_SHA256
309            | RSA_PSS_SHA384 | RSA_PSS_SHA512 => {
310                return Err(webpki::Error::UnsupportedSignatureAlgorithm)
311            }
312            // Similarly, hash functions with an output length less than 256 bits
313            // MUST NOT be used, due to the possibility of collision attacks.
314            // In particular, MD5 and SHA1 MUST NOT be used.
315            RSA_PKCS1_SHA1 => return Err(webpki::Error::UnsupportedSignatureAlgorithm),
316            ECDSA_SHA1_Legacy => return Err(webpki::Error::UnsupportedSignatureAlgorithm),
317            Unknown(_) => return Err(webpki::Error::UnsupportedSignatureAlgorithm),
318            _ => return Err(webpki::Error::UnsupportedSignatureAlgorithm),
319        };
320        let spki = &self.certificate.tbs_certificate.subject_pki;
321        let key = signature::UnparsedPublicKey::new(
322            verification_algorithm,
323            spki.subject_public_key.as_ref(),
324        );
325
326        Ok(key)
327    }
328
329    /// This method validates the certificate according to libp2p TLS 1.3 specs.
330    /// The certificate MUST:
331    /// 1. be valid at the time it is received by the peer;
332    /// 2. use the NamedCurve encoding;
333    /// 3. use hash functions with an output length not less than 256 bits;
334    /// 4. be self signed;
335    /// 5. contain a valid signature in the specific libp2p extension.
336    fn verify(&self) -> Result<(), webpki::Error> {
337        use webpki::Error;
338
339        // The certificate MUST have NotBefore and NotAfter fields set
340        // such that the certificate is valid at the time it is received by the peer.
341        if !self.certificate.validity().is_valid() {
342            return Err(Error::InvalidCertValidity);
343        }
344
345        // Certificates MUST use the NamedCurve encoding for elliptic curve parameters.
346        // Similarly, hash functions with an output length less than 256 bits
347        // MUST NOT be used, due to the possibility of collision attacks.
348        // In particular, MD5 and SHA1 MUST NOT be used.
349        // Endpoints MUST abort the connection attempt if it is not used.
350        let signature_scheme = self.signature_scheme()?;
351        // Endpoints MUST abort the connection attempt if the certificate’s
352        // self-signature is not valid.
353        let raw_certificate = self.certificate.tbs_certificate.as_ref();
354        let signature = self.certificate.signature_value.as_ref();
355        // check if self signed
356        self.verify_signature(signature_scheme, raw_certificate, signature)
357            .map_err(|_| Error::SignatureAlgorithmMismatch)?;
358
359        let subject_pki = self.certificate.public_key().raw;
360
361        // The peer signs the concatenation of the string `libp2p-tls-handshake:`
362        // and the public key that it used to generate the certificate carrying
363        // the libp2p Public Key Extension, using its private host key.
364        let mut msg = vec![];
365        msg.extend(P2P_SIGNING_PREFIX);
366        msg.extend(subject_pki);
367
368        // This signature provides cryptographic proof that the peer was in possession
369        // of the private host key at the time the certificate was signed.
370        // Peers MUST verify the signature, and abort the connection attempt
371        // if signature verification fails.
372        let user_owns_sk = self
373            .extension
374            .public_key
375            .verify(&msg, &self.extension.signature)
376            .is_ok();
377        if !user_owns_sk {
378            return Err(Error::UnknownIssuer);
379        }
380
381        Ok(())
382    }
383
384    /// Return the signature scheme corresponding to [`AlgorithmIdentifier`]s
385    /// of `subject_pki` and `signature_algorithm`
386    /// according to <https://www.rfc-editor.org/rfc/rfc8446.html#section-4.2.3>.
387    fn signature_scheme(&self) -> Result<rustls::SignatureScheme, webpki::Error> {
388        // Certificates MUST use the NamedCurve encoding for elliptic curve parameters.
389        // Endpoints MUST abort the connection attempt if it is not used.
390        use oid_registry::*;
391        use rustls::SignatureScheme::*;
392
393        let signature_algorithm = &self.certificate.signature_algorithm;
394        let pki_algorithm = &self.certificate.tbs_certificate.subject_pki.algorithm;
395
396        if pki_algorithm.algorithm == OID_KEY_TYPE_EC_PUBLIC_KEY {
397            let signature_param = pki_algorithm
398                .parameters
399                .as_ref()
400                .ok_or(webpki::Error::BadDer)?
401                .as_oid()
402                .map_err(|_| webpki::Error::BadDer)?;
403            if signature_param == OID_EC_P256
404                && signature_algorithm.algorithm == OID_SIG_ECDSA_WITH_SHA256
405            {
406                return Ok(ECDSA_NISTP256_SHA256);
407            }
408            if signature_param == OID_NIST_EC_P384
409                && signature_algorithm.algorithm == OID_SIG_ECDSA_WITH_SHA384
410            {
411                return Ok(ECDSA_NISTP384_SHA384);
412            }
413            if signature_param == OID_NIST_EC_P521
414                && signature_algorithm.algorithm == OID_SIG_ECDSA_WITH_SHA512
415            {
416                return Ok(ECDSA_NISTP521_SHA512);
417            }
418            return Err(webpki::Error::UnsupportedSignatureAlgorithm);
419        }
420
421        if signature_algorithm.algorithm == OID_SIG_ED25519 {
422            return Ok(ED25519);
423        }
424        if signature_algorithm.algorithm == OID_SIG_ED448 {
425            return Ok(ED448);
426        }
427
428        Err(webpki::Error::UnsupportedSignatureAlgorithm)
429    }
430}
431
432#[cfg(test)]
433mod tests {
434    use super::*;
435
436    #[test]
437    fn sanity_check() {
438        let secret_key = SecretKey::generate();
439
440        let (cert, _) = generate(&secret_key).unwrap();
441        let parsed_cert = parse(&cert).unwrap();
442
443        assert!(parsed_cert.verify().is_ok());
444        assert_eq!(secret_key.public(), parsed_cert.extension.public_key);
445    }
446}