Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to using the pki-types crate #147

Merged
merged 1 commit into from
Sep 1, 2023
Merged
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
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", version = "0.1", 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> {
djc marked this conversation as resolved.
Show resolved Hide resolved
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> {
cpu marked this conversation as resolved.
Show resolved Hide resolved
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