Skip to content

Commit

Permalink
Switch to using the pki-types crate
Browse files Browse the repository at this point in the history
  • Loading branch information
djc committed Aug 30, 2023
1 parent 02b8423 commit ce0be38
Show file tree
Hide file tree
Showing 14 changed files with 199 additions and 171 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,11 @@ name = "webpki"
[features]
default = ["std", "ring"]
ring = ["dep:ring"]
alloc = ["ring?/alloc"]
alloc = ["ring?/alloc", "pki-types/alloc"]
std = ["alloc"]

[dependencies]
pki-types = { package = "rustls-pki-types", git = "https://github.com/rustls/pki-types.git", rev = "f65659f631caa3ae7ea283e1774adc2419b07522", default-features = false }
ring = { version = "0.16.19", default-features = false, optional = true }
untrusted = "0.7.1"

Expand Down
12 changes: 7 additions & 5 deletions src/end_entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

use pki_types::{CertificateDer, TrustAnchor};

#[cfg(feature = "alloc")]
use crate::subject_name::GeneralDnsNameRef;
use crate::{
cert, signed_data, subject_name, verify_cert, Error, KeyUsage, RevocationOptions,
SignatureVerificationAlgorithm, SubjectNameRef, Time, TrustAnchor,
SignatureVerificationAlgorithm, SubjectNameRef, Time,
};

/// An end-entity certificate.
Expand Down Expand Up @@ -54,15 +56,15 @@ pub struct EndEntityCert<'a> {
inner: cert::Cert<'a>,
}

impl<'a> TryFrom<&'a [u8]> for EndEntityCert<'a> {
impl<'a> TryFrom<&'a CertificateDer<'a>> for EndEntityCert<'a> {
type Error = Error;

/// Parse the ASN.1 DER-encoded X.509 encoding of the certificate
/// `cert_der`.
fn try_from(cert_der: &'a [u8]) -> Result<Self, Self::Error> {
fn try_from(cert: &'a CertificateDer<'a>) -> Result<Self, Self::Error> {
Ok(Self {
inner: cert::Cert::from_der(
untrusted::Input::from(cert_der),
untrusted::Input::from(cert.as_ref()),
cert::EndEntityOrCa::EndEntity,
)?,
})
Expand Down Expand Up @@ -93,7 +95,7 @@ impl<'a> EndEntityCert<'a> {
&self,
supported_sig_algs: &[&dyn SignatureVerificationAlgorithm],
trust_anchors: &[TrustAnchor],
intermediate_certs: &[&[u8]],
intermediate_certs: &[CertificateDer<'_>],
time: Time,
usage: KeyUsage,
revocation: Option<RevocationOptions>,
Expand Down
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,16 @@ pub use {
SubjectNameRef,
},
time::Time,
trust_anchor::TrustAnchor,
trust_anchor::extract_trust_anchor,
verify_cert::{
KeyUsage, RevocationCheckDepth, RevocationOptions, RevocationOptionsBuilder,
UnknownStatusPolicy,
},
};

pub use pki_types as types;

#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[cfg(feature = "alloc")]
pub use {
crl::{OwnedCertRevocationList, OwnedRevokedCert},
Expand Down
148 changes: 65 additions & 83 deletions src/trust_anchor.rs
Original file line number Diff line number Diff line change
@@ -1,103 +1,85 @@
use pki_types::{CertificateDer, TrustAnchor};

use crate::cert::{lenient_certificate_serial_number, Cert, EndEntityOrCa};
use crate::der;
use crate::error::{DerTypeId, Error};

/// A trust anchor (a.k.a. root CA).
///
/// Traditionally, certificate verification libraries have represented trust
/// anchors as full X.509 root certificates. However, those certificates
/// contain a lot more data than is needed for verifying certificates. The
/// `TrustAnchor` representation allows an application to store just the
/// essential elements of trust anchors. The `TrustAnchor::try_from_cert_der`
/// function allows converting X.509 certificates to to the minimized
/// `TrustAnchor` representation, either at runtime or in a build script.
#[derive(Debug)]
pub struct TrustAnchor<'a> {
/// The value of the `subject` field of the trust anchor.
pub subject: &'a [u8],

/// The value of the `subjectPublicKeyInfo` field of the trust anchor.
pub spki: &'a [u8],

/// The value of a DER-encoded NameConstraints, containing name
/// constraints to apply to the trust anchor, if any.
pub name_constraints: Option<&'a [u8]>,
}
/// Interprets the given DER-encoded certificate as a `TrustAnchor`. The
/// certificate is not validated. In particular, there is no check that the
/// certificate is self-signed or even that the certificate has the cA basic
/// constraint.
pub fn extract_trust_anchor<'a>(cert: &'a CertificateDer<'a>) -> Result<TrustAnchor<'a>, Error> {
let cert_der = untrusted::Input::from(cert.as_ref());

impl<'a> TrustAnchor<'a> {
/// Interprets the given DER-encoded certificate as a `TrustAnchor`. The
/// certificate is not validated. In particular, there is no check that the
/// certificate is self-signed or even that the certificate has the cA basic
/// constraint.
pub fn try_from_cert_der(cert_der: &'a [u8]) -> Result<Self, Error> {
let cert_der = untrusted::Input::from(cert_der);

// XXX: `EndEntityOrCA::EndEntity` is used instead of `EndEntityOrCA::CA`
// because we don't have a reference to a child cert, which is needed for
// `EndEntityOrCA::CA`. For this purpose, it doesn't matter.
//
// v1 certificates will result in `Error::BadDer` because `parse_cert` will
// expect a version field that isn't there. In that case, try to parse the
// certificate using a special parser for v1 certificates. Notably, that
// parser doesn't allow extensions, so there's no need to worry about
// embedded name constraints in a v1 certificate.
match Cert::from_der(cert_der, EndEntityOrCa::EndEntity) {
Ok(cert) => Ok(Self::from(cert)),
Err(Error::UnsupportedCertVersion) => {
Self::from_v1_der(cert_der).or(Err(Error::BadDer))
}
Err(err) => Err(err),
// XXX: `EndEntityOrCA::EndEntity` is used instead of `EndEntityOrCA::CA`
// because we don't have a reference to a child cert, which is needed for
// `EndEntityOrCA::CA`. For this purpose, it doesn't matter.
//
// v1 certificates will result in `Error::BadDer` because `parse_cert` will
// expect a version field that isn't there. In that case, try to parse the
// certificate using a special parser for v1 certificates. Notably, that
// parser doesn't allow extensions, so there's no need to worry about
// embedded name constraints in a v1 certificate.
match Cert::from_der(cert_der, EndEntityOrCa::EndEntity) {
Ok(cert) => Ok(TrustAnchor::from(cert)),
Err(Error::UnsupportedCertVersion) => {
extract_trust_anchor_from_v1_cert_der(cert_der).or(Err(Error::BadDer))
}
Err(err) => Err(err),

Check warning on line 28 in src/trust_anchor.rs

View check run for this annotation

Codecov / codecov/patch

src/trust_anchor.rs#L28

Added line #L28 was not covered by tests
}
}

/// Parses a v1 certificate directly into a TrustAnchor.
fn from_v1_der(cert_der: untrusted::Input<'a>) -> Result<Self, Error> {
// X.509 Certificate: https://tools.ietf.org/html/rfc5280#section-4.1.
cert_der.read_all(Error::BadDer, |cert_der| {
der::nested(
cert_der,
der::Tag::Sequence,
Error::TrailingData(DerTypeId::TrustAnchorV1),
|cert_der| {
let anchor = der::nested(
cert_der,
der::Tag::Sequence,
Error::TrailingData(DerTypeId::TrustAnchorV1TbsCertificate),
|tbs| {
// The version number field does not appear in v1 certificates.
lenient_certificate_serial_number(tbs)?;
/// Parses a v1 certificate directly into a TrustAnchor.
fn extract_trust_anchor_from_v1_cert_der(
cert_der: untrusted::Input<'_>,
) -> Result<TrustAnchor<'_>, Error> {
// X.509 Certificate: https://tools.ietf.org/html/rfc5280#section-4.1.
cert_der.read_all(Error::BadDer, |cert_der| {
der::nested(
cert_der,
der::Tag::Sequence,
Error::TrailingData(DerTypeId::TrustAnchorV1),
|cert_der| {
let anchor = der::nested(
cert_der,
der::Tag::Sequence,
Error::TrailingData(DerTypeId::TrustAnchorV1TbsCertificate),
|tbs| {
// The version number field does not appear in v1 certificates.
lenient_certificate_serial_number(tbs)?;

skip(tbs, der::Tag::Sequence)?; // signature.
skip(tbs, der::Tag::Sequence)?; // issuer.
skip(tbs, der::Tag::Sequence)?; // validity.
let subject = der::expect_tag(tbs, der::Tag::Sequence)?;
let spki = der::expect_tag(tbs, der::Tag::Sequence)?;
skip(tbs, der::Tag::Sequence)?; // signature.
skip(tbs, der::Tag::Sequence)?; // issuer.
skip(tbs, der::Tag::Sequence)?; // validity.
let subject = der::expect_tag(tbs, der::Tag::Sequence)?;
let spki = der::expect_tag(tbs, der::Tag::Sequence)?;

Ok(TrustAnchor {
subject: subject.as_slice_less_safe(),
spki: spki.as_slice_less_safe(),
name_constraints: None,
})
},
);
Ok(TrustAnchor {
subject: subject.as_slice_less_safe().into(),
subject_public_key_info: spki.as_slice_less_safe().into(),
name_constraints: None,
})
},
);

// read and discard signatureAlgorithm + signature
skip(cert_der, der::Tag::Sequence)?;
skip(cert_der, der::Tag::BitString)?;
// read and discard signatureAlgorithm + signature
skip(cert_der, der::Tag::Sequence)?;
skip(cert_der, der::Tag::BitString)?;

anchor
},
)
})
}
anchor
},
)
})
}

impl<'a> From<Cert<'a>> for TrustAnchor<'a> {
fn from(cert: Cert<'a>) -> Self {
Self {
subject: cert.subject.as_slice_less_safe(),
spki: cert.spki.as_slice_less_safe(),
name_constraints: cert.name_constraints.map(|nc| nc.as_slice_less_safe()),
subject: cert.subject.as_slice_less_safe().into(),
subject_public_key_info: cert.spki.as_slice_less_safe().into(),
name_constraints: cert
.name_constraints
.map(|nc| nc.as_slice_less_safe().into()),
}
}
}
Expand Down
35 changes: 21 additions & 14 deletions src/verify_cert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

use pki_types::{CertificateDer, TrustAnchor};

use crate::cert::{Cert, EndEntityOrCa};
use crate::crl::IssuingDistributionPoint;
use crate::der::{self, FromDer};
use crate::{
signed_data, subject_name, time, CertRevocationList, Error, SignatureVerificationAlgorithm,
TrustAnchor,
};

/// Builds a RevocationOptions instance to control how revocation checking is performed.
Expand Down Expand Up @@ -124,7 +125,7 @@ pub(crate) struct ChainOptions<'a> {
pub(crate) eku: KeyUsage,
pub(crate) supported_sig_algs: &'a [&'a dyn SignatureVerificationAlgorithm],
pub(crate) trust_anchors: &'a [TrustAnchor<'a>],
pub(crate) intermediate_certs: &'a [&'a [u8]],
pub(crate) intermediate_certs: &'a [CertificateDer<'a>],
pub(crate) revocation: Option<RevocationOptions<'a>>,
}

Expand Down Expand Up @@ -177,12 +178,15 @@ fn build_chain_inner(
Error::UnknownIssuer,
opts.trust_anchors,
|trust_anchor: &TrustAnchor| {
let trust_anchor_subject = untrusted::Input::from(trust_anchor.subject);
let trust_anchor_subject = untrusted::Input::from(trust_anchor.subject.as_ref());
if cert.issuer != trust_anchor_subject {
return Err(Error::UnknownIssuer);
}

let name_constraints = trust_anchor.name_constraints.map(untrusted::Input::from);
let name_constraints = trust_anchor
.name_constraints
.as_ref()
.map(|der| untrusted::Input::from(der.as_ref()));

untrusted::read_all_optional(name_constraints, Error::BadDer, |value| {
subject_name::check_name_constraints(value, cert, subject_common_name_contents)
Expand Down Expand Up @@ -251,8 +255,8 @@ fn check_signatures(
revocation: Option<RevocationOptions>,
signatures: &mut usize,
) -> Result<(), Error> {
let mut spki_value = untrusted::Input::from(trust_anchor.spki);
let mut issuer_subject = untrusted::Input::from(trust_anchor.subject);
let mut spki_value = untrusted::Input::from(trust_anchor.subject_public_key_info.as_ref());
let mut issuer_subject = untrusted::Input::from(trust_anchor.subject.as_ref());
let mut issuer_key_usage = None; // TODO(XXX): Consider whether to track TrustAnchor KU.
let mut cert = cert_chain;
loop {
Expand Down Expand Up @@ -790,7 +794,8 @@ mod tests {
#[test]
#[cfg(feature = "alloc")]
fn test_too_many_signatures() {
use crate::ECDSA_P256_SHA256;
use crate::types::CertificateDer;
use crate::{extract_trust_anchor, ECDSA_P256_SHA256};
use crate::{EndEntityCert, Time};

let alg = &rcgen::PKCS_ECDSA_P256_SHA256;
Expand All @@ -811,7 +816,7 @@ mod tests {
};

let ca_cert = make_issuer();
let ca_cert_der = ca_cert.serialize_der().unwrap();
let ca_cert_der = CertificateDer::from(ca_cert.serialize_der().unwrap());

let mut intermediates = Vec::with_capacity(101);
let mut issuer = ca_cert;
Expand All @@ -826,20 +831,22 @@ mod tests {
ee_params.is_ca = rcgen::IsCa::ExplicitNoCa;
ee_params.alg = alg;
let ee_cert = rcgen::Certificate::from_params(ee_params).unwrap();
let ee_cert_der = ee_cert.serialize_der_with_signer(&issuer).unwrap();
let ee_cert_der = CertificateDer::from(ee_cert.serialize_der_with_signer(&issuer).unwrap());

let anchors = &[TrustAnchor::try_from_cert_der(&ca_cert_der).unwrap()];
let anchors = &[extract_trust_anchor(&ca_cert_der).unwrap()];
let time = Time::from_seconds_since_unix_epoch(0x1fed_f00d);
let cert = EndEntityCert::try_from(&ee_cert_der[..]).unwrap();
let intermediates_der: Vec<&[u8]> = intermediates.iter().map(|x| x.as_ref()).collect();
let intermediate_certs: &[&[u8]] = intermediates_der.as_ref();
let cert = EndEntityCert::try_from(&ee_cert_der).unwrap();
let intermediates_der = intermediates
.iter()
.map(|x| CertificateDer::from(x.as_ref()))
.collect::<Vec<_>>();

let result = build_chain(
&ChainOptions {
eku: KeyUsage::server_auth(),
supported_sig_algs: &[ECDSA_P256_SHA256],
trust_anchors: anchors,
intermediate_certs,
intermediate_certs: &intermediates_der,
revocation: None,
},
cert.inner(),
Expand Down
16 changes: 9 additions & 7 deletions tests/better_tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ use base64::{engine::general_purpose, Engine as _};
use bzip2::read::BzDecoder;
use serde::Deserialize;

use webpki::{KeyUsage, SubjectNameRef, TrustAnchor};
use webpki::types::{CertificateDer, TrustAnchor};
use webpki::{extract_trust_anchor, KeyUsage, SubjectNameRef};

#[test]
fn better_tls() {
let better_tls = testdata();
let root_der = &better_tls.root_der();
let roots = &[TrustAnchor::try_from_cert_der(root_der).expect("invalid trust anchor")];
let root_der = CertificateDer::from(root_der.as_slice());
let roots = &[extract_trust_anchor(&root_der).expect("invalid trust anchor")];

let suite = "pathbuilding";
run_testsuite(
Expand All @@ -30,7 +32,8 @@ fn better_tls() {
fn name_constraints() {
let better_tls = testdata();
let root_der = &better_tls.root_der();
let roots = &[TrustAnchor::try_from_cert_der(root_der).expect("invalid trust anchor")];
let root_der = CertificateDer::from(root_der.as_slice());
let roots = &[extract_trust_anchor(&root_der).expect("invalid trust anchor")];

let suite = "nameconstraints";
run_testsuite(
Expand All @@ -48,14 +51,13 @@ fn run_testsuite(suite_name: &str, suite: &BetterTlsSuite, roots: &[TrustAnchor]
println!("Testing {suite_name} test case {}", testcase.id);

let certs_der = testcase.certs_der();
let ee_der = &certs_der[0];
let ee_der = CertificateDer::from(certs_der[0].as_slice());
let intermediates = &certs_der[1..]
.iter()
.map(|cert| cert.as_slice())
.map(|cert| CertificateDer::from(cert.as_slice()))
.collect::<Vec<_>>();

let ee_cert =
webpki::EndEntityCert::try_from(ee_der.as_slice()).expect("invalid end entity cert");
let ee_cert = webpki::EndEntityCert::try_from(&ee_der).expect("invalid end entity cert");

// Set the time to the time of test case generation. This ensures that the test case
// certificates won't expire.
Expand Down
Loading

0 comments on commit ce0be38

Please sign in to comment.