Skip to content

Commit

Permalink
add basic support for subject alt name other name
Browse files Browse the repository at this point in the history
  • Loading branch information
Tudyx committed Jan 25, 2024
1 parent 28ec9fa commit 5d0fadf
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 0 deletions.
59 changes: 59 additions & 0 deletions rcgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,29 @@ pub enum SanType {
DnsName(Ia5String),
URI(Ia5String),
IpAddress(IpAddr),
OtherName((Vec<u64>, OtherNameValue)),
}

/// An OtherName value, defined in [RFC 5280§4.1.2.4].
///
/// Technically, this could be any ASN.1 types but for now we cover only UTF-8 strings
/// as it will cover most of use cases, for instance smart cards.
///
/// [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<T> From<T> for OtherNameValue
where
T: Into<String>,
{
fn from(t: T) -> Self {
OtherNameValue::Utf8String(t.into())
}
}

#[cfg(feature = "x509-parser")]
Expand All @@ -172,6 +195,7 @@ fn ip_addr_from_octets(octets: &[u8]) -> Result<IpAddr, Error> {
impl SanType {
#[cfg(feature = "x509-parser")]
fn try_from_general(name: &x509_parser::extensions::GeneralName<'_>) -> Result<Self, Error> {
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()?)
Expand All @@ -183,19 +207,38 @@ 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::<asn1_rs::Any, _, 0>::from_der(&value)
.map_err(|_| Error::CouldNotParseCertificate)?;
let other_name = other_name.into_inner();

let data = other_name.data;
let try_str =
|data| std::str::from_utf8(data).map_err(|_| Error::CouldNotParseCertificate);
let other_name_value = match other_name.tag() {
Tag::Utf8String => OtherNameValue::Utf8String(try_str(data)?.to_owned()),
_ => return Err(Error::CouldNotParseCertificate),

Check warning on line 222 in rcgen/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

rcgen/src/lib.rs#L222

Added line #L222 was not covered by tests
};

SanType::OtherName((oid.collect(), other_name_value))
},
_ => return Err(Error::InvalidNameType),
})
}

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;
const TAG_IP_ADDRESS: u64 = 7;

match self {
Self::OtherName(_oid) => TAG_OTHER_NAME,
SanType::Rfc822Name(_name) => TAG_RFC822_NAME,
SanType::DnsName(_name) => TAG_DNS_NAME,
SanType::URI(_name) => TAG_URI,
Expand Down Expand Up @@ -865,6 +908,22 @@ 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
let oid = ObjectIdentifier::from_slice(&oid);
writer.write_sequence(|writer| {
writer.next().write_oid(&oid);
writer.next().write_tagged(
Tag::context(0),
|writer| match value {
OtherNameValue::Utf8String(s) => {
writer.write_utf8_string(s)
},
},
);
});
},
},
);
}
Expand Down
32 changes: 32 additions & 0 deletions rcgen/tests/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,3 +306,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::<Vec<_>>();
assert_eq!(subject_alt_names, expected_alt_names);
}
}

0 comments on commit 5d0fadf

Please sign in to comment.