Skip to content

Commit 28ec9fa

Browse files
authored
Tighten up string type representations to prevent illegal values (#214)
Fix #181 Mainly inspired by the [der](https://docs.rs/der/0.7.8/der/asn1/index.html) and the [asn1_rs](https://github.com/rusticata/asn1-rs/tree/d05f860d07998b4e7f152005ec9a923bb284a708) crates.
1 parent 5139429 commit 28ec9fa

File tree

12 files changed

+786
-76
lines changed

12 files changed

+786
-76
lines changed

rcgen/examples/sign-leaf-with-ca.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ fn main() {
1717
}
1818

1919
fn new_ca() -> Certificate {
20-
let mut params = CertificateParams::new(Vec::default());
20+
let mut params =
21+
CertificateParams::new(Vec::default()).expect("empty subject alt name can't produce error");
2122
let (yesterday, tomorrow) = validity_period();
2223
params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
23-
params
24-
.distinguished_name
25-
.push(DnType::CountryName, PrintableString("BR".into()));
24+
params.distinguished_name.push(
25+
DnType::CountryName,
26+
PrintableString("BR".try_into().unwrap()),
27+
);
2628
params
2729
.distinguished_name
2830
.push(DnType::OrganizationName, "Crab widgits SE");
@@ -39,7 +41,7 @@ fn new_ca() -> Certificate {
3941

4042
fn new_end_entity() -> Certificate {
4143
let name = "entity.other.host";
42-
let mut params = CertificateParams::new(vec![name.into()]);
44+
let mut params = CertificateParams::new(vec![name.into()]).expect("we know the name is valid");
4345
let (yesterday, tomorrow) = validity_period();
4446
params.distinguished_name.push(DnType::CommonName, name);
4547
params.use_authority_key_identifier_extension = true;

rcgen/examples/simple.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
1515
.distinguished_name
1616
.push(DnType::CommonName, "Master Cert");
1717
params.subject_alt_names = vec![
18-
SanType::DnsName("crabs.crabs".to_string()),
19-
SanType::DnsName("localhost".to_string()),
18+
SanType::DnsName("crabs.crabs".try_into()?),
19+
SanType::DnsName("localhost".try_into()?),
2020
];
2121

2222
let key_pair = KeyPair::generate()?;

rcgen/src/error.rs

+33
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub enum Error {
1313
#[cfg(feature = "x509-parser")]
1414
/// Invalid subject alternative name type
1515
InvalidNameType,
16+
/// Invalid ASN.1 string
17+
InvalidAsn1String(InvalidAsn1String),
1618
/// An IP address was provided as a byte array, but the byte array was an invalid length.
1719
InvalidIpAddressOctetLength(usize),
1820
/// There is no support for generating
@@ -55,6 +57,7 @@ impl fmt::Display for Error {
5557
CouldNotParseKeyPair => write!(f, "Could not parse key pair")?,
5658
#[cfg(feature = "x509-parser")]
5759
InvalidNameType => write!(f, "Invalid subject alternative name type")?,
60+
InvalidAsn1String(e) => write!(f, "{}", e)?,
5861
InvalidIpAddressOctetLength(actual) => {
5962
write!(f, "Invalid IP address octet length of {actual} bytes")?
6063
},
@@ -90,6 +93,36 @@ impl fmt::Display for Error {
9093

9194
impl std::error::Error for Error {}
9295

96+
/// Invalid ASN.1 string type
97+
#[derive(Debug, PartialEq, Eq)]
98+
#[non_exhaustive]
99+
pub enum InvalidAsn1String {
100+
/// Invalid PrintableString type
101+
PrintableString(String),
102+
/// Invalid UniversalString type
103+
UniversalString(String),
104+
/// Invalid Ia5String type
105+
Ia5String(String),
106+
/// Invalid TeletexString type
107+
TeletexString(String),
108+
/// Invalid BmpString type
109+
BmpString(String),
110+
}
111+
112+
impl fmt::Display for InvalidAsn1String {
113+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114+
use InvalidAsn1String::*;
115+
match self {
116+
PrintableString(s) => write!(f, "Invalid PrintableString: '{}'", s)?,
117+
Ia5String(s) => write!(f, "Invalid IA5String: '{}'", s)?,
118+
BmpString(s) => write!(f, "Invalid BMPString: '{}'", s)?,
119+
UniversalString(s) => write!(f, "Invalid UniversalString: '{}'", s)?,
120+
TeletexString(s) => write!(f, "Invalid TeletexString: '{}'", s)?,
121+
};
122+
Ok(())
123+
}
124+
}
125+
93126
/// A trait describing an error that can be converted into an `rcgen::Error`.
94127
///
95128
/// We use this trait to avoid leaking external error types into the public API

rcgen/src/lib.rs

+47-33
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,13 @@ pub use crate::crl::{
5656
CrlIssuingDistributionPoint, CrlScope, RevocationReason, RevokedCertParams,
5757
};
5858
pub use crate::csr::{CertificateSigningRequestParams, PublicKey};
59-
pub use crate::error::Error;
59+
pub use crate::error::{Error, InvalidAsn1String};
6060
use crate::key_pair::PublicKeyData;
6161
pub use crate::key_pair::{KeyPair, RemoteKeyPair};
6262
use crate::oid::*;
6363
pub use crate::sign_algo::algo::*;
6464
pub use crate::sign_algo::SignatureAlgorithm;
65+
pub use crate::string_types::*;
6566

6667
/// Type-alias for the old name of [`Error`].
6768
#[deprecated(
@@ -118,7 +119,7 @@ pub fn generate_simple_self_signed(
118119
) -> Result<CertifiedKey, Error> {
119120
let key_pair = KeyPair::generate()?;
120121
let cert =
121-
Certificate::generate_self_signed(CertificateParams::new(subject_alt_names), &key_pair)?;
122+
Certificate::generate_self_signed(CertificateParams::new(subject_alt_names)?, &key_pair)?;
122123
Ok(CertifiedKey { cert, key_pair })
123124
}
124125

@@ -131,6 +132,7 @@ mod key_pair;
131132
mod oid;
132133
mod ring_like;
133134
mod sign_algo;
135+
mod string_types;
134136

135137
// Example certs usable as reference:
136138
// Uses ECDSA: https://crt.sh/?asn1=607203242
@@ -150,9 +152,9 @@ const ENCODE_CONFIG: pem::EncodeConfig = {
150152
/// The type of subject alt name
151153
pub enum SanType {
152154
/// Also known as E-Mail address
153-
Rfc822Name(String),
154-
DnsName(String),
155-
URI(String),
155+
Rfc822Name(Ia5String),
156+
DnsName(Ia5String),
157+
URI(Ia5String),
156158
IpAddress(IpAddr),
157159
}
158160

@@ -172,10 +174,12 @@ impl SanType {
172174
fn try_from_general(name: &x509_parser::extensions::GeneralName<'_>) -> Result<Self, Error> {
173175
Ok(match name {
174176
x509_parser::extensions::GeneralName::RFC822Name(name) => {
175-
SanType::Rfc822Name((*name).into())
177+
SanType::Rfc822Name((*name).try_into()?)
178+
},
179+
x509_parser::extensions::GeneralName::DNSName(name) => {
180+
SanType::DnsName((*name).try_into()?)
176181
},
177-
x509_parser::extensions::GeneralName::DNSName(name) => SanType::DnsName((*name).into()),
178-
x509_parser::extensions::GeneralName::URI(name) => SanType::URI((*name).into()),
182+
x509_parser::extensions::GeneralName::URI(name) => SanType::URI((*name).try_into()?),
179183
x509_parser::extensions::GeneralName::IPAddress(octets) => {
180184
SanType::IpAddress(ip_addr_from_octets(octets)?)
181185
},
@@ -376,15 +380,15 @@ impl DnType {
376380
#[non_exhaustive]
377381
pub enum DnValue {
378382
/// A string encoded using UCS-2
379-
BmpString(Vec<u8>),
383+
BmpString(BmpString),
380384
/// An ASCII string.
381-
Ia5String(String),
385+
Ia5String(Ia5String),
382386
/// An ASCII string containing only A-Z, a-z, 0-9, '()+,-./:=? and `<SPACE>`
383-
PrintableString(String),
387+
PrintableString(PrintableString),
384388
/// A string of characters from the T.61 character set
385-
TeletexString(Vec<u8>),
389+
TeletexString(TeletexString),
386390
/// A string encoded using UTF-32
387-
UniversalString(Vec<u8>),
391+
UniversalString(UniversalString),
388392
/// A string encoded using UTF-8
389393
Utf8String(String),
390394
}
@@ -444,9 +448,9 @@ impl DistinguishedName {
444448
/// # use rcgen::{DistinguishedName, DnType, DnValue};
445449
/// let mut dn = DistinguishedName::new();
446450
/// dn.push(DnType::OrganizationName, "Crab widgits SE");
447-
/// dn.push(DnType::CommonName, DnValue::PrintableString("Master Cert".to_string()));
451+
/// dn.push(DnType::CommonName, DnValue::PrintableString("Master Cert".try_into().unwrap()));
448452
/// assert_eq!(dn.get(&DnType::OrganizationName), Some(&DnValue::Utf8String("Crab widgits SE".to_string())));
449-
/// assert_eq!(dn.get(&DnType::CommonName), Some(&DnValue::PrintableString("Master Cert".to_string())));
453+
/// assert_eq!(dn.get(&DnType::CommonName), Some(&DnValue::PrintableString("Master Cert".try_into().unwrap())));
450454
/// ```
451455
pub fn push(&mut self, ty: DnType, s: impl Into<DnValue>) {
452456
if !self.entries.contains_key(&ty) {
@@ -490,11 +494,13 @@ impl DistinguishedName {
490494
let try_str =
491495
|data| std::str::from_utf8(data).map_err(|_| Error::CouldNotParseCertificate);
492496
let dn_value = match attr.attr_value().header.tag() {
493-
Tag::BmpString => DnValue::BmpString(data.into()),
494-
Tag::Ia5String => DnValue::Ia5String(try_str(data)?.to_owned()),
495-
Tag::PrintableString => DnValue::PrintableString(try_str(data)?.to_owned()),
496-
Tag::T61String => DnValue::TeletexString(data.into()),
497-
Tag::UniversalString => DnValue::UniversalString(data.into()),
497+
Tag::BmpString => DnValue::BmpString(BmpString::from_utf16be(data.to_vec())?),
498+
Tag::Ia5String => DnValue::Ia5String(try_str(data)?.try_into()?),
499+
Tag::PrintableString => DnValue::PrintableString(try_str(data)?.try_into()?),
500+
Tag::T61String => DnValue::TeletexString(try_str(data)?.try_into()?),
501+
Tag::UniversalString => {
502+
DnValue::UniversalString(UniversalString::from_utf32be(data.to_vec())?)
503+
},
498504
Tag::Utf8String => DnValue::Utf8String(try_str(data)?.to_owned()),
499505
_ => return Err(Error::CouldNotParseCertificate),
500506
};
@@ -578,19 +584,21 @@ impl Default for CertificateParams {
578584

579585
impl CertificateParams {
580586
/// Generate certificate parameters with reasonable defaults
581-
pub fn new(subject_alt_names: impl Into<Vec<String>>) -> Self {
587+
pub fn new(subject_alt_names: impl Into<Vec<String>>) -> Result<Self, Error> {
582588
let subject_alt_names = subject_alt_names
583589
.into()
584590
.into_iter()
585-
.map(|s| match s.parse() {
586-
Ok(ip) => SanType::IpAddress(ip),
587-
Err(_) => SanType::DnsName(s),
591+
.map(|s| {
592+
Ok(match IpAddr::from_str(&s) {
593+
Ok(ip) => SanType::IpAddress(ip),
594+
Err(_) => SanType::DnsName(s.try_into()?),
595+
})
588596
})
589-
.collect::<Vec<_>>();
590-
CertificateParams {
597+
.collect::<Result<Vec<_>, _>>()?;
598+
Ok(CertificateParams {
591599
subject_alt_names,
592600
..Default::default()
593-
}
601+
})
594602
}
595603

596604
/// Parses an existing ca certificate from the ASCII PEM format.
@@ -850,7 +858,7 @@ impl CertificateParams {
850858
|writer| match san {
851859
SanType::Rfc822Name(name)
852860
| SanType::DnsName(name)
853-
| SanType::URI(name) => writer.write_ia5_string(name),
861+
| SanType::URI(name) => writer.write_ia5_string(name.as_str()),
854862
SanType::IpAddress(IpAddr::V4(addr)) => {
855863
writer.write_bytes(&addr.octets())
856864
},
@@ -1450,18 +1458,24 @@ fn write_distinguished_name(writer: DERWriter, dn: &DistinguishedName) {
14501458
match content {
14511459
DnValue::BmpString(s) => writer
14521460
.next()
1453-
.write_tagged_implicit(TAG_BMPSTRING, |writer| writer.write_bytes(s)),
1454-
DnValue::Ia5String(s) => writer.next().write_ia5_string(s),
1455-
DnValue::PrintableString(s) => writer.next().write_printable_string(s),
1461+
.write_tagged_implicit(TAG_BMPSTRING, |writer| {
1462+
writer.write_bytes(s.as_bytes())
1463+
}),
1464+
1465+
DnValue::Ia5String(s) => writer.next().write_ia5_string(s.as_str()),
1466+
1467+
DnValue::PrintableString(s) => {
1468+
writer.next().write_printable_string(s.as_str())
1469+
},
14561470
DnValue::TeletexString(s) => writer
14571471
.next()
14581472
.write_tagged_implicit(TAG_TELETEXSTRING, |writer| {
1459-
writer.write_bytes(s)
1473+
writer.write_bytes(s.as_bytes())
14601474
}),
14611475
DnValue::UniversalString(s) => writer
14621476
.next()
14631477
.write_tagged_implicit(TAG_UNIVERSALSTRING, |writer| {
1464-
writer.write_bytes(s)
1478+
writer.write_bytes(s.as_bytes())
14651479
}),
14661480
DnValue::Utf8String(s) => writer.next().write_utf8_string(s),
14671481
}

0 commit comments

Comments
 (0)