Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
450 changes: 233 additions & 217 deletions Cargo.lock

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ tokio = { version = "1.45.0", features = [
"parking_lot",
] }
tracing = { version = "0.1.40", features = ["log"] }
hickory-resolver = "0.25.2"
hickory-resolver = "0.26"
uint = "0.10.0"
unsigned-varint = { version = "0.8.0", features = ["codec"] }
url = "2.5.4"
Expand All @@ -74,15 +74,17 @@ tokio-tungstenite = { version = "0.27.0", features = [
# End of websocket related dependencies.

# Quic related dependencies. Quic is an experimental feature flag. The dependencies must be updated.
quinn = { version = "0.9.3", default-features = false, features = [
"tls-rustls",
quinn = { version = "0.11.9", default-features = false, features = [
"runtime-tokio",
"rustls-ring",
], optional = true }
rustls = { version = "0.20.7", default-features = false, features = [
"dangerous_configuration",
rustls = { version = "0.23.38", default-features = false, features = [
"ring",
"std",
"logging",
], optional = true }
ring = { version = "0.17.14", optional = true }
webpki = { version = "0.22.4", optional = true }
webpki = { version = "0.22.4", default-features = false, features = ["std"], optional = true }
rcgen = { version = "0.14.5", optional = true }
# End of Quic related dependencies.

Expand Down
65 changes: 58 additions & 7 deletions src/crypto/tls/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use crate::{

// use libp2p_identity as identity;
// use libp2p_identity::PeerId;
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
use x509_parser::{prelude::*, signature_algorithm::SignatureAlgorithm};

/// The libp2p Public Key Extension is a X.509 extension
Expand All @@ -51,14 +52,16 @@ static P2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_
/// certificate extension containing the public key of the given keypair.
pub fn generate(
identity_keypair: &Keypair,
) -> Result<(rustls::Certificate, rustls::PrivateKey), GenError> {
) -> Result<(CertificateDer<'static>, PrivateKeyDer<'static>), GenError> {
// Keypair used to sign the certificate.
// SHOULD NOT be related to the host's key.
// Endpoints MAY generate a new key and certificate
// for every connection attempt, or they MAY reuse the same key
// and certificate for multiple connections.
let certificate_keypair = rcgen::KeyPair::generate_for(P2P_SIGNATURE_ALGORITHM)?;
let rustls_key = rustls::PrivateKey(certificate_keypair.serialize_der());
let rustls_key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(
certificate_keypair.serialize_der(),
));

let certificate = {
let mut params = rcgen::CertificateParams::new(vec![])?;
Expand All @@ -70,16 +73,61 @@ pub fn generate(
params.self_signed(&certificate_keypair)?
};

let rustls_certificate = rustls::Certificate(certificate.der().to_vec());
let rustls_certificate = certificate.der().clone();

Ok((rustls_certificate, rustls_key))
}

/// A `rustls` certificate resolver that always presents the same libp2p certificate.
///
/// We resolve through this instead of `ConfigBuilder::with_single_cert` /
/// `with_client_auth_cert` because rustls 0.23 validates the certificate passed to those methods
/// and rejects the libp2p extension, which is marked critical. A resolver bypasses that check.
#[derive(Debug)]
pub struct AlwaysResolvesCert(std::sync::Arc<rustls::sign::CertifiedKey>);

impl AlwaysResolvesCert {
pub fn new(
cert: CertificateDer<'static>,
key: &PrivateKeyDer<'_>,
) -> Result<Self, rustls::Error> {
let certified_key = rustls::sign::CertifiedKey::new(
vec![cert],
rustls::crypto::ring::sign::any_ecdsa_type(key)?,
);

Ok(Self(std::sync::Arc::new(certified_key)))
}
}

impl rustls::client::ResolvesClientCert for AlwaysResolvesCert {
fn resolve(
&self,
_root_hint_subjects: &[&[u8]],
_sigschemes: &[rustls::SignatureScheme],
) -> Option<std::sync::Arc<rustls::sign::CertifiedKey>> {
Some(std::sync::Arc::clone(&self.0))
}

fn has_certs(&self) -> bool {
true
}
}

impl rustls::server::ResolvesServerCert for AlwaysResolvesCert {
fn resolve(
&self,
_client_hello: rustls::server::ClientHello<'_>,
) -> Option<std::sync::Arc<rustls::sign::CertifiedKey>> {
Some(std::sync::Arc::clone(&self.0))
}
}

/// Attempts to parse the provided bytes as a [`P2pCertificate`].
///
/// For this to succeed, the certificate must contain the specified extension and the signature must
/// match the embedded public key.
pub fn parse(certificate: &rustls::Certificate) -> Result<P2pCertificate<'_>, ParseError> {
pub fn parse<'a>(certificate: &'a CertificateDer<'a>) -> Result<P2pCertificate<'a>, ParseError> {
let certificate = parse_unverified(certificate.as_ref())?;

certificate.verify()?;
Expand Down Expand Up @@ -292,6 +340,8 @@ impl P2pCertificate<'_> {
RSA_PKCS1_SHA1 => return Err(webpki::Error::UnsupportedSignatureAlgorithm),
ECDSA_SHA1_Legacy => return Err(webpki::Error::UnsupportedSignatureAlgorithm),
Unknown(_) => return Err(webpki::Error::UnsupportedSignatureAlgorithm),
// `rustls::SignatureScheme` is `#[non_exhaustive]` as of rustls 0.23.
_ => return Err(webpki::Error::UnsupportedSignatureAlgorithm),
};
let spki = &self.certificate.tbs_certificate.subject_pki;
let key = signature::UnparsedPublicKey::new(
Expand Down Expand Up @@ -487,7 +537,8 @@ mod tests {

#[test]
fn rsa_pss_sha384() {
let cert = rustls::Certificate(include_bytes!("./test_assets/rsa_pss_sha384.der").to_vec());
let cert =
CertificateDer::from(include_bytes!("./test_assets/rsa_pss_sha384.der").to_vec());

let cert = parse(&cert).unwrap();

Expand All @@ -508,7 +559,7 @@ mod tests {

#[test]
fn can_parse_certificate_with_ed25519_keypair() {
let certificate = rustls::Certificate(hex!("308201773082011ea003020102020900f5bd0debaa597f52300a06082a8648ce3d04030230003020170d3735303130313030303030305a180f34303936303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d030107034200046bf9871220d71dcb3483ecdfcbfcc7c103f8509d0974b3c18ab1f1be1302d643103a08f7a7722c1b247ba3876fe2c59e26526f479d7718a85202ddbe47562358a37f307d307b060a2b0601040183a25a01010101ff046a30680424080112207fda21856709c5ae12fd6e8450623f15f11955d384212b89f56e7e136d2e17280440aaa6bffabe91b6f30c35e3aa4f94b1188fed96b0ffdd393f4c58c1c047854120e674ce64c788406d1c2c4b116581fd7411b309881c3c7f20b46e54c7e6fe7f0f300a06082a8648ce3d040302034700304402207d1a1dbd2bda235ff2ec87daf006f9b04ba076a5a5530180cd9c2e8f6399e09d0220458527178c7e77024601dbb1b256593e9b96d961b96349d1f560114f61a87595").to_vec());
let certificate = CertificateDer::from(hex!("308201773082011ea003020102020900f5bd0debaa597f52300a06082a8648ce3d04030230003020170d3735303130313030303030305a180f34303936303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d030107034200046bf9871220d71dcb3483ecdfcbfcc7c103f8509d0974b3c18ab1f1be1302d643103a08f7a7722c1b247ba3876fe2c59e26526f479d7718a85202ddbe47562358a37f307d307b060a2b0601040183a25a01010101ff046a30680424080112207fda21856709c5ae12fd6e8450623f15f11955d384212b89f56e7e136d2e17280440aaa6bffabe91b6f30c35e3aa4f94b1188fed96b0ffdd393f4c58c1c047854120e674ce64c788406d1c2c4b116581fd7411b309881c3c7f20b46e54c7e6fe7f0f300a06082a8648ce3d040302034700304402207d1a1dbd2bda235ff2ec87daf006f9b04ba076a5a5530180cd9c2e8f6399e09d0220458527178c7e77024601dbb1b256593e9b96d961b96349d1f560114f61a87595").to_vec());

let peer_id = parse(&certificate).unwrap().peer_id();

Expand All @@ -522,7 +573,7 @@ mod tests {

#[test]
fn fails_to_parse_bad_certificate_with_ed25519_keypair() {
let certificate = rustls::Certificate(hex!("308201773082011da003020102020830a73c5d896a1109300a06082a8648ce3d04030230003020170d3735303130313030303030305a180f34303936303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d03010703420004bbe62df9a7c1c46b7f1f21d556deec5382a36df146fb29c7f1240e60d7d5328570e3b71d99602b77a65c9b3655f62837f8d66b59f1763b8c9beba3be07778043a37f307d307b060a2b0601040183a25a01010101ff046a3068042408011220ec8094573afb9728088860864f7bcea2d4fd412fef09a8e2d24d482377c20db60440ecabae8354afa2f0af4b8d2ad871e865cb5a7c0c8d3dbdbf42de577f92461a0ebb0a28703e33581af7d2a4f2270fc37aec6261fcc95f8af08f3f4806581c730a300a06082a8648ce3d040302034800304502202dfb17a6fa0f94ee0e2e6a3b9fb6e986f311dee27392058016464bd130930a61022100ba4b937a11c8d3172b81e7cd04aedb79b978c4379c2b5b24d565dd5d67d3cb3c").to_vec());
let certificate = CertificateDer::from(hex!("308201773082011da003020102020830a73c5d896a1109300a06082a8648ce3d04030230003020170d3735303130313030303030305a180f34303936303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d03010703420004bbe62df9a7c1c46b7f1f21d556deec5382a36df146fb29c7f1240e60d7d5328570e3b71d99602b77a65c9b3655f62837f8d66b59f1763b8c9beba3be07778043a37f307d307b060a2b0601040183a25a01010101ff046a3068042408011220ec8094573afb9728088860864f7bcea2d4fd412fef09a8e2d24d482377c20db60440ecabae8354afa2f0af4b8d2ad871e865cb5a7c0c8d3dbdbf42de577f92461a0ebb0a28703e33581af7d2a4f2270fc37aec6261fcc95f8af08f3f4806581c730a300a06082a8648ce3d040302034800304502202dfb17a6fa0f94ee0e2e6a3b9fb6e986f311dee27392058016464bd130930a61022100ba4b937a11c8d3172b81e7cd04aedb79b978c4379c2b5b24d565dd5d67d3cb3c").to_vec());

match parse(&certificate) {
Ok(_) => unreachable!(),
Expand Down
25 changes: 15 additions & 10 deletions src/crypto/tls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,16 @@ pub fn make_server_config(
) -> Result<rustls::ServerConfig, certificate::GenError> {
let (certificate, private_key) = certificate::generate(keypair)?;

let mut crypto = rustls::ServerConfig::builder()
.with_cipher_suites(verifier::CIPHERSUITES)
.with_safe_default_kx_groups()
let cert_resolver = Arc::new(
certificate::AlwaysResolvesCert::new(certificate, &private_key)
.expect("Server cert key DER is valid; qed"),
);

let mut crypto = rustls::ServerConfig::builder_with_provider(verifier::crypto_provider())
.with_protocol_versions(verifier::PROTOCOL_VERSIONS)
.expect("Cipher suites and kx groups are configured; qed")
.with_client_cert_verifier(Arc::new(verifier::Libp2pCertificateVerifier::new()))
.with_single_cert(vec![certificate], private_key)
.expect("Server cert key DER is valid; qed");
.with_cert_resolver(cert_resolver);
crypto.alpn_protocols = vec![P2P_ALPN.to_vec()];

Ok(crypto)
Expand All @@ -60,16 +62,19 @@ pub fn make_client_config(
) -> Result<rustls::ClientConfig, certificate::GenError> {
let (certificate, private_key) = certificate::generate(keypair)?;

let mut crypto = rustls::ClientConfig::builder()
.with_cipher_suites(verifier::CIPHERSUITES)
.with_safe_default_kx_groups()
let cert_resolver = Arc::new(
certificate::AlwaysResolvesCert::new(certificate, &private_key)
.expect("Client cert key DER is valid; qed"),
);

let mut crypto = rustls::ClientConfig::builder_with_provider(verifier::crypto_provider())
.with_protocol_versions(verifier::PROTOCOL_VERSIONS)
.expect("Cipher suites and kx groups are configured; qed")
.dangerous()
.with_custom_certificate_verifier(Arc::new(
verifier::Libp2pCertificateVerifier::with_remote_peer_id(remote_peer_id),
))
.with_single_cert(vec![certificate], private_key)
.expect("Client cert key DER is valid; qed");
.with_client_cert_resolver(cert_resolver);
crypto.alpn_protocols = vec![P2P_ALPN.to_vec()];

Ok(crypto)
Expand Down
88 changes: 56 additions & 32 deletions src/crypto/tls/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,21 @@
use crate::{crypto::tls::certificate, PeerId};

use rustls::{
cipher_suite::{
TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, TLS13_CHACHA20_POLY1305_SHA256,
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
crypto::{
ring::cipher_suite::{
TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, TLS13_CHACHA20_POLY1305_SHA256,
},
CryptoProvider,
},
client::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
internal::msgs::handshake::DigitallySignedStruct,
server::{ClientCertVerified, ClientCertVerifier},
Certificate, DistinguishedNames, SignatureScheme, SupportedCipherSuite,
SupportedProtocolVersion,
pki_types::{CertificateDer, ServerName, UnixTime},
server::danger::{ClientCertVerified, ClientCertVerifier},
CertificateError, DigitallySignedStruct, DistinguishedName, SignatureScheme,
SupportedCipherSuite, SupportedProtocolVersion,
};

use std::sync::Arc;

/// The protocol versions supported by this verifier.
///
/// The spec says:
Expand All @@ -54,9 +59,23 @@ pub static CIPHERSUITES: &[SupportedCipherSuite] = &[
TLS13_AES_128_GCM_SHA256,
];

/// The [`CryptoProvider`] used for libp2p TLS.
///
/// It is the *ring* provider restricted to the TLS 1.3 cipher suites mandated by the libp2p spec.
/// rustls 0.23 no longer takes cipher suites and key-exchange groups on the config builder; they
/// are carried by the provider, which must be passed explicitly because no process-default
/// provider is installed (the `aws-lc-rs` default is disabled).
pub fn crypto_provider() -> Arc<CryptoProvider> {
Arc::new(CryptoProvider {
cipher_suites: CIPHERSUITES.to_vec(),
..rustls::crypto::ring::default_provider()
})
}

/// Implementation of the `rustls` certificate verification traits for libp2p.
///
/// Only TLS 1.3 is supported. TLS 1.2 should be disabled in the configuration of `rustls`.
#[derive(Debug)]
pub struct Libp2pCertificateVerifier {
/// The peer ID we intend to connect to
remote_peer_id: Option<PeerId>,
Expand Down Expand Up @@ -105,12 +124,11 @@ impl Libp2pCertificateVerifier {
impl ServerCertVerifier for Libp2pCertificateVerifier {
fn verify_server_cert(
&self,
end_entity: &Certificate,
intermediates: &[Certificate],
_server_name: &rustls::ServerName,
_scts: &mut dyn Iterator<Item = &[u8]>,
end_entity: &CertificateDer<'_>,
intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
_now: std::time::SystemTime,
_now: UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {
let peer_id = verify_presented_certs(end_entity, intermediates)?;

Expand All @@ -120,8 +138,8 @@ impl ServerCertVerifier for Libp2pCertificateVerifier {
// the certificate matches the peer ID they intended to connect to,
// and MUST abort the connection if there is a mismatch.
if remote_peer_id != peer_id {
return Err(rustls::Error::PeerMisbehavedError(
"Wrong peer ID in p2p extension".to_string(),
return Err(rustls::Error::InvalidCertificate(
CertificateError::ApplicationVerificationFailure,
));
}
}
Expand All @@ -132,7 +150,7 @@ impl ServerCertVerifier for Libp2pCertificateVerifier {
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &Certificate,
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
unreachable!("`PROTOCOL_VERSIONS` only allows TLS 1.3")
Expand All @@ -141,7 +159,7 @@ impl ServerCertVerifier for Libp2pCertificateVerifier {
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &Certificate,
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
verify_tls13_signature(cert, dss.scheme, message, dss.signature())
Expand All @@ -164,15 +182,15 @@ impl ClientCertVerifier for Libp2pCertificateVerifier {
true
}

fn client_auth_root_subjects(&self) -> Option<DistinguishedNames> {
Some(vec![])
fn root_hint_subjects(&self) -> &[DistinguishedName] {
&[]
}

fn verify_client_cert(
&self,
end_entity: &Certificate,
intermediates: &[Certificate],
_now: std::time::SystemTime,
end_entity: &CertificateDer<'_>,
intermediates: &[CertificateDer<'_>],
_now: UnixTime,
) -> Result<ClientCertVerified, rustls::Error> {
let _: PeerId = verify_presented_certs(end_entity, intermediates)?;

Expand All @@ -182,7 +200,7 @@ impl ClientCertVerifier for Libp2pCertificateVerifier {
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &Certificate,
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
unreachable!("`PROTOCOL_VERSIONS` only allows TLS 1.3")
Expand All @@ -191,7 +209,7 @@ impl ClientCertVerifier for Libp2pCertificateVerifier {
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &Certificate,
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
verify_tls13_signature(cert, dss.scheme, message, dss.signature())
Expand All @@ -209,8 +227,8 @@ impl ClientCertVerifier for Libp2pCertificateVerifier {
/// Endpoints MUST abort the connection attempt if more than one certificate is received,
/// or if the certificate’s self-signature is not valid.
fn verify_presented_certs(
end_entity: &Certificate,
intermediates: &[Certificate],
end_entity: &CertificateDer<'_>,
intermediates: &[CertificateDer<'_>],
) -> Result<PeerId, rustls::Error> {
if !intermediates.is_empty() {
return Err(rustls::Error::General(
Expand All @@ -224,7 +242,7 @@ fn verify_presented_certs(
}

fn verify_tls13_signature(
cert: &Certificate,
cert: &CertificateDer<'_>,
signature_scheme: SignatureScheme,
message: &[u8],
signature: &[u8],
Expand All @@ -238,19 +256,25 @@ impl From<certificate::ParseError> for rustls::Error {
fn from(certificate::ParseError(e): certificate::ParseError) -> Self {
use webpki::Error::*;
match e {
BadDer => rustls::Error::InvalidCertificateEncoding,
e => rustls::Error::InvalidCertificateData(format!("invalid peer certificate: {e}")),
BadDer => rustls::Error::InvalidCertificate(CertificateError::BadEncoding),
e => rustls::Error::InvalidCertificate(CertificateError::Other(rustls::OtherError(
std::sync::Arc::new(e),
))),
}
}
}
impl From<certificate::VerificationError> for rustls::Error {
fn from(certificate::VerificationError(e): certificate::VerificationError) -> Self {
use webpki::Error::*;
match e {
InvalidSignatureForPublicKey => rustls::Error::InvalidCertificateSignature,
UnsupportedSignatureAlgorithm | UnsupportedSignatureAlgorithmForPublicKey =>
rustls::Error::InvalidCertificateSignatureType,
e => rustls::Error::InvalidCertificateData(format!("invalid peer certificate: {e}")),
InvalidSignatureForPublicKey =>
rustls::Error::InvalidCertificate(CertificateError::BadSignature),
// The unsupported-algorithm cases (and anything else) are forwarded as the underlying
// webpki error: `CertificateError::UnsupportedSignatureAlgorithm` is deprecated in
// rustls 0.23 and its replacement requires context we don't have here.
e => rustls::Error::InvalidCertificate(CertificateError::Other(rustls::OtherError(
std::sync::Arc::new(e),
))),
}
}
}
Loading