Skip to content

Commit

Permalink
x509-cert: make Name a new type over RdnSequence
Browse files Browse the repository at this point in the history
This make `FromStr` documented way to build a name.
This also exposes a set of getter methods to get elements from the name
(CN, Org, ...).
  • Loading branch information
baloo committed Sep 5, 2024
1 parent 6877a3e commit 3a8da71
Show file tree
Hide file tree
Showing 8 changed files with 501 additions and 412 deletions.
12 changes: 5 additions & 7 deletions x509-cert/src/builder/profile/cabf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub fn check_names_encoding(name: &Name, multiple_allowed: bool) -> Result<()> {

let mut seen = HashSet::new();

for rdn in name.0.iter() {
for rdn in name.iter_rdn() {
if rdn.0.len() != 1 {
return Err(Error::NonUniqueRdn);
}
Expand Down Expand Up @@ -87,13 +87,11 @@ pub fn ca_certificate_naming(subject: &Name) -> Result<()> {

check_names_encoding(subject, false)?;

for rdn in subject.0.iter() {
for atv in rdn.0.iter() {
if !allowed.remove(&atv.oid) {
return Err(Error::InvalidAttribute { oid: atv.oid });
}
required.remove(&atv.oid);
for atv in subject.iter() {
if !allowed.remove(&atv.oid) {
return Err(Error::InvalidAttribute { oid: atv.oid });
}
required.remove(&atv.oid);
}

if !required.is_empty() {
Expand Down
8 changes: 4 additions & 4 deletions x509-cert/src/builder/profile/cabf/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::{
},
AsExtension, Extension,
},
name::{Name, RelativeDistinguishedName},
name::{Name, RdnSequence, RelativeDistinguishedName},
};
use spki::SubjectPublicKeyInfoRef;

Expand Down Expand Up @@ -145,8 +145,7 @@ impl CertificateType {
// TODO(baloo): not very happy with all that, might as well throw that in a helper
// or something.
let rdns: vec::Vec<RelativeDistinguishedName> = subject
.0
.iter()
.iter_rdn()
.filter_map(|rdn| {
let out = SetOfVec::<AttributeTypeAndValue>::from_iter(
rdn.0
Expand All @@ -161,7 +160,8 @@ impl CertificateType {
.filter(|rdn| !rdn.0.is_empty())
.collect();

let subject: Name = rdns.into();
let subject: RdnSequence = rdns.into();
let subject: Name = subject.into();

Ok(Self::DomainValidated(DomainValidated { subject, names }))
}
Expand Down
168 changes: 166 additions & 2 deletions x509-cert/src/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,181 @@

use crate::attr::AttributeTypeAndValue;
use alloc::vec::Vec;
use const_oid::{
db::{rfc3280, rfc4519},
ObjectIdentifier,
};
use core::{fmt, str::FromStr};
use der::{asn1::SetOfVec, Encode};
use der::{
asn1::{Any, PrintableStringRef, SetOfVec},
Encode,
};

/// X.501 Name as defined in [RFC 5280 Section 4.1.2.4]. X.501 Name is used to represent distinguished names.
///
/// ```text
/// Name ::= CHOICE { rdnSequence RDNSequence }
/// ```
///
/// # Example
///
/// ```
/// use std::str::FromStr;
/// use x509_cert::name::Name;
///
/// let subject = Name::from_str("CN=example.com").expect("correctly formatted subject");
/// ```
///
/// [RFC 5280 Section 4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4
pub type Name = RdnSequence;
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Name(RdnSequence);

impl_newtype!(Name, RdnSequence);

impl Name {
/// Is this [`Name`] empty?
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

/// Returns the number of [`RelativeDistinguishedName`]
pub fn len(&self) -> usize {
self.0 .0.len()
}

/// Iterator over the inner [`AttributeTypeAndValue`]
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &'_ AttributeTypeAndValue> + '_ {
self.0 .0.iter().flat_map(move |rdn| rdn.0.as_slice())
}

/// Iterator over the inner [`RelativeDistinguishedName`]
#[inline]
pub fn iter_rdn(&self) -> impl Iterator<Item = &'_ RelativeDistinguishedName> + '_ {
self.0 .0.iter()
}
}

impl Name {
/// Returns the element found in the name identified by `oid`
///
/// This will return a `Ok(None)` if no such element is present
///
/// # Errors
///
/// This will return a [`der::Error`] if the content is not serialized as expected.
pub fn by_oid<'a, T>(&'a self, oid: ObjectIdentifier) -> der::Result<Option<T>>
where
T: TryFrom<&'a Any, Error = der::Error>,
{
self.iter()
.filter(|atav| atav.oid == oid)
.map(|atav| T::try_from(&atav.value))
.next()
.transpose()
}

/// Returns the Common Name (CN) found in the name.
///
/// This will return a `Ok(None)` if no CN is found.
///
/// # Errors
///
/// This will return a [`der::Error`] if the content is not serialized as a printableString.
pub fn common_name(&self) -> der::Result<Option<PrintableStringRef<'_>>> {
self.by_oid::<PrintableStringRef<'_>>(rfc4519::COMMON_NAME)
}

/// Returns the Country (C) found in the name.
///
/// This will return a `Ok(None)` if no Country is found.
///
/// # Errors
///
/// This will return a [`der::Error`] if the content is not serialized as a printableString.
pub fn country(&self) -> der::Result<Option<PrintableStringRef<'_>>> {
self.by_oid::<PrintableStringRef<'_>>(rfc4519::COUNTRY)
}

/// Returns the State or Province (ST) found in the name.
///
/// This will return a `Ok(None)` if no State or Province is found.
///
/// # Errors
///
/// This will return a [`der::Error`] if the content is not serialized as a printableString.
pub fn state_or_province(&self) -> der::Result<Option<PrintableStringRef<'_>>> {
self.by_oid::<PrintableStringRef<'_>>(rfc4519::ST)
}

/// Returns the Locality (L) found in the name.
///
/// This will return a `Ok(None)` if no Locality is found.
///
/// # Errors
///
/// This will return a [`der::Error`] if the content is not serialized as a printableString.
pub fn locality(&self) -> der::Result<Option<PrintableStringRef<'_>>> {
self.by_oid::<PrintableStringRef<'_>>(rfc4519::LOCALITY)
}

/// Returns the Organization (O) found in the name.
///
/// This will return a `Ok(None)` if no Organization is found.
///
/// # Errors
///
/// This will return a [`der::Error`] if the content is not serialized as a printableString.
pub fn organization(&self) -> der::Result<Option<PrintableStringRef<'_>>> {
self.by_oid::<PrintableStringRef<'_>>(rfc4519::ORGANIZATION)
}

/// Returns the Organization Unit (OU) found in the name.
///
/// This will return a `Ok(None)` if no Organization Unit is found.
///
/// # Errors
///
/// This will return a [`der::Error`] if the content is not serialized as a printableString.
pub fn organization_unit(&self) -> der::Result<Option<PrintableStringRef<'_>>> {
self.by_oid::<PrintableStringRef<'_>>(rfc4519::ORGANIZATIONAL_UNIT)
}

/// Returns the Email Address (emailAddress) found in the name.
///
/// This will return a `Ok(None)` if no email address is found.
///
/// # Errors
///
/// This will return a [`der::Error`] if the content is not serialized as a printableString.
pub fn email_address(&self) -> der::Result<Option<PrintableStringRef<'_>>> {
self.by_oid::<PrintableStringRef<'_>>(rfc3280::EMAIL_ADDRESS)
}
}

/// Parse a [`Name`] string.
///
/// Follows the rules in [RFC 4514].
///
/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
impl FromStr for Name {
type Err = der::Error;

fn from_str(s: &str) -> der::Result<Self> {
Ok(Self(RdnSequence::from_str(s)?))
}
}

/// Serializes the name according to the rules in [RFC 4514].
///
/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}

/// X.501 RDNSequence as defined in [RFC 5280 Section 4.1.2.4].
///
Expand Down
138 changes: 63 additions & 75 deletions x509-cert/tests/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,41 +238,35 @@ fn decode_cert() {
.unwrap()
.is_null());

let mut counter = 0;
let i = cert.tbs_certificate.issuer.0.iter();
for rdn in i {
let i1 = rdn.0.iter();
for atav in i1 {
if 0 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.6");
assert_eq!(
PrintableStringRef::try_from(&atav.value)
.unwrap()
.to_string(),
"US"
);
} else if 1 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
assert_eq!(
PrintableStringRef::try_from(&atav.value)
.unwrap()
.to_string(),
"Mock"
);
} else if 2 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
assert_eq!(
Utf8StringRef::try_from(&atav.value).unwrap().to_string(),
"IdenTrust Services LLC"
);
} else if 3 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.3");
assert_eq!(
Utf8StringRef::try_from(&atav.value).unwrap().to_string(),
"PTE IdenTrust Global Common Root CA 1"
);
}
counter += 1;
for (counter, atav) in cert.tbs_certificate.issuer.iter().enumerate() {
if 0 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.6");
assert_eq!(
PrintableStringRef::try_from(&atav.value)
.unwrap()
.to_string(),
"US"
);
} else if 1 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
assert_eq!(
PrintableStringRef::try_from(&atav.value)
.unwrap()
.to_string(),
"Mock"
);
} else if 2 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
assert_eq!(
Utf8StringRef::try_from(&atav.value).unwrap().to_string(),
"IdenTrust Services LLC"
);
} else if 3 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.3");
assert_eq!(
Utf8StringRef::try_from(&atav.value).unwrap().to_string(),
"PTE IdenTrust Global Common Root CA 1"
);
}
}

Expand All @@ -293,46 +287,40 @@ fn decode_cert() {
1516628593
);

counter = 0;
let i = cert.tbs_certificate.subject.0.iter();
for rdn in i {
let i1 = rdn.0.iter();
for atav in i1 {
// Yes, this cert features RDNs encoded in reverse order
if 0 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.3");
assert_eq!(
PrintableStringRef::try_from(&atav.value)
.unwrap()
.to_string(),
"Test Federal Bridge CA"
);
} else if 1 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.11");
assert_eq!(
PrintableStringRef::try_from(&atav.value)
.unwrap()
.to_string(),
"TestFPKI"
);
} else if 2 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
assert_eq!(
PrintableStringRef::try_from(&atav.value)
.unwrap()
.to_string(),
"U.S. Government"
);
} else if 3 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.6");
assert_eq!(
PrintableStringRef::try_from(&atav.value)
.unwrap()
.to_string(),
"US"
);
}
counter += 1;
for (counter, atav) in cert.tbs_certificate.subject.iter().enumerate() {
// Yes, this cert features RDNs encoded in reverse order
if 0 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.3");
assert_eq!(
PrintableStringRef::try_from(&atav.value)
.unwrap()
.to_string(),
"Test Federal Bridge CA"
);
} else if 1 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.11");
assert_eq!(
PrintableStringRef::try_from(&atav.value)
.unwrap()
.to_string(),
"TestFPKI"
);
} else if 2 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
assert_eq!(
PrintableStringRef::try_from(&atav.value)
.unwrap()
.to_string(),
"U.S. Government"
);
} else if 3 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.6");
assert_eq!(
PrintableStringRef::try_from(&atav.value)
.unwrap()
.to_string(),
"US"
);
}
}

Expand Down
Loading

0 comments on commit 3a8da71

Please sign in to comment.