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 31, 2024
1 parent 4a49584 commit ab3d3d6
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 0 deletions.
60 changes: 60 additions & 0 deletions rcgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,38 @@ pub enum SanType {
DnsName(Ia5String),
URI(Ia5String),
IpAddress(IpAddr),
OtherName((Vec<u64>, 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<T> From<T> for OtherNameValue
where
T: Into<String>,
{
fn from(t: T) -> Self {
OtherNameValue::Utf8String(t.into())
}
}

#[cfg(feature = "x509-parser")]
Expand All @@ -174,6 +206,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 @@ -185,13 +218,31 @@ 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 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),

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

View check run for this annotation

Codecov / codecov/patch

rcgen/src/lib.rs#L234

Added line #L234 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;
Expand All @@ -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,
}
}
}
Expand Down Expand Up @@ -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());
});
},
},
);
}
Expand Down
32 changes: 32 additions & 0 deletions rcgen/tests/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<_>>();
assert_eq!(subject_alt_names, expected_alt_names);
}
}

0 comments on commit ab3d3d6

Please sign in to comment.