Skip to content

Commit 5d0fadf

Browse files
committed
add basic support for subject alt name other name
1 parent 28ec9fa commit 5d0fadf

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-0
lines changed

rcgen/src/lib.rs

+59
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,29 @@ pub enum SanType {
156156
DnsName(Ia5String),
157157
URI(Ia5String),
158158
IpAddress(IpAddr),
159+
OtherName((Vec<u64>, OtherNameValue)),
160+
}
161+
162+
/// An OtherName value, defined in [RFC 5280§4.1.2.4].
163+
///
164+
/// Technically, this could be any ASN.1 types but for now we cover only UTF-8 strings
165+
/// as it will cover most of use cases, for instance smart cards.
166+
///
167+
/// [RFC 5280§4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4
168+
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
169+
#[non_exhaustive]
170+
pub enum OtherNameValue {
171+
/// A string encoded using UTF-8
172+
Utf8String(String),
173+
}
174+
175+
impl<T> From<T> for OtherNameValue
176+
where
177+
T: Into<String>,
178+
{
179+
fn from(t: T) -> Self {
180+
OtherNameValue::Utf8String(t.into())
181+
}
159182
}
160183

161184
#[cfg(feature = "x509-parser")]
@@ -172,6 +195,7 @@ fn ip_addr_from_octets(octets: &[u8]) -> Result<IpAddr, Error> {
172195
impl SanType {
173196
#[cfg(feature = "x509-parser")]
174197
fn try_from_general(name: &x509_parser::extensions::GeneralName<'_>) -> Result<Self, Error> {
198+
use x509_parser::der_parser::asn1_rs::{self, FromDer, Tag, TaggedExplicit};
175199
Ok(match name {
176200
x509_parser::extensions::GeneralName::RFC822Name(name) => {
177201
SanType::Rfc822Name((*name).try_into()?)
@@ -183,19 +207,38 @@ impl SanType {
183207
x509_parser::extensions::GeneralName::IPAddress(octets) => {
184208
SanType::IpAddress(ip_addr_from_octets(octets)?)
185209
},
210+
x509_parser::extensions::GeneralName::OtherName(oid, value) => {
211+
let oid = oid.iter().ok_or(Error::CouldNotParseCertificate)?;
212+
// We first remove the explicit tag ([0] EXPLICIT)
213+
let (_, other_name) = TaggedExplicit::<asn1_rs::Any, _, 0>::from_der(&value)
214+
.map_err(|_| Error::CouldNotParseCertificate)?;
215+
let other_name = other_name.into_inner();
216+
217+
let data = other_name.data;
218+
let try_str =
219+
|data| std::str::from_utf8(data).map_err(|_| Error::CouldNotParseCertificate);
220+
let other_name_value = match other_name.tag() {
221+
Tag::Utf8String => OtherNameValue::Utf8String(try_str(data)?.to_owned()),
222+
_ => return Err(Error::CouldNotParseCertificate),
223+
};
224+
225+
SanType::OtherName((oid.collect(), other_name_value))
226+
},
186227
_ => return Err(Error::InvalidNameType),
187228
})
188229
}
189230

190231
fn tag(&self) -> u64 {
191232
// Defined in the GeneralName list in
192233
// https://tools.ietf.org/html/rfc5280#page-38
234+
const TAG_OTHER_NAME: u64 = 0;
193235
const TAG_RFC822_NAME: u64 = 1;
194236
const TAG_DNS_NAME: u64 = 2;
195237
const TAG_URI: u64 = 6;
196238
const TAG_IP_ADDRESS: u64 = 7;
197239

198240
match self {
241+
Self::OtherName(_oid) => TAG_OTHER_NAME,
199242
SanType::Rfc822Name(_name) => TAG_RFC822_NAME,
200243
SanType::DnsName(_name) => TAG_DNS_NAME,
201244
SanType::URI(_name) => TAG_URI,
@@ -865,6 +908,22 @@ impl CertificateParams {
865908
SanType::IpAddress(IpAddr::V6(addr)) => {
866909
writer.write_bytes(&addr.octets())
867910
},
911+
SanType::OtherName((oid, value)) => {
912+
// otherName SEQUENCE { OID, [0] explicit any defined by oid }
913+
// https://datatracker.ietf.org/doc/html/rfc5280#page-38
914+
let oid = ObjectIdentifier::from_slice(&oid);
915+
writer.write_sequence(|writer| {
916+
writer.next().write_oid(&oid);
917+
writer.next().write_tagged(
918+
Tag::context(0),
919+
|writer| match value {
920+
OtherNameValue::Utf8String(s) => {
921+
writer.write_utf8_string(s)
922+
},
923+
},
924+
);
925+
});
926+
},
868927
},
869928
);
870929
}

rcgen/tests/generic.rs

+32
Original file line numberDiff line numberDiff line change
@@ -306,3 +306,35 @@ mod test_parse_ia5string_subject {
306306
assert_eq!(names, expected_names);
307307
}
308308
}
309+
310+
#[cfg(feature = "x509-parser")]
311+
mod test_parse_other_name_alt_name {
312+
use rcgen::{Certificate, CertificateParams, KeyPair, SanType};
313+
314+
#[test]
315+
fn parse_other_name_alt_name() {
316+
// Create and serialize a certificate with an alternative name containing an "OtherName".
317+
let mut params = CertificateParams::default();
318+
let other_name = SanType::OtherName((vec![1, 2, 3, 4], "Foo".into()));
319+
params.subject_alt_names.push(other_name.clone());
320+
let key_pair = KeyPair::generate().unwrap();
321+
322+
let cert = Certificate::generate_self_signed(params, &key_pair).unwrap();
323+
324+
let cert_der = cert.der();
325+
326+
// We should be able to parse the certificate with x509-parser.
327+
assert!(x509_parser::parse_x509_certificate(cert_der).is_ok());
328+
329+
// We should be able to reconstitute params from the DER using x509-parser.
330+
let params_from_cert = CertificateParams::from_ca_cert_der(cert_der).unwrap();
331+
332+
// We should find the expected distinguished name in the reconstituted params.
333+
let expected_alt_names = &[&other_name];
334+
let subject_alt_names = params_from_cert
335+
.subject_alt_names
336+
.iter()
337+
.collect::<Vec<_>>();
338+
assert_eq!(subject_alt_names, expected_alt_names);
339+
}
340+
}

0 commit comments

Comments
 (0)