From ab3d3d6a1bcd55d4e9270a4bbe1476d231853e79 Mon Sep 17 00:00:00 2001 From: Tudyx <56633664+Tudyx@users.noreply.github.com> Date: Sun, 14 Jan 2024 10:16:46 +0100 Subject: [PATCH] add basic support for subject alt name other name --- rcgen/src/lib.rs | 60 ++++++++++++++++++++++++++++++++++++++++++ rcgen/tests/generic.rs | 32 ++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/rcgen/src/lib.rs b/rcgen/src/lib.rs index 0b8689a2..8a6295fd 100644 --- a/rcgen/src/lib.rs +++ b/rcgen/src/lib.rs @@ -158,6 +158,38 @@ pub enum SanType { DnsName(Ia5String), URI(Ia5String), IpAddress(IpAddr), + OtherName((Vec, OtherNameValue)), +} + +/// An `OtherName` value, defined in [RFC 5280§4.1.2.4]. +/// +/// While the standard specifies this could be any ASN.1 type rcgen limits +/// the value to a UTF-8 encoded string as this will cover the most common +/// use cases, for instance smart card user principal names (UPN). +/// +/// [RFC 5280§4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4 +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[non_exhaustive] +pub enum OtherNameValue { + /// A string encoded using UTF-8 + Utf8String(String), +} + +impl OtherNameValue { + fn write_der(&self, writer: DERWriter) { + writer.write_tagged(Tag::context(0), |writer| match self { + OtherNameValue::Utf8String(s) => writer.write_utf8_string(s), + }); + } +} + +impl From for OtherNameValue +where + T: Into, +{ + fn from(t: T) -> Self { + OtherNameValue::Utf8String(t.into()) + } } #[cfg(feature = "x509-parser")] @@ -174,6 +206,7 @@ fn ip_addr_from_octets(octets: &[u8]) -> Result { impl SanType { #[cfg(feature = "x509-parser")] fn try_from_general(name: &x509_parser::extensions::GeneralName<'_>) -> Result { + use x509_parser::der_parser::asn1_rs::{self, FromDer, Tag, TaggedExplicit}; Ok(match name { x509_parser::extensions::GeneralName::RFC822Name(name) => { SanType::Rfc822Name((*name).try_into()?) @@ -185,6 +218,23 @@ impl SanType { x509_parser::extensions::GeneralName::IPAddress(octets) => { SanType::IpAddress(ip_addr_from_octets(octets)?) }, + x509_parser::extensions::GeneralName::OtherName(oid, value) => { + let oid = oid.iter().ok_or(Error::CouldNotParseCertificate)?; + // We first remove the explicit tag ([0] EXPLICIT) + let (_, other_name) = TaggedExplicit::::from_der(&value) + .map_err(|_| Error::CouldNotParseCertificate)?; + let other_name = other_name.into_inner(); + + let other_name_value = match other_name.tag() { + Tag::Utf8String => OtherNameValue::Utf8String( + std::str::from_utf8(other_name.data) + .map_err(|_| Error::CouldNotParseCertificate)? + .to_owned(), + ), + _ => return Err(Error::CouldNotParseCertificate), + }; + SanType::OtherName((oid.collect(), other_name_value)) + }, _ => return Err(Error::InvalidNameType), }) } @@ -192,6 +242,7 @@ impl SanType { fn tag(&self) -> u64 { // Defined in the GeneralName list in // https://tools.ietf.org/html/rfc5280#page-38 + const TAG_OTHER_NAME: u64 = 0; const TAG_RFC822_NAME: u64 = 1; const TAG_DNS_NAME: u64 = 2; const TAG_URI: u64 = 6; @@ -202,6 +253,7 @@ impl SanType { SanType::DnsName(_name) => TAG_DNS_NAME, SanType::URI(_name) => TAG_URI, SanType::IpAddress(_addr) => TAG_IP_ADDRESS, + Self::OtherName(_oid) => TAG_OTHER_NAME, } } } @@ -879,6 +931,14 @@ impl CertificateParams { SanType::IpAddress(IpAddr::V6(addr)) => { writer.write_bytes(&addr.octets()) }, + SanType::OtherName((oid, value)) => { + // otherName SEQUENCE { OID, [0] explicit any defined by oid } + // https://datatracker.ietf.org/doc/html/rfc5280#page-38 + writer.write_sequence(|writer| { + writer.next().write_oid(&ObjectIdentifier::from_slice(&oid)); + value.write_der(writer.next()); + }); + }, }, ); } diff --git a/rcgen/tests/generic.rs b/rcgen/tests/generic.rs index 5aa8ff4e..4b79aa0d 100644 --- a/rcgen/tests/generic.rs +++ b/rcgen/tests/generic.rs @@ -308,3 +308,35 @@ mod test_parse_ia5string_subject { assert_eq!(names, expected_names); } } + +#[cfg(feature = "x509-parser")] +mod test_parse_other_name_alt_name { + use rcgen::{Certificate, CertificateParams, KeyPair, SanType}; + + #[test] + fn parse_other_name_alt_name() { + // Create and serialize a certificate with an alternative name containing an "OtherName". + let mut params = CertificateParams::default(); + let other_name = SanType::OtherName((vec![1, 2, 3, 4], "Foo".into())); + params.subject_alt_names.push(other_name.clone()); + let key_pair = KeyPair::generate().unwrap(); + + let cert = Certificate::generate_self_signed(params, &key_pair).unwrap(); + + let cert_der = cert.der(); + + // We should be able to parse the certificate with x509-parser. + assert!(x509_parser::parse_x509_certificate(cert_der).is_ok()); + + // We should be able to reconstitute params from the DER using x509-parser. + let params_from_cert = CertificateParams::from_ca_cert_der(cert_der).unwrap(); + + // We should find the expected distinguished name in the reconstituted params. + let expected_alt_names = &[&other_name]; + let subject_alt_names = params_from_cert + .subject_alt_names + .iter() + .collect::>(); + assert_eq!(subject_alt_names, expected_alt_names); + } +}