Skip to content
Closed
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
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions transports/tls/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## unreleased

- Return the context-bearing `webpki::Error::UnsupportedSignatureAlgorithmContext`
and `UnsupportedSignatureAlgorithmForPublicKeyContext` variants from
certificate verification, so failures surface the offending signature
and public-key algorithm OIDs instead of a bare enum. Bumps the minimum
`rustls-webpki` to `v0.103.9` for the new variants.

## 0.7.0

- Raise MSRV to 1.88.0.
Expand Down
2 changes: 1 addition & 1 deletion transports/tls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ libp2p-identity = { workspace = true }
rcgen = { workspace = true }
ring = { workspace = true }
thiserror = { workspace = true }
webpki = { version = "0.103", package = "rustls-webpki", features = ["std"] }
webpki = { version = "0.103.9", package = "rustls-webpki", features = ["std"] }
x509-parser = "0.17.0"
yasna = "0.5.2"

Expand Down
119 changes: 108 additions & 11 deletions transports/tls/src/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use std::sync::Arc;

use libp2p_identity as identity;
use libp2p_identity::PeerId;
use x509_parser::{prelude::*, signature_algorithm::SignatureAlgorithm};
use x509_parser::{asn1_rs::ToDer, prelude::*, signature_algorithm::SignatureAlgorithm};

/// The libp2p Public Key Extension is a X.509 extension
/// with the Object Identifier 1.3.6.1.4.1.53594.1.1,
Expand All @@ -44,6 +44,56 @@ const P2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:";
// Similarly, hash functions with an output length less than 256 bits MUST NOT be used.
static P2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_P256_SHA256;

const SUPPORTED_SIGNATURE_ALGORITHMS: &[rustls::pki_types::AlgorithmIdentifier] = &[
rustls::pki_types::alg_id::RSA_PKCS1_SHA256,
rustls::pki_types::alg_id::RSA_PKCS1_SHA384,
rustls::pki_types::alg_id::RSA_PKCS1_SHA512,
rustls::pki_types::alg_id::ECDSA_SHA256,
rustls::pki_types::alg_id::ECDSA_SHA384,
rustls::pki_types::alg_id::RSA_PSS_SHA256,
rustls::pki_types::alg_id::RSA_PSS_SHA384,
rustls::pki_types::alg_id::RSA_PSS_SHA512,
rustls::pki_types::alg_id::ED25519,
];

fn algorithm_identifier_value(
algorithm: &x509_parser::x509::AlgorithmIdentifier<'_>,
) -> Result<Vec<u8>, webpki::Error> {
let mut value = algorithm
.algorithm
.to_der_vec()
.map_err(|_| webpki::Error::BadDer)?;
if let Some(parameters) = &algorithm.parameters {
value.extend(parameters.to_der_vec().map_err(|_| webpki::Error::BadDer)?);
}
Ok(value)
}

fn unsupported_signature_algorithm(
signature_algorithm: &x509_parser::x509::AlgorithmIdentifier<'_>,
) -> Result<webpki::Error, webpki::Error> {
Ok(webpki::Error::UnsupportedSignatureAlgorithmContext(
webpki::UnsupportedSignatureAlgorithmContext {
signature_algorithm_id: algorithm_identifier_value(signature_algorithm)?,
supported_algorithms: SUPPORTED_SIGNATURE_ALGORITHMS.to_vec(),
},
))
}

fn unsupported_signature_algorithm_for_public_key(
signature_algorithm: &x509_parser::x509::AlgorithmIdentifier<'_>,
public_key_algorithm: &x509_parser::x509::AlgorithmIdentifier<'_>,
) -> Result<webpki::Error, webpki::Error> {
Ok(
webpki::Error::UnsupportedSignatureAlgorithmForPublicKeyContext(
webpki::UnsupportedSignatureAlgorithmForPublicKeyContext {
signature_algorithm_id: algorithm_identifier_value(signature_algorithm)?,
public_key_algorithm_id: algorithm_identifier_value(public_key_algorithm)?,
},
),
)
}

#[derive(Debug)]
pub(crate) struct AlwaysResolvesCert(Arc<rustls::sign::CertifiedKey>);

Expand Down Expand Up @@ -305,9 +355,14 @@ impl P2pCertificate<'_> {
use rustls::SignatureScheme::*;

let current_signature_scheme = self.signature_scheme()?;
let signature_algorithm = &self.certificate.signature_algorithm;
let pki_algorithm = &self.certificate.tbs_certificate.subject_pki.algorithm;
if signature_scheme != current_signature_scheme {
// This certificate was signed with a different signature scheme
return Err(webpki::Error::UnsupportedSignatureAlgorithmForPublicKey);
return Err(unsupported_signature_algorithm_for_public_key(
signature_algorithm,
pki_algorithm,
)?);
}

let verification_algorithm: &dyn signature::VerificationAlgorithm = match signature_scheme {
Expand All @@ -318,22 +373,24 @@ impl P2pCertificate<'_> {
ECDSA_NISTP384_SHA384 => &signature::ECDSA_P384_SHA384_ASN1,
ECDSA_NISTP521_SHA512 => {
// See https://github.com/briansmith/ring/issues/824
return Err(webpki::Error::UnsupportedSignatureAlgorithm);
return Err(unsupported_signature_algorithm(signature_algorithm)?);
}
RSA_PSS_SHA256 => &signature::RSA_PSS_2048_8192_SHA256,
RSA_PSS_SHA384 => &signature::RSA_PSS_2048_8192_SHA384,
RSA_PSS_SHA512 => &signature::RSA_PSS_2048_8192_SHA512,
ED25519 => &signature::ED25519,
ED448 => {
// See https://github.com/briansmith/ring/issues/463
return Err(webpki::Error::UnsupportedSignatureAlgorithm);
return Err(unsupported_signature_algorithm(signature_algorithm)?);
}
// Similarly, hash functions with an output length less than 256 bits
// MUST NOT be used, due to the possibility of collision attacks.
// In particular, MD5 and SHA1 MUST NOT be used.
RSA_PKCS1_SHA1 => return Err(webpki::Error::UnsupportedSignatureAlgorithm),
ECDSA_SHA1_Legacy => return Err(webpki::Error::UnsupportedSignatureAlgorithm),
_ => return Err(webpki::Error::UnsupportedSignatureAlgorithm),
RSA_PKCS1_SHA1 => return Err(unsupported_signature_algorithm(signature_algorithm)?),
ECDSA_SHA1_Legacy => {
return Err(unsupported_signature_algorithm(signature_algorithm)?);
}
_ => return Err(unsupported_signature_algorithm(signature_algorithm)?),
};
let spki = &self.certificate.tbs_certificate.subject_pki;
let key = signature::UnparsedPublicKey::new(
Expand Down Expand Up @@ -446,7 +503,7 @@ impl P2pCertificate<'_> {

// Default hash algo is SHA-1, however:
// In particular, MD5 and SHA1 MUST NOT be used.
return Err(webpki::Error::UnsupportedSignatureAlgorithm);
return Err(unsupported_signature_algorithm(signature_algorithm)?);
}
}

Expand All @@ -472,7 +529,7 @@ impl P2pCertificate<'_> {
{
return Ok(ECDSA_NISTP521_SHA512);
}
return Err(webpki::Error::UnsupportedSignatureAlgorithm);
return Err(unsupported_signature_algorithm(signature_algorithm)?);
}

if signature_algorithm.algorithm == OID_SIG_ED25519 {
Expand All @@ -482,7 +539,7 @@ impl P2pCertificate<'_> {
return Ok(ED448);
}

Err(webpki::Error::UnsupportedSignatureAlgorithm)
Err(unsupported_signature_algorithm(signature_algorithm)?)
}
}

Expand Down Expand Up @@ -547,7 +604,47 @@ mod tests {

let cert = parse_unverified(cert).unwrap();

assert!(cert.signature_scheme().is_err());
match cert.signature_scheme() {
Err(webpki::Error::UnsupportedSignatureAlgorithmContext(ctx)) => {
assert!(
!ctx.signature_algorithm_id.is_empty(),
"signature_algorithm_id must carry the offending OID bytes",
);
assert!(
!ctx.supported_algorithms.is_empty(),
"supported_algorithms must list libp2p's accepted set",
);
}
other => panic!("expected UnsupportedSignatureAlgorithmContext, got {other:?}"),
}
}

#[test]
fn signature_scheme_mismatch_carries_both_oids() {
// Ed25519 cert verified against an RSA-PSS scheme triggers the
// public-key-vs-signature mismatch branch in `verify_signature`.
let cert: &[u8] = include_bytes!("./test_assets/ed25519.der");

let cert = parse_unverified(cert).unwrap();
let err = cert
.verify_signature(rustls::SignatureScheme::RSA_PSS_SHA256, &[], &[])
.unwrap_err();

match err.0 {
webpki::Error::UnsupportedSignatureAlgorithmForPublicKeyContext(ctx) => {
assert!(
!ctx.signature_algorithm_id.is_empty(),
"signature_algorithm_id must carry the offending OID bytes",
);
assert!(
!ctx.public_key_algorithm_id.is_empty(),
"public_key_algorithm_id must carry the certificate's SPKI OID bytes",
);
}
other => {
panic!("expected UnsupportedSignatureAlgorithmForPublicKeyContext, got {other:?}")
}
}
}

#[test]
Expand Down
Loading