Skip to content

Commit

Permalink
Migrate to pki-types ServerName
Browse files Browse the repository at this point in the history
  • Loading branch information
djc committed Nov 21, 2023
1 parent 24facb0 commit 735061e
Show file tree
Hide file tree
Showing 13 changed files with 88 additions and 1,648 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ default = ["std", "ring"]
alloc = ["ring?/alloc", "pki-types/alloc"]
aws_lc_rs = ["dep:aws-lc-rs"]
ring = ["dep:ring"]
std = ["alloc"]
std = ["alloc", "pki-types/std"]

[dependencies]
aws-lc-rs = { version = "1.0.0", optional = true }
pki-types = { package = "rustls-pki-types", version = "0.2.1", default-features = false }
pki-types = { package = "rustls-pki-types", version = "0.2.2", git = "https://github.com/rustls/pki-types", rev = "4e64f851484b9430d071b38bceeaa0cbe2141821", default-features = false }
ring = { version = "0.17", default-features = false, optional = true }
untrusted = "0.9"

Expand Down
9 changes: 5 additions & 4 deletions src/cert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
// 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;
use pki_types::{CertificateDer, DnsName};

use crate::der::{self, DerIterator, FromDer, Tag, CONSTRUCTED, CONTEXT_SPECIFIC};
use crate::error::{DerTypeId, Error};
use crate::public_values_eq;
use crate::signed_data::SignedData;
use crate::subject_name::{GeneralName, NameIterator, WildcardDnsNameRef};
use crate::x509::{remember_extension, set_extension_once, DistributionPointName, Extension};
use crate::{public_values_eq, DnsNameRef};

/// A parsed X509 certificate.
pub struct Cert<'a> {
Expand Down Expand Up @@ -145,8 +145,9 @@ impl<'a> Cert<'a> {

// if the name could be converted to a DNS name, return it; otherwise,
// keep going.
match DnsNameRef::try_from_ascii(presented_id.as_slice_less_safe()) {
Ok(dns_name) => Some(dns_name.as_str()),
let dns_str = core::str::from_utf8(presented_id.as_slice_less_safe()).ok()?;
match DnsName::try_from(dns_str) {
Ok(_) => Some(dns_str),
Err(_) => {
match WildcardDnsNameRef::try_from_ascii(presented_id.as_slice_less_safe()) {
Ok(wildcard_dns_name) => Some(wildcard_dns_name.as_str()),
Expand Down
26 changes: 16 additions & 10 deletions src/end_entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@

use core::ops::Deref;

use pki_types::{CertificateDer, SignatureVerificationAlgorithm, TrustAnchor, UnixTime};
use pki_types::{
CertificateDer, ServerName, SignatureVerificationAlgorithm, TrustAnchor, UnixTime,
};

use crate::crl::RevocationOptions;
use crate::error::Error;
use crate::subject_name::{NameIterator, SubjectNameRef};
use crate::subject_name::{verify_dns_names, verify_ip_address_names, NameIterator};
use crate::verify_cert::{self, KeyUsage, VerifiedPath};
use crate::{cert, signed_data};

Expand Down Expand Up @@ -119,17 +121,21 @@ impl<'a> EndEntityCert<'a> {
/// Verifies that the certificate is valid for the given Subject Name.
pub fn verify_is_valid_for_subject_name(
&self,
subject_name: SubjectNameRef,
server_name: &ServerName<'_>,
) -> Result<(), Error> {
match subject_name {
SubjectNameRef::DnsName(dns_name) => dns_name.verify_dns_names(NameIterator::new(
Some(self.inner.subject),
self.inner.subject_alt_name,
)),
match server_name {
ServerName::DnsName(dns_name) => verify_dns_names(
dns_name,
NameIterator::new(Some(self.inner.subject), self.inner.subject_alt_name),
),
// IP addresses are not compared against the subject field;
// only against Subject Alternative Names.
SubjectNameRef::IpAddress(ip_address) => ip_address
.verify_ip_address_names(NameIterator::new(None, self.inner.subject_alt_name)),
#[cfg(feature = "std")]
ServerName::IpAddress(ip_address) => verify_ip_address_names(
ip_address,
NameIterator::new(None, self.inner.subject_alt_name),
),
_ => unreachable!(),

Check warning on line 138 in src/end_entity.rs

View check run for this annotation

Codecov / codecov/patch

src/end_entity.rs#L138

Added line #L138 was not covered by tests
}
}

Expand Down
9 changes: 1 addition & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,14 @@ pub use {
end_entity::EndEntityCert,
error::{DerTypeId, Error},
signed_data::alg_id,
subject_name::{
AddrParseError, DnsNameRef, InvalidDnsNameError, InvalidSubjectNameError, IpAddrRef,
SubjectNameRef,
},
trust_anchor::anchor_from_trusted_cert,
verify_cert::KeyUsage,
};

pub use pki_types as types;

#[cfg(feature = "alloc")]
pub use {
crl::{OwnedCertRevocationList, OwnedRevokedCert},
subject_name::{DnsName, IpAddr},
};
pub use crl::{OwnedCertRevocationList, OwnedRevokedCert};

#[cfg(feature = "ring")]
/// Signature verification algorithm implementations using the *ring* crypto library.
Expand Down
155 changes: 24 additions & 131 deletions src/subject_name/dns_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,130 +12,37 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

#[cfg(feature = "alloc")]
use alloc::string::String;
use core::fmt::Write;

use pki_types::{DnsName, InvalidDnsNameError};

use super::verify::{GeneralName, NameIterator};
use crate::Error;

/// A DNS Name suitable for use in the TLS Server Name Indication (SNI)
/// extension and/or for use as the reference hostname for which to verify a
/// certificate.
///
/// A `DnsName` is guaranteed to be syntactically valid. The validity rules are
/// specified in [RFC 5280 Section 7.2], except that underscores are also
/// allowed. `DnsName`s do not include wildcard labels.
///
/// `DnsName` stores a copy of the input it was constructed from in a `String`
/// and so it is only available when the `alloc` default feature is enabled.
///
/// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2
///
/// Requires the `alloc` feature.
#[cfg(feature = "alloc")]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct DnsName(String);

#[cfg(feature = "alloc")]
impl DnsName {
/// Returns a `DnsNameRef` that refers to this `DnsName`.
pub fn as_ref(&self) -> DnsNameRef {
DnsNameRef(self.0.as_bytes())
}
}

#[cfg(feature = "alloc")]
impl AsRef<str> for DnsName {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}

/// A reference to a DNS Name suitable for use in the TLS Server Name Indication
/// (SNI) extension and/or for use as the reference hostname for which to verify
/// a certificate.
///
/// A `DnsNameRef` is guaranteed to be syntactically valid. The validity rules
/// are specified in [RFC 5280 Section 7.2], except that underscores are also
/// allowed. `DnsNameRef`s do not include wildcard labels.
///
/// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
pub struct DnsNameRef<'a>(pub(crate) &'a [u8]);

impl<'a> DnsNameRef<'a> {
/// Constructs a `DnsNameRef` from the given input if the input is a
/// syntactically-valid DNS name.
pub fn try_from_ascii(dns_name: &'a [u8]) -> Result<Self, InvalidDnsNameError> {
if !is_valid_dns_id(
untrusted::Input::from(dns_name),
IdRole::Reference,
Wildcards::Deny,
) {
return Err(InvalidDnsNameError);
}

Ok(Self(dns_name))
}

/// Constructs a `DnsNameRef` from the given input if the input is a
/// syntactically-valid DNS name.
pub fn try_from_ascii_str(dns_name: &'a str) -> Result<Self, InvalidDnsNameError> {
Self::try_from_ascii(dns_name.as_bytes())
}

pub(crate) fn verify_dns_names(&self, mut names: NameIterator<'_>) -> Result<(), Error> {
let dns_name = untrusted::Input::from(self.as_str().as_bytes());
names
.find_map(|result| {
let name = match result {
Ok(name) => name,
Err(err) => return Some(Err(err)),
};

let presented_id = match name {
GeneralName::DnsName(presented) => presented,
_ => return None,
};

match presented_id_matches_reference_id(presented_id, IdRole::Reference, dns_name) {
Ok(true) => Some(Ok(())),
Ok(false) | Err(Error::MalformedDnsIdentifier) => None,
Err(e) => Some(Err(e)),
}
})
.unwrap_or(Err(Error::CertNotValidForName))
}

/// Constructs a `DnsName` from this `DnsNameRef`
#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> DnsName {
// DnsNameRef is already guaranteed to be valid ASCII, which is subset of UTF-8.
DnsName(self.as_str().to_ascii_lowercase())
}

/// Yields a reference to the DNS name as a `&str`.
pub fn as_str(&self) -> &'a str {
// The unwrap won't fail because `DnsNameRef` values are guaranteed to be ASCII and ASCII
// is a subset of UTF-8.
core::str::from_utf8(self.0).unwrap()
}
}

impl core::fmt::Debug for DnsNameRef<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
f.write_str("DnsNameRef(\"")?;
pub(crate) fn verify_dns_names(
reference: &DnsName<'_>,
mut names: NameIterator<'_>,
) -> Result<(), Error> {
let dns_name = untrusted::Input::from(reference.as_ref().as_bytes());
names
.find_map(|result| {
let name = match result {
Ok(name) => name,
Err(err) => return Some(Err(err)),

Check warning on line 31 in src/subject_name/dns_name.rs

View check run for this annotation

Codecov / codecov/patch

src/subject_name/dns_name.rs#L31

Added line #L31 was not covered by tests
};

// Convert each byte of the underlying ASCII string to a `char` and
// downcase it prior to formatting it. We avoid self.clone().to_owned()
// since it requires allocation.
for &ch in self.0 {
f.write_char(char::from(ch).to_ascii_lowercase())?;
}
let presented_id = match name {
GeneralName::DnsName(presented) => presented,
_ => return None,
};

f.write_str("\")")
}
match presented_id_matches_reference_id(presented_id, IdRole::Reference, dns_name) {
Ok(true) => Some(Ok(())),
Ok(false) | Err(Error::MalformedDnsIdentifier) => None,
Err(e) => Some(Err(e)),

Check warning on line 42 in src/subject_name/dns_name.rs

View check run for this annotation

Codecov / codecov/patch

src/subject_name/dns_name.rs#L42

Added line #L42 was not covered by tests
}
})
.unwrap_or(Err(Error::CertNotValidForName))
}

/// A reference to a DNS Name presented by a server that may include a wildcard.
Expand Down Expand Up @@ -191,20 +98,6 @@ impl core::fmt::Debug for WildcardDnsNameRef<'_> {
}
}

/// An error indicating that a `DnsNameRef` could not built because the input
/// is not a syntactically-valid DNS Name.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct InvalidDnsNameError;

impl core::fmt::Display for InvalidDnsNameError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{:?}", self)
}
}

#[cfg(feature = "std")]
impl ::std::error::Error for InvalidDnsNameError {}

// We assume that both presented_dns_id and reference_dns_id are encoded in
// such a way that US-ASCII (7-bit) characters are encoded in one byte and no
// encoding of a non-US-ASCII character contains a code point in the range
Expand Down
Loading

0 comments on commit 735061e

Please sign in to comment.