Skip to content

Commit 1b75025

Browse files
committed
add basic support for subject alt name other name
1 parent b87fced commit 1b75025

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed

rcgen/src/lib.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,41 @@ const ENCODE_CONFIG: pem::EncodeConfig = {
149149
#[non_exhaustive]
150150
/// The type of subject alt name
151151
pub enum SanType {
152+
OtherName((Vec<u64>, OtherNameValue)),
152153
/// Also known as E-Mail address
153154
Rfc822Name(String),
154155
DnsName(String),
155156
URI(String),
156157
IpAddress(IpAddr),
157158
}
158159

160+
/// An other name entry
161+
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
162+
#[non_exhaustive]
163+
pub enum OtherNameValue {
164+
/// A string encoded using UCS-2
165+
BmpString(Vec<u8>),
166+
/// An ASCII string.
167+
Ia5String(String),
168+
/// An ASCII string containing only A-Z, a-z, 0-9, '()+,-./:=? and `<SPACE>`
169+
PrintableString(String),
170+
/// A string of characters from the T.61 character set
171+
TeletexString(Vec<u8>),
172+
/// A string encoded using UTF-32
173+
UniversalString(Vec<u8>),
174+
/// A string encoded using UTF-8
175+
Utf8String(String),
176+
}
177+
178+
impl<T> From<T> for OtherNameValue
179+
where
180+
T: Into<String>,
181+
{
182+
fn from(t: T) -> Self {
183+
OtherNameValue::Utf8String(t.into())
184+
}
185+
}
186+
159187
#[cfg(feature = "x509-parser")]
160188
fn ip_addr_from_octets(octets: &[u8]) -> Result<IpAddr, Error> {
161189
if let Ok(ipv6_octets) = <&[u8; 16]>::try_from(octets) {
@@ -170,7 +198,32 @@ fn ip_addr_from_octets(octets: &[u8]) -> Result<IpAddr, Error> {
170198
impl SanType {
171199
#[cfg(feature = "x509-parser")]
172200
fn try_from_general(name: &x509_parser::extensions::GeneralName<'_>) -> Result<Self, Error> {
201+
use x509_parser::der_parser::asn1_rs::{self, FromDer, Tag, TaggedExplicit};
173202
Ok(match name {
203+
x509_parser::extensions::GeneralName::OtherName(oid, value) => {
204+
let oid = oid.iter().ok_or(Error::CouldNotParseCertificate)?;
205+
// We first remove the explicit tag ([0] EXPLICIT)
206+
let (_, other_name) = TaggedExplicit::<asn1_rs::Any, _, 0>::from_der(&value)
207+
.map_err(|_| Error::CouldNotParseCertificate)?;
208+
let other_name = other_name.into_inner();
209+
210+
let data = other_name.data;
211+
let try_str =
212+
|data| std::str::from_utf8(data).map_err(|_| Error::CouldNotParseCertificate);
213+
let other_name_value = match other_name.tag() {
214+
Tag::BmpString => OtherNameValue::BmpString(data.into()),
215+
Tag::Ia5String => OtherNameValue::Ia5String(try_str(data)?.to_owned()),
216+
Tag::PrintableString => {
217+
OtherNameValue::PrintableString(try_str(data)?.to_owned())
218+
},
219+
Tag::T61String => OtherNameValue::TeletexString(data.into()),
220+
Tag::UniversalString => OtherNameValue::UniversalString(data.into()),
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+
},
174227
x509_parser::extensions::GeneralName::RFC822Name(name) => {
175228
SanType::Rfc822Name((*name).into())
176229
},
@@ -186,12 +239,14 @@ impl SanType {
186239
fn tag(&self) -> u64 {
187240
// Defined in the GeneralName list in
188241
// https://tools.ietf.org/html/rfc5280#page-38
242+
const TAG_OTHER_NAME: u64 = 0;
189243
const TAG_RFC822_NAME: u64 = 1;
190244
const TAG_DNS_NAME: u64 = 2;
191245
const TAG_URI: u64 = 6;
192246
const TAG_IP_ADDRESS: u64 = 7;
193247

194248
match self {
249+
Self::OtherName(_oid) => TAG_OTHER_NAME,
195250
SanType::Rfc822Name(_name) => TAG_RFC822_NAME,
196251
SanType::DnsName(_name) => TAG_DNS_NAME,
197252
SanType::URI(_name) => TAG_URI,
@@ -848,6 +903,42 @@ impl CertificateParams {
848903
writer.next().write_tagged_implicit(
849904
Tag::context(san.tag()),
850905
|writer| match san {
906+
SanType::OtherName((oid, value)) => {
907+
// otherName SEQUENCE { OID, [0] explicit any defined by oid }
908+
// https://datatracker.ietf.org/doc/html/rfc5280#page-38
909+
let oid = ObjectIdentifier::from_slice(&oid);
910+
writer.write_sequence(|writer| {
911+
writer.next().write_oid(&oid);
912+
writer.next().write_tagged(
913+
Tag::context(0),
914+
|writer| match value {
915+
OtherNameValue::BmpString(s) => writer
916+
.write_tagged_implicit(TAG_BMPSTRING, |writer| {
917+
writer.write_bytes(s)
918+
}),
919+
OtherNameValue::Ia5String(s) => {
920+
writer.write_ia5_string(s)
921+
},
922+
OtherNameValue::PrintableString(s) => {
923+
writer.write_printable_string(s)
924+
},
925+
OtherNameValue::TeletexString(s) => writer
926+
.write_tagged_implicit(
927+
TAG_TELETEXSTRING,
928+
|writer| writer.write_bytes(s),
929+
),
930+
OtherNameValue::UniversalString(s) => writer
931+
.write_tagged_implicit(
932+
TAG_UNIVERSALSTRING,
933+
|writer| writer.write_bytes(s),
934+
),
935+
OtherNameValue::Utf8String(s) => {
936+
writer.write_utf8_string(s)
937+
},
938+
},
939+
);
940+
});
941+
},
851942
SanType::Rfc822Name(name)
852943
| SanType::DnsName(name)
853944
| SanType::URI(name) => writer.write_ia5_string(name),

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)