Skip to content

Commit bff7da9

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

File tree

2 files changed

+86
-0
lines changed

2 files changed

+86
-0
lines changed

rcgen/src/lib.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,31 @@ const ENCODE_CONFIG: pem::EncodeConfig = {
151151
#[non_exhaustive]
152152
/// The type of subject alt name
153153
pub enum SanType {
154+
OtherName((Vec<u64>, OtherNameValue)),
154155
/// Also known as E-Mail address
155156
Rfc822Name(Ia5String),
156157
DnsName(Ia5String),
157158
URI(Ia5String),
158159
IpAddress(IpAddr),
159160
}
160161

162+
/// An other name entry
163+
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
164+
#[non_exhaustive]
165+
pub enum OtherNameValue {
166+
/// A string encoded using UTF-8
167+
Utf8String(String),
168+
}
169+
170+
impl<T> From<T> for OtherNameValue
171+
where
172+
T: Into<String>,
173+
{
174+
fn from(t: T) -> Self {
175+
OtherNameValue::Utf8String(t.into())
176+
}
177+
}
178+
161179
#[cfg(feature = "x509-parser")]
162180
fn ip_addr_from_octets(octets: &[u8]) -> Result<IpAddr, Error> {
163181
if let Ok(ipv6_octets) = <&[u8; 16]>::try_from(octets) {
@@ -172,7 +190,25 @@ fn ip_addr_from_octets(octets: &[u8]) -> Result<IpAddr, Error> {
172190
impl SanType {
173191
#[cfg(feature = "x509-parser")]
174192
fn try_from_general(name: &x509_parser::extensions::GeneralName<'_>) -> Result<Self, Error> {
193+
use x509_parser::der_parser::asn1_rs::{self, FromDer, Tag, TaggedExplicit};
175194
Ok(match name {
195+
x509_parser::extensions::GeneralName::OtherName(oid, value) => {
196+
let oid = oid.iter().ok_or(Error::CouldNotParseCertificate)?;
197+
// We first remove the explicit tag ([0] EXPLICIT)
198+
let (_, other_name) = TaggedExplicit::<asn1_rs::Any, _, 0>::from_der(&value)
199+
.map_err(|_| Error::CouldNotParseCertificate)?;
200+
let other_name = other_name.into_inner();
201+
202+
let data = other_name.data;
203+
let try_str =
204+
|data| std::str::from_utf8(data).map_err(|_| Error::CouldNotParseCertificate);
205+
let other_name_value = match other_name.tag() {
206+
Tag::Utf8String => OtherNameValue::Utf8String(try_str(data)?.to_owned()),
207+
_ => return Err(Error::CouldNotParseCertificate),
208+
};
209+
210+
SanType::OtherName((oid.collect(), other_name_value))
211+
},
176212
x509_parser::extensions::GeneralName::RFC822Name(name) => {
177213
SanType::Rfc822Name((*name).try_into()?)
178214
},
@@ -190,12 +226,14 @@ impl SanType {
190226
fn tag(&self) -> u64 {
191227
// Defined in the GeneralName list in
192228
// https://tools.ietf.org/html/rfc5280#page-38
229+
const TAG_OTHER_NAME: u64 = 0;
193230
const TAG_RFC822_NAME: u64 = 1;
194231
const TAG_DNS_NAME: u64 = 2;
195232
const TAG_URI: u64 = 6;
196233
const TAG_IP_ADDRESS: u64 = 7;
197234

198235
match self {
236+
Self::OtherName(_oid) => TAG_OTHER_NAME,
199237
SanType::Rfc822Name(_name) => TAG_RFC822_NAME,
200238
SanType::DnsName(_name) => TAG_DNS_NAME,
201239
SanType::URI(_name) => TAG_URI,
@@ -856,6 +894,22 @@ impl CertificateParams {
856894
writer.next().write_tagged_implicit(
857895
Tag::context(san.tag()),
858896
|writer| match san {
897+
SanType::OtherName((oid, value)) => {
898+
// otherName SEQUENCE { OID, [0] explicit any defined by oid }
899+
// https://datatracker.ietf.org/doc/html/rfc5280#page-38
900+
let oid = ObjectIdentifier::from_slice(&oid);
901+
writer.write_sequence(|writer| {
902+
writer.next().write_oid(&oid);
903+
writer.next().write_tagged(
904+
Tag::context(0),
905+
|writer| match value {
906+
OtherNameValue::Utf8String(s) => {
907+
writer.write_utf8_string(s)
908+
},
909+
},
910+
);
911+
});
912+
},
859913
SanType::Rfc822Name(name)
860914
| SanType::DnsName(name)
861915
| SanType::URI(name) => writer.write_ia5_string(name.as_str()),

rcgen/tests/generic.rs

Lines changed: 32 additions & 0 deletions
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)