Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic support for Subject Alternative Name OtherName #209

Merged
merged 1 commit into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 {
est31 marked this conversation as resolved.
Show resolved Hide resolved
/// 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 @@
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 @@
x509_parser::extensions::GeneralName::IPAddress(octets) => {
SanType::IpAddress(ip_addr_from_octets(octets)?)
},
x509_parser::extensions::GeneralName::OtherName(oid, value) => {
Tudyx marked this conversation as resolved.
Show resolved Hide resolved
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 @@
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 @@
SanType::IpAddress(IpAddr::V6(addr)) => {
writer.write_bytes(&addr.octets())
},
SanType::OtherName((oid, value)) => {
Tudyx marked this conversation as resolved.
Show resolved Hide resolved
// 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);
}
}
Loading