Skip to content

Commit ab3d3d6

Browse files
committed
add basic support for subject alt name other name
1 parent 4a49584 commit ab3d3d6

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

rcgen/src/lib.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,38 @@ pub enum SanType {
158158
DnsName(Ia5String),
159159
URI(Ia5String),
160160
IpAddress(IpAddr),
161+
OtherName((Vec<u64>, OtherNameValue)),
162+
}
163+
164+
/// An `OtherName` value, defined in [RFC 5280§4.1.2.4].
165+
///
166+
/// While the standard specifies this could be any ASN.1 type rcgen limits
167+
/// the value to a UTF-8 encoded string as this will cover the most common
168+
/// use cases, for instance smart card user principal names (UPN).
169+
///
170+
/// [RFC 5280§4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4
171+
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
172+
#[non_exhaustive]
173+
pub enum OtherNameValue {
174+
/// A string encoded using UTF-8
175+
Utf8String(String),
176+
}
177+
178+
impl OtherNameValue {
179+
fn write_der(&self, writer: DERWriter) {
180+
writer.write_tagged(Tag::context(0), |writer| match self {
181+
OtherNameValue::Utf8String(s) => writer.write_utf8_string(s),
182+
});
183+
}
184+
}
185+
186+
impl<T> From<T> for OtherNameValue
187+
where
188+
T: Into<String>,
189+
{
190+
fn from(t: T) -> Self {
191+
OtherNameValue::Utf8String(t.into())
192+
}
161193
}
162194

163195
#[cfg(feature = "x509-parser")]
@@ -174,6 +206,7 @@ fn ip_addr_from_octets(octets: &[u8]) -> Result<IpAddr, Error> {
174206
impl SanType {
175207
#[cfg(feature = "x509-parser")]
176208
fn try_from_general(name: &x509_parser::extensions::GeneralName<'_>) -> Result<Self, Error> {
209+
use x509_parser::der_parser::asn1_rs::{self, FromDer, Tag, TaggedExplicit};
177210
Ok(match name {
178211
x509_parser::extensions::GeneralName::RFC822Name(name) => {
179212
SanType::Rfc822Name((*name).try_into()?)
@@ -185,13 +218,31 @@ impl SanType {
185218
x509_parser::extensions::GeneralName::IPAddress(octets) => {
186219
SanType::IpAddress(ip_addr_from_octets(octets)?)
187220
},
221+
x509_parser::extensions::GeneralName::OtherName(oid, value) => {
222+
let oid = oid.iter().ok_or(Error::CouldNotParseCertificate)?;
223+
// We first remove the explicit tag ([0] EXPLICIT)
224+
let (_, other_name) = TaggedExplicit::<asn1_rs::Any, _, 0>::from_der(&value)
225+
.map_err(|_| Error::CouldNotParseCertificate)?;
226+
let other_name = other_name.into_inner();
227+
228+
let other_name_value = match other_name.tag() {
229+
Tag::Utf8String => OtherNameValue::Utf8String(
230+
std::str::from_utf8(other_name.data)
231+
.map_err(|_| Error::CouldNotParseCertificate)?
232+
.to_owned(),
233+
),
234+
_ => return Err(Error::CouldNotParseCertificate),
235+
};
236+
SanType::OtherName((oid.collect(), other_name_value))
237+
},
188238
_ => return Err(Error::InvalidNameType),
189239
})
190240
}
191241

192242
fn tag(&self) -> u64 {
193243
// Defined in the GeneralName list in
194244
// https://tools.ietf.org/html/rfc5280#page-38
245+
const TAG_OTHER_NAME: u64 = 0;
195246
const TAG_RFC822_NAME: u64 = 1;
196247
const TAG_DNS_NAME: u64 = 2;
197248
const TAG_URI: u64 = 6;
@@ -202,6 +253,7 @@ impl SanType {
202253
SanType::DnsName(_name) => TAG_DNS_NAME,
203254
SanType::URI(_name) => TAG_URI,
204255
SanType::IpAddress(_addr) => TAG_IP_ADDRESS,
256+
Self::OtherName(_oid) => TAG_OTHER_NAME,
205257
}
206258
}
207259
}
@@ -879,6 +931,14 @@ impl CertificateParams {
879931
SanType::IpAddress(IpAddr::V6(addr)) => {
880932
writer.write_bytes(&addr.octets())
881933
},
934+
SanType::OtherName((oid, value)) => {
935+
// otherName SEQUENCE { OID, [0] explicit any defined by oid }
936+
// https://datatracker.ietf.org/doc/html/rfc5280#page-38
937+
writer.write_sequence(|writer| {
938+
writer.next().write_oid(&ObjectIdentifier::from_slice(&oid));
939+
value.write_der(writer.next());
940+
});
941+
},
882942
},
883943
);
884944
}

rcgen/tests/generic.rs

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

0 commit comments

Comments
 (0)