Skip to content

Commit

Permalink
basic Ia5String support for DistinguishedName values (#182)
Browse files Browse the repository at this point in the history
This branch adds basic support emitting and parsing distinguished name
values that are Ia5Strings. For example, email address attributes in a
certificate subject distinguished name.

Note that because of #181 this code will panic when emitting invalid
Ia5String values. This problem is general to rcgen's handling of ASN.1
string types and so isn't addressed with additional care in this branch.
A broader rework is required.

Along the way I also fixed a warning from
#176 related to where we were
defining the custom `profile.dev.package.num-bigint-dig` profile
metadata.

Resolves #180
  • Loading branch information
cpu authored Oct 30, 2023
1 parent 20ee1f6 commit 58febf6
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 55 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ version = "0.11.3"
description = "Rust X.509 certificate generator"
repository = "https://github.com/rustls/rcgen"
keywords = ["mkcert", "ca", "certificate"]

# This greatly speeds up rsa key generation times
# (only applies to the dev-dependency of rcgen because cargo
# ignores profile overrides for non leaf packages)
[profile.dev.package.num-bigint-dig]
opt-level = 3
6 changes: 0 additions & 6 deletions rcgen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,3 @@ rustls-webpki = { version = "0.101.0", features = ["std"] }
botan = { version = "0.10", features = ["vendored"] }
rand = "0.8"
rsa = "0.9"

# This greatly speeds up rsa key generation times
# (only applies to the dev-dependency because cargo
# ignores profile overrides for non leaf packages)
[profile.dev.package.num-bigint-dig]
opt-level = 3
67 changes: 18 additions & 49 deletions rcgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,16 +361,18 @@ impl DnType {
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[non_exhaustive]
pub enum DnValue {
/// A string of characters from the T.61 character set
TeletexString(Vec<u8>),
/// A string encoded using UCS-2
BmpString(Vec<u8>),
/// An ASCII string.
Ia5String(String),
/// An ASCII string containing only A-Z, a-z, 0-9, '()+,-./:=? and `<SPACE>`
PrintableString(String),
/// A string of characters from the T.61 character set
TeletexString(Vec<u8>),
/// A string encoded using UTF-32
UniversalString(Vec<u8>),
/// A string encoded using UTF-8
Utf8String(String),
/// A string encoded using UCS-2
BmpString(Vec<u8>),
}

impl<T> From<T> for DnValue
Expand Down Expand Up @@ -471,20 +473,15 @@ impl DistinguishedName {
.ok_or(Error::CouldNotParseCertificate)?;
let dn_type = DnType::from_oid(&attr_type_oid.collect::<Vec<_>>());
let data = attr.attr_value().data;
let try_str =
|data| std::str::from_utf8(data).map_err(|_| Error::CouldNotParseCertificate);
let dn_value = match attr.attr_value().header.tag() {
Tag::BmpString => DnValue::BmpString(data.into()),
Tag::Ia5String => DnValue::Ia5String(try_str(data)?.to_owned()),
Tag::PrintableString => DnValue::PrintableString(try_str(data)?.to_owned()),
Tag::T61String => DnValue::TeletexString(data.into()),
Tag::PrintableString => {
let data =
std::str::from_utf8(data).map_err(|_| Error::CouldNotParseCertificate)?;
DnValue::PrintableString(data.to_owned())
},
Tag::UniversalString => DnValue::UniversalString(data.into()),
Tag::Utf8String => {
let data =
std::str::from_utf8(data).map_err(|_| Error::CouldNotParseCertificate)?;
DnValue::Utf8String(data.to_owned())
},
Tag::BmpString => DnValue::BmpString(data.into()),
Tag::Utf8String => DnValue::Utf8String(try_str(data)?.to_owned()),
_ => return Err(Error::CouldNotParseCertificate),
};

Expand Down Expand Up @@ -879,36 +876,7 @@ impl CertificateParams {
// Write version
writer.next().write_u8(0);
// Write issuer
writer.next().write_sequence(|writer| {
for (ty, content) in distinguished_name.iter() {
writer.next().write_set(|writer| {
writer.next().write_sequence(|writer| {
writer.next().write_oid(&ty.to_oid());
match content {
DnValue::TeletexString(s) => writer
.next()
.write_tagged_implicit(TAG_TELETEXSTRING, |writer| {
writer.write_bytes(s)
}),
DnValue::PrintableString(s) => {
writer.next().write_printable_string(s)
},
DnValue::UniversalString(s) => writer
.next()
.write_tagged_implicit(TAG_UNIVERSALSTRING, |writer| {
writer.write_bytes(s)
}),
DnValue::Utf8String(s) => writer.next().write_utf8_string(s),
DnValue::BmpString(s) => writer
.next()
.write_tagged_implicit(TAG_BMPSTRING, |writer| {
writer.write_bytes(s)
}),
}
});
});
}
});
write_distinguished_name(writer.next(), &distinguished_name);
// Write subjectPublicKeyInfo
pub_key.serialize_public_key_der(writer.next());
// Write extensions
Expand Down Expand Up @@ -1440,21 +1408,22 @@ fn write_distinguished_name(writer: DERWriter, dn: &DistinguishedName) {
writer.next().write_sequence(|writer| {
writer.next().write_oid(&ty.to_oid());
match content {
DnValue::BmpString(s) => writer
.next()
.write_tagged_implicit(TAG_BMPSTRING, |writer| writer.write_bytes(s)),
DnValue::Ia5String(s) => writer.next().write_ia5_string(s),
DnValue::PrintableString(s) => writer.next().write_printable_string(s),
DnValue::TeletexString(s) => writer
.next()
.write_tagged_implicit(TAG_TELETEXSTRING, |writer| {
writer.write_bytes(s)
}),
DnValue::PrintableString(s) => writer.next().write_printable_string(s),
DnValue::UniversalString(s) => writer
.next()
.write_tagged_implicit(TAG_UNIVERSALSTRING, |writer| {
writer.write_bytes(s)
}),
DnValue::Utf8String(s) => writer.next().write_utf8_string(s),
DnValue::BmpString(s) => writer
.next()
.write_tagged_implicit(TAG_BMPSTRING, |writer| writer.write_bytes(s)),
}
});
});
Expand Down
37 changes: 37 additions & 0 deletions rcgen/tests/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,3 +299,40 @@ mod test_parse_crl_dps {
);
}
}

#[cfg(feature = "x509-parser")]
mod test_parse_ia5string_subject {
use crate::util;
use rcgen::DnType::CustomDnType;
use rcgen::{Certificate, CertificateParams, DistinguishedName, DnValue, KeyPair};

#[test]
fn parse_ia5string_subject() {
// Create and serialize a certificate with a subject containing an IA5String email address.
let email_address_dn_type = CustomDnType(vec![1, 2, 840, 113549, 1, 9, 1]); // id-emailAddress
let email_address_dn_value = DnValue::Ia5String("[email protected]".into());
let mut params = util::default_params();
params.distinguished_name = DistinguishedName::new();
params.distinguished_name.push(
email_address_dn_type.clone(),
email_address_dn_value.clone(),
);
let cert = Certificate::from_params(params).unwrap();
let cert_der = cert.serialize_der().unwrap();

// 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 key_pair = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap();
let params_from_cert = CertificateParams::from_ca_cert_der(&cert_der, key_pair).unwrap();

// We should find the expected distinguished name in the reconstituted params.
let expected_names = &[(&email_address_dn_type, &email_address_dn_value)];
let names = params_from_cert
.distinguished_name
.iter()
.collect::<Vec<(_, _)>>();
assert_eq!(names, expected_names);
}
}

0 comments on commit 58febf6

Please sign in to comment.