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 3fb883b commit d9fbfc4
Show file tree
Hide file tree
Showing 9 changed files with 577 additions and 435 deletions.
29 changes: 6 additions & 23 deletions cms/tests/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use cms::enveloped_data::RecipientInfo::Ktri;
use cms::enveloped_data::{EnvelopedData, RecipientIdentifier, RecipientInfo};
use cms::signed_data::{EncapsulatedContentInfo, SignedData, SignerIdentifier};
use const_oid::ObjectIdentifier;
use der::asn1::{OctetString, PrintableString, SetOfVec, Utf8StringRef};
use der::asn1::{OctetString, PrintableString, SetOfVec};
use der::{Any, AnyRef, Decode, DecodePem, Encode, Tag, Tagged};
use p256::{pkcs8::DecodePrivateKey, NistP256};
use pem_rfc7468::LineEnding;
Expand All @@ -24,8 +24,7 @@ use rsa::{Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey};
use sha2::Sha256;
use signature::Verifier;
use spki::AlgorithmIdentifierOwned;
use x509_cert::attr::{Attribute, AttributeTypeAndValue, AttributeValue};
use x509_cert::name::{RdnSequence, RelativeDistinguishedName};
use x509_cert::attr::{Attribute, AttributeValue};
use x509_cert::serial_number::SerialNumber;

// TODO bk replace this by const_oid definitions as soon as released
Expand All @@ -50,34 +49,18 @@ fn ecdsa_signer() -> ecdsa::SigningKey<NistP256> {
}

fn signer_identifier(id: i32) -> SignerIdentifier {
let mut rdn_sequence = RdnSequence::default();
let rdn = &[AttributeTypeAndValue {
oid: const_oid::db::rfc4519::CN,
value: Any::from(Utf8StringRef::new(&format!("test client {id}")).unwrap()),
}];
let set_of_vector = SetOfVec::try_from(rdn.to_vec()).unwrap();
rdn_sequence
.0
.push(RelativeDistinguishedName::from(set_of_vector));
let issuer = format!("CN=test client {id}").parse().unwrap();
SignerIdentifier::IssuerAndSerialNumber(IssuerAndSerialNumber {
issuer: rdn_sequence,
issuer,
serial_number: SerialNumber::new(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06])
.expect("failed to create a serial number"),
})
}

fn recipient_identifier(id: i32) -> RecipientIdentifier {
let mut rdn_sequence = RdnSequence::default();
let rdn = &[AttributeTypeAndValue {
oid: const_oid::db::rfc4519::CN,
value: Any::from(Utf8StringRef::new(&format!("test client {id}")).unwrap()),
}];
let set_of_vector = SetOfVec::try_from(rdn.to_vec()).unwrap();
rdn_sequence
.0
.push(RelativeDistinguishedName::from(set_of_vector));
let issuer = format!("CN=test client {id}").parse().unwrap();
RecipientIdentifier::IssuerAndSerialNumber(IssuerAndSerialNumber {
issuer: rdn_sequence,
issuer,
serial_number: SerialNumber::new(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06])
.expect("failed to create a serial number"),
})
Expand Down
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
188 changes: 186 additions & 2 deletions x509-cert/src/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,201 @@

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, Ia5StringRef, PrintableStringRef, SetOfVec, Utf8StringRef},
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`] elements in this [`Name`].
pub fn len(&self) -> usize {
self.0 .0.len()
}

/// Returns an iterator over the inner [`AttributeTypeAndValue`]s.
///
/// This iterator does not expose which attributes are grouped together as
/// [`RelativeDistinguishedName`]s. If you need this, use [`Self::iter_rdn`].
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &'_ AttributeTypeAndValue> + '_ {
self.0 .0.iter().flat_map(move |rdn| rdn.0.as_slice())
}

/// Returns an iterator over the inner [`RelativeDistinguishedName`]s.
#[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 `Ok(None)` if no such element is present.
///
/// # Errors
///
/// This will return [`der::Error`] if the content is not serialized as expected or if the more
/// than one attribute with the given oid is present.
pub fn by_oid<'a, T>(&'a self, oid: ObjectIdentifier) -> der::Result<Option<T>>
where
T: TryFrom<&'a Any, Error = der::Error>,
T: fmt::Debug,
{
let mut iter = self
.iter()
.filter(|atav| atav.oid == oid)
.map(|atav| T::try_from(&atav.value))
.peekable();

match iter.next() {
None => Ok(None),
Some(item) => match iter.peek() {
Some(..) => Err(der::Error::from(der::ErrorKind::Failed).into()),
None => Ok(Some(item?)),
},
}
}

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

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

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

/// Returns the Locality (L) found in the name.
///
/// This will return `Ok(None)` if no Locality is found.
///
/// # Errors
///
/// This will return [`der::Error`] if the content is not serialized as an utf8String or if the more
/// than one L is present
pub fn locality(&self) -> der::Result<Option<Utf8StringRef<'_>>> {
self.by_oid::<Utf8StringRef<'_>>(rfc4519::LOCALITY_NAME)
}

/// Returns the Organization (O) found in the name.
///
/// This will return `Ok(None)` if no Organization is found.
///
/// # Errors
///
/// This will return [`der::Error`] if the content is not serialized as an utf8String or if the more
/// than one O is present
pub fn organization(&self) -> der::Result<Option<Utf8StringRef<'_>>> {
self.by_oid::<Utf8StringRef<'_>>(rfc4519::ORGANIZATION_NAME)
}

/// Returns the Organization Unit (OU) found in the name.
///
/// This will return `Ok(None)` if no Organization Unit is found.
///
/// # Errors
///
/// This will return [`der::Error`] if the content is not serialized as an utf8String or if the more
/// than one OU is present
pub fn organization_unit(&self) -> der::Result<Option<Utf8StringRef<'_>>> {
self.by_oid::<Utf8StringRef<'_>>(rfc4519::ORGANIZATIONAL_UNIT_NAME)
}

/// Returns the Email Address (emailAddress) found in the name.
///
/// This will return `Ok(None)` if no email address is found.
///
/// # Errors
///
/// This will return [`der::Error`] if the content is not serialized as an ia5String or if the more
/// than one emailAddress is present
pub fn email_address(&self) -> der::Result<Option<Ia5StringRef<'_>>> {
self.by_oid::<Ia5StringRef<'_>>(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
Loading

0 comments on commit d9fbfc4

Please sign in to comment.