Skip to content

Commit b168e0d

Browse files
committed
x509-cert: make Name a new type over RdnSequence
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, ...).
1 parent 3fb883b commit b168e0d

File tree

9 files changed

+510
-435
lines changed

9 files changed

+510
-435
lines changed

cms/tests/builder.rs

+6-23
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use cms::enveloped_data::RecipientInfo::Ktri;
1313
use cms::enveloped_data::{EnvelopedData, RecipientIdentifier, RecipientInfo};
1414
use cms::signed_data::{EncapsulatedContentInfo, SignedData, SignerIdentifier};
1515
use const_oid::ObjectIdentifier;
16-
use der::asn1::{OctetString, PrintableString, SetOfVec, Utf8StringRef};
16+
use der::asn1::{OctetString, PrintableString, SetOfVec};
1717
use der::{Any, AnyRef, Decode, DecodePem, Encode, Tag, Tagged};
1818
use p256::{pkcs8::DecodePrivateKey, NistP256};
1919
use pem_rfc7468::LineEnding;
@@ -24,8 +24,7 @@ use rsa::{Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey};
2424
use sha2::Sha256;
2525
use signature::Verifier;
2626
use spki::AlgorithmIdentifierOwned;
27-
use x509_cert::attr::{Attribute, AttributeTypeAndValue, AttributeValue};
28-
use x509_cert::name::{RdnSequence, RelativeDistinguishedName};
27+
use x509_cert::attr::{Attribute, AttributeValue};
2928
use x509_cert::serial_number::SerialNumber;
3029

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

5251
fn signer_identifier(id: i32) -> SignerIdentifier {
53-
let mut rdn_sequence = RdnSequence::default();
54-
let rdn = &[AttributeTypeAndValue {
55-
oid: const_oid::db::rfc4519::CN,
56-
value: Any::from(Utf8StringRef::new(&format!("test client {id}")).unwrap()),
57-
}];
58-
let set_of_vector = SetOfVec::try_from(rdn.to_vec()).unwrap();
59-
rdn_sequence
60-
.0
61-
.push(RelativeDistinguishedName::from(set_of_vector));
52+
let issuer = format!("CN=test client {id}").parse().unwrap();
6253
SignerIdentifier::IssuerAndSerialNumber(IssuerAndSerialNumber {
63-
issuer: rdn_sequence,
54+
issuer,
6455
serial_number: SerialNumber::new(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06])
6556
.expect("failed to create a serial number"),
6657
})
6758
}
6859

6960
fn recipient_identifier(id: i32) -> RecipientIdentifier {
70-
let mut rdn_sequence = RdnSequence::default();
71-
let rdn = &[AttributeTypeAndValue {
72-
oid: const_oid::db::rfc4519::CN,
73-
value: Any::from(Utf8StringRef::new(&format!("test client {id}")).unwrap()),
74-
}];
75-
let set_of_vector = SetOfVec::try_from(rdn.to_vec()).unwrap();
76-
rdn_sequence
77-
.0
78-
.push(RelativeDistinguishedName::from(set_of_vector));
61+
let issuer = format!("CN=test client {id}").parse().unwrap();
7962
RecipientIdentifier::IssuerAndSerialNumber(IssuerAndSerialNumber {
80-
issuer: rdn_sequence,
63+
issuer,
8164
serial_number: SerialNumber::new(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06])
8265
.expect("failed to create a serial number"),
8366
})

x509-cert/src/builder/profile/cabf.rs

+5-7
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub fn check_names_encoding(name: &Name, multiple_allowed: bool) -> Result<()> {
4343

4444
let mut seen = HashSet::new();
4545

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

8888
check_names_encoding(subject, false)?;
8989

90-
for rdn in subject.0.iter() {
91-
for atv in rdn.0.iter() {
92-
if !allowed.remove(&atv.oid) {
93-
return Err(Error::InvalidAttribute { oid: atv.oid });
94-
}
95-
required.remove(&atv.oid);
90+
for atv in subject.iter() {
91+
if !allowed.remove(&atv.oid) {
92+
return Err(Error::InvalidAttribute { oid: atv.oid });
9693
}
94+
required.remove(&atv.oid);
9795
}
9896

9997
if !required.is_empty() {

x509-cert/src/builder/profile/cabf/tls.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::{
2222
},
2323
AsExtension, Extension,
2424
},
25-
name::{Name, RelativeDistinguishedName},
25+
name::{Name, RdnSequence, RelativeDistinguishedName},
2626
};
2727
use spki::SubjectPublicKeyInfoRef;
2828

@@ -145,8 +145,7 @@ impl CertificateType {
145145
// TODO(baloo): not very happy with all that, might as well throw that in a helper
146146
// or something.
147147
let rdns: vec::Vec<RelativeDistinguishedName> = subject
148-
.0
149-
.iter()
148+
.iter_rdn()
150149
.filter_map(|rdn| {
151150
let out = SetOfVec::<AttributeTypeAndValue>::from_iter(
152151
rdn.0
@@ -161,7 +160,8 @@ impl CertificateType {
161160
.filter(|rdn| !rdn.0.is_empty())
162161
.collect();
163162

164-
let subject: Name = rdns.into();
163+
let subject: RdnSequence = rdns.into();
164+
let subject: Name = subject.into();
165165

166166
Ok(Self::DomainValidated(DomainValidated { subject, names }))
167167
}

x509-cert/src/name.rs

+169-2
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,184 @@
22
33
use crate::attr::AttributeTypeAndValue;
44
use alloc::vec::Vec;
5+
use const_oid::{
6+
db::{rfc3280, rfc4519},
7+
ObjectIdentifier,
8+
};
59
use core::{fmt, str::FromStr};
6-
use der::{asn1::SetOfVec, Encode};
10+
use der::{
11+
asn1::{Any, PrintableStringRef, SetOfVec},
12+
Encode,
13+
};
714

815
/// X.501 Name as defined in [RFC 5280 Section 4.1.2.4]. X.501 Name is used to represent distinguished names.
916
///
1017
/// ```text
1118
/// Name ::= CHOICE { rdnSequence RDNSequence }
1219
/// ```
1320
///
21+
/// # Example
22+
///
23+
/// ```
24+
/// use std::str::FromStr;
25+
/// use x509_cert::name::Name;
26+
///
27+
/// let subject = Name::from_str("CN=example.com").expect("correctly formatted subject");
28+
/// ```
29+
///
1430
/// [RFC 5280 Section 4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4
15-
pub type Name = RdnSequence;
31+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
32+
#[derive(Clone, Debug, Default, PartialEq, Eq)]
33+
pub struct Name(RdnSequence);
34+
35+
impl_newtype!(Name, RdnSequence);
36+
37+
impl Name {
38+
/// Is this [`Name`] empty?
39+
#[inline]
40+
pub fn is_empty(&self) -> bool {
41+
self.0.is_empty()
42+
}
43+
44+
/// Returns the number of [`RelativeDistinguishedName`] elements in this [`Name`].
45+
pub fn len(&self) -> usize {
46+
self.0 .0.len()
47+
}
48+
49+
/// Returns an iterator over the inner [`AttributeTypeAndValue`]s.
50+
///
51+
/// This iterator does not expose which attributes are grouped together as
52+
/// [`RelativeDistinguishedName`]s. If you need this, use [`Self::iter_rdn`].
53+
#[inline]
54+
pub fn iter(&self) -> impl Iterator<Item = &'_ AttributeTypeAndValue> + '_ {
55+
self.0 .0.iter().flat_map(move |rdn| rdn.0.as_slice())
56+
}
57+
58+
/// Returns an iterator over the inner [`RelativeDistinguishedName`]s.
59+
#[inline]
60+
pub fn iter_rdn(&self) -> impl Iterator<Item = &'_ RelativeDistinguishedName> + '_ {
61+
self.0 .0.iter()
62+
}
63+
}
64+
65+
impl Name {
66+
/// Returns the element found in the name identified by `oid`
67+
///
68+
/// This will return `Ok(None)` if no such element is present.
69+
///
70+
/// # Errors
71+
///
72+
/// This will return [`der::Error`] if the content is not serialized as expected.
73+
pub fn by_oid<'a, T>(&'a self, oid: ObjectIdentifier) -> der::Result<Option<T>>
74+
where
75+
T: TryFrom<&'a Any, Error = der::Error>,
76+
{
77+
self.iter()
78+
.filter(|atav| atav.oid == oid)
79+
.map(|atav| T::try_from(&atav.value))
80+
.next()
81+
.transpose()
82+
}
83+
84+
/// Returns the Common Name (CN) found in the name.
85+
///
86+
/// This will return `Ok(None)` if no CN is found.
87+
///
88+
/// # Errors
89+
///
90+
/// This will return [`der::Error`] if the content is not serialized as a printableString.
91+
pub fn common_name(&self) -> der::Result<Option<PrintableStringRef<'_>>> {
92+
self.by_oid::<PrintableStringRef<'_>>(rfc4519::COMMON_NAME)
93+
}
94+
95+
/// Returns the Country (C) found in the name.
96+
///
97+
/// This will return `Ok(None)` if no Country is found.
98+
///
99+
/// # Errors
100+
///
101+
/// This will return [`der::Error`] if the content is not serialized as a printableString.
102+
pub fn country(&self) -> der::Result<Option<PrintableStringRef<'_>>> {
103+
self.by_oid::<PrintableStringRef<'_>>(rfc4519::COUNTRY)
104+
}
105+
106+
/// Returns the State or Province (ST) found in the name.
107+
///
108+
/// This will return `Ok(None)` if no State or Province is found.
109+
///
110+
/// # Errors
111+
///
112+
/// This will return [`der::Error`] if the content is not serialized as a printableString.
113+
pub fn state_or_province(&self) -> der::Result<Option<PrintableStringRef<'_>>> {
114+
self.by_oid::<PrintableStringRef<'_>>(rfc4519::ST)
115+
}
116+
117+
/// Returns the Locality (L) found in the name.
118+
///
119+
/// This will return `Ok(None)` if no Locality is found.
120+
///
121+
/// # Errors
122+
///
123+
/// This will return [`der::Error`] if the content is not serialized as a printableString.
124+
pub fn locality(&self) -> der::Result<Option<PrintableStringRef<'_>>> {
125+
self.by_oid::<PrintableStringRef<'_>>(rfc4519::LOCALITY)
126+
}
127+
128+
/// Returns the Organization (O) found in the name.
129+
///
130+
/// This will return `Ok(None)` if no Organization is found.
131+
///
132+
/// # Errors
133+
///
134+
/// This will return [`der::Error`] if the content is not serialized as a printableString.
135+
pub fn organization(&self) -> der::Result<Option<PrintableStringRef<'_>>> {
136+
self.by_oid::<PrintableStringRef<'_>>(rfc4519::ORGANIZATION)
137+
}
138+
139+
/// Returns the Organization Unit (OU) found in the name.
140+
///
141+
/// This will return `Ok(None)` if no Organization Unit is found.
142+
///
143+
/// # Errors
144+
///
145+
/// This will return [`der::Error`] if the content is not serialized as a printableString.
146+
pub fn organization_unit(&self) -> der::Result<Option<PrintableStringRef<'_>>> {
147+
self.by_oid::<PrintableStringRef<'_>>(rfc4519::ORGANIZATIONAL_UNIT)
148+
}
149+
150+
/// Returns the Email Address (emailAddress) found in the name.
151+
///
152+
/// This will return `Ok(None)` if no email address is found.
153+
///
154+
/// # Errors
155+
///
156+
/// This will return [`der::Error`] if the content is not serialized as a printableString.
157+
pub fn email_address(&self) -> der::Result<Option<PrintableStringRef<'_>>> {
158+
self.by_oid::<PrintableStringRef<'_>>(rfc3280::EMAIL_ADDRESS)
159+
}
160+
}
161+
162+
/// Parse a [`Name`] string.
163+
///
164+
/// Follows the rules in [RFC 4514].
165+
///
166+
/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
167+
impl FromStr for Name {
168+
type Err = der::Error;
169+
170+
fn from_str(s: &str) -> der::Result<Self> {
171+
Ok(Self(RdnSequence::from_str(s)?))
172+
}
173+
}
174+
175+
/// Serializes the name according to the rules in [RFC 4514].
176+
///
177+
/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
178+
impl fmt::Display for Name {
179+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180+
self.0.fmt(f)
181+
}
182+
}
16183

17184
/// X.501 RDNSequence as defined in [RFC 5280 Section 4.1.2.4].
18185
///

0 commit comments

Comments
 (0)