webpki/src/trust_anchor.rs

120 lines
5.0 KiB
Rust
Raw Normal View History

use crate::cert::{certificate_serial_number, Cert};
use crate::{
cert::{parse_cert_internal, EndEntityOrCa},
der, 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 `webpki::trust_anchor_util` module
/// provides functions for 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]>,
}
/// Trust anchors which may be used for authenticating servers.
#[derive(Debug)]
Revert main branch crate contents to the 0.22.0 release contents. Reset the crate contents (sources, tests, etc.) to what they were at that commit, while retaining the newer CI configuration. The changes since the 0.22.0 release were primarily intended to accomplish two goals: * Fix and improve the GitHub Actions configuration. * Prepare a 0.21.5 release that was backward compatible with 0.21.4 but which also contained the improvements that were in 0.22.0. 0.21.5 was never released and will not be released. Therefore all of the noise to facilitate the 0.21.5 release can just be deleted, as long as we leave the CI changes that are necessary for GitHub Actions to work correctly now. The exact commands I used were: ``` git checkout \ 6c334a2cf5853fb0aa93b5eb0318c031fc2f6f98 \ -- \ Cargo.toml \ LICENSE \ README.md \ src \ tests \ third-party git rm src/trust_anchor_util.rs ``` Commit 6c334a2cf5853fb0aa93b5eb0318c031fc2f6f98 was the commit from which 0.22.0 was released. It is confusing because the commit immediately prior, 0b7cbf2d327d7665d9d06072bf46b2e7ca05f065, has commit message "0.22.0". It appears that I merged the "0.22.0" commit, expecting to `cargo publish` from that commit, but then `cargo publish` failed. Then I added 6c334a2cf5853fb0aa93b5eb0318c031fc2f6f98 to fix `cargo publish` and did the `cargo publish` from that commit. That's why I added the `package` CI step at that time, to prevent this confusing situation from happening again. `trust_anchor_utils.rs` was not in 0.22.0; the `git checkout` didn't delete it, so I had to do it separately. I left the tests added subsequent to 0.22.0 in `tests/` (e.g. `name_tests.rs`) since those tests pass with the 0.22.0 sources too. Unfortunately, this requires disabling a bunch of Clippy lints, to avoid modifying the contents from 0.22.0. (I know it is confusing. It took me a while to figure it out myself today.)
2023-08-30 01:13:07 +00:00
pub struct TlsServerTrustAnchors<'a>(pub &'a [TrustAnchor<'a>]);
/// Trust anchors which may be used for authenticating clients.
#[derive(Debug)]
Revert main branch crate contents to the 0.22.0 release contents. Reset the crate contents (sources, tests, etc.) to what they were at that commit, while retaining the newer CI configuration. The changes since the 0.22.0 release were primarily intended to accomplish two goals: * Fix and improve the GitHub Actions configuration. * Prepare a 0.21.5 release that was backward compatible with 0.21.4 but which also contained the improvements that were in 0.22.0. 0.21.5 was never released and will not be released. Therefore all of the noise to facilitate the 0.21.5 release can just be deleted, as long as we leave the CI changes that are necessary for GitHub Actions to work correctly now. The exact commands I used were: ``` git checkout \ 6c334a2cf5853fb0aa93b5eb0318c031fc2f6f98 \ -- \ Cargo.toml \ LICENSE \ README.md \ src \ tests \ third-party git rm src/trust_anchor_util.rs ``` Commit 6c334a2cf5853fb0aa93b5eb0318c031fc2f6f98 was the commit from which 0.22.0 was released. It is confusing because the commit immediately prior, 0b7cbf2d327d7665d9d06072bf46b2e7ca05f065, has commit message "0.22.0". It appears that I merged the "0.22.0" commit, expecting to `cargo publish` from that commit, but then `cargo publish` failed. Then I added 6c334a2cf5853fb0aa93b5eb0318c031fc2f6f98 to fix `cargo publish` and did the `cargo publish` from that commit. That's why I added the `package` CI step at that time, to prevent this confusing situation from happening again. `trust_anchor_utils.rs` was not in 0.22.0; the `git checkout` didn't delete it, so I had to do it separately. I left the tests added subsequent to 0.22.0 in `tests/` (e.g. `name_tests.rs`) since those tests pass with the 0.22.0 sources too. Unfortunately, this requires disabling a bunch of Clippy lints, to avoid modifying the contents from 0.22.0. (I know it is confusing. It took me a while to figure it out myself today.)
2023-08-30 01:13:07 +00:00
pub struct TlsClientTrustAnchors<'a>(pub &'a [TrustAnchor<'a>]);
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 parse_cert_internal(
cert_der,
EndEntityOrCa::EndEntity,
possibly_invalid_certificate_serial_number,
) {
Ok(cert) => Ok(Self::from(cert)),
Revert main branch crate contents to the 0.22.0 release contents. Reset the crate contents (sources, tests, etc.) to what they were at that commit, while retaining the newer CI configuration. The changes since the 0.22.0 release were primarily intended to accomplish two goals: * Fix and improve the GitHub Actions configuration. * Prepare a 0.21.5 release that was backward compatible with 0.21.4 but which also contained the improvements that were in 0.22.0. 0.21.5 was never released and will not be released. Therefore all of the noise to facilitate the 0.21.5 release can just be deleted, as long as we leave the CI changes that are necessary for GitHub Actions to work correctly now. The exact commands I used were: ``` git checkout \ 6c334a2cf5853fb0aa93b5eb0318c031fc2f6f98 \ -- \ Cargo.toml \ LICENSE \ README.md \ src \ tests \ third-party git rm src/trust_anchor_util.rs ``` Commit 6c334a2cf5853fb0aa93b5eb0318c031fc2f6f98 was the commit from which 0.22.0 was released. It is confusing because the commit immediately prior, 0b7cbf2d327d7665d9d06072bf46b2e7ca05f065, has commit message "0.22.0". It appears that I merged the "0.22.0" commit, expecting to `cargo publish` from that commit, but then `cargo publish` failed. Then I added 6c334a2cf5853fb0aa93b5eb0318c031fc2f6f98 to fix `cargo publish` and did the `cargo publish` from that commit. That's why I added the `package` CI step at that time, to prevent this confusing situation from happening again. `trust_anchor_utils.rs` was not in 0.22.0; the `git checkout` didn't delete it, so I had to do it separately. I left the tests added subsequent to 0.22.0 in `tests/` (e.g. `name_tests.rs`) since those tests pass with the 0.22.0 sources too. Unfortunately, this requires disabling a bunch of Clippy lints, to avoid modifying the contents from 0.22.0. (I know it is confusing. It took me a while to figure it out myself today.)
2023-08-30 01:13:07 +00:00
Err(Error::UnsupportedCertVersion) => parse_cert_v1(cert_der).or(Err(Error::BadDer)),
Err(err) => Err(err),
}
}
}
fn possibly_invalid_certificate_serial_number(input: &mut untrusted::Reader) -> Result<(), Error> {
// https://tools.ietf.org/html/rfc5280#section-4.1.2.2:
// * Conforming CAs MUST NOT use serialNumber values longer than 20 octets."
// * "The serial number MUST be a positive integer [...]"
//
// However, we don't enforce these constraints on trust anchors, as there
// are widely-deployed trust anchors that violate these constraints.
skip(input, der::Tag::Integer)
}
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.value().as_slice_less_safe(),
name_constraints: cert.name_constraints.map(|nc| nc.as_slice_less_safe()),
}
}
}
/// Parses a v1 certificate directly into a TrustAnchor.
fn parse_cert_v1(cert_der: untrusted::Input) -> Result<TrustAnchor, Error> {
// X.509 Certificate: https://tools.ietf.org/html/rfc5280#section-4.1.
Revert main branch crate contents to the 0.22.0 release contents. Reset the crate contents (sources, tests, etc.) to what they were at that commit, while retaining the newer CI configuration. The changes since the 0.22.0 release were primarily intended to accomplish two goals: * Fix and improve the GitHub Actions configuration. * Prepare a 0.21.5 release that was backward compatible with 0.21.4 but which also contained the improvements that were in 0.22.0. 0.21.5 was never released and will not be released. Therefore all of the noise to facilitate the 0.21.5 release can just be deleted, as long as we leave the CI changes that are necessary for GitHub Actions to work correctly now. The exact commands I used were: ``` git checkout \ 6c334a2cf5853fb0aa93b5eb0318c031fc2f6f98 \ -- \ Cargo.toml \ LICENSE \ README.md \ src \ tests \ third-party git rm src/trust_anchor_util.rs ``` Commit 6c334a2cf5853fb0aa93b5eb0318c031fc2f6f98 was the commit from which 0.22.0 was released. It is confusing because the commit immediately prior, 0b7cbf2d327d7665d9d06072bf46b2e7ca05f065, has commit message "0.22.0". It appears that I merged the "0.22.0" commit, expecting to `cargo publish` from that commit, but then `cargo publish` failed. Then I added 6c334a2cf5853fb0aa93b5eb0318c031fc2f6f98 to fix `cargo publish` and did the `cargo publish` from that commit. That's why I added the `package` CI step at that time, to prevent this confusing situation from happening again. `trust_anchor_utils.rs` was not in 0.22.0; the `git checkout` didn't delete it, so I had to do it separately. I left the tests added subsequent to 0.22.0 in `tests/` (e.g. `name_tests.rs`) since those tests pass with the 0.22.0 sources too. Unfortunately, this requires disabling a bunch of Clippy lints, to avoid modifying the contents from 0.22.0. (I know it is confusing. It took me a while to figure it out myself today.)
2023-08-30 01:13:07 +00:00
cert_der.read_all(Error::BadDer, |cert_der| {
der::nested(cert_der, der::Tag::Sequence, Error::BadDer, |cert_der| {
let anchor = der::nested(cert_der, der::Tag::Sequence, Error::BadDer, |tbs| {
// The version number field does not appear in v1 certificates.
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_and_get_value(tbs, der::Tag::Sequence)?;
let spki = der::expect_tag_and_get_value(tbs, der::Tag::Sequence)?;
Ok(TrustAnchor {
subject: subject.as_slice_less_safe(),
spki: spki.as_slice_less_safe(),
name_constraints: None,
})
});
// read and discard signatureAlgorithm + signature
skip(cert_der, der::Tag::Sequence)?;
skip(cert_der, der::Tag::BitString)?;
anchor
})
})
}
fn skip(input: &mut untrusted::Reader, tag: der::Tag) -> Result<(), Error> {
der::expect_tag_and_get_value(input, tag).map(|_| ())
}