#![cfg_attr(
feature = "pem",
doc = r##"
## Example
```
use rcgen::{generate_simple_self_signed, CertifiedKey};
# fn main () {
// Generate a certificate that's valid for "localhost" and "hello.world.example"
let subject_alt_names = vec!["hello.world.example".to_string(),
"localhost".to_string()];
let CertifiedKey { cert, key_pair } = generate_simple_self_signed(subject_alt_names).unwrap();
println!("{}", cert.pem());
println!("{}", key_pair.serialize_pem());
# }
```"##
)]
#![forbid(unsafe_code)]
#![forbid(non_ascii_idents)]
#![deny(missing_docs)]
#![allow(clippy::complexity, clippy::style, clippy::pedantic)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#![warn(unreachable_pub)]
use std::collections::HashMap;
use std::fmt;
use std::hash::Hash;
use std::net::IpAddr;
#[cfg(feature = "x509-parser")]
use std::net::{Ipv4Addr, Ipv6Addr};
use time::{OffsetDateTime, Time};
use yasna::models::ObjectIdentifier;
use yasna::models::{GeneralizedTime, UTCTime};
use yasna::tags::{TAG_BMPSTRING, TAG_TELETEXSTRING, TAG_UNIVERSALSTRING};
use yasna::DERWriter;
use yasna::Tag;
pub use certificate::{
date_time_ymd, Attribute, BasicConstraints, Certificate, CertificateParams, CidrSubnet,
CustomExtension, DnType, ExtendedKeyUsagePurpose, GeneralSubtree, IsCa, NameConstraints,
};
pub use crl::{
CertificateRevocationList, CertificateRevocationListParams, CrlDistributionPoint,
CrlIssuingDistributionPoint, CrlScope, RevocationReason, RevokedCertParams,
};
pub use csr::{CertificateSigningRequest, CertificateSigningRequestParams, PublicKey};
pub use error::{Error, InvalidAsn1String};
pub use key_pair::PublicKeyData;
#[cfg(all(feature = "crypto", feature = "aws_lc_rs"))]
pub use key_pair::RsaKeySize;
pub use key_pair::{KeyPair, RemoteKeyPair, SubjectPublicKeyInfo};
#[cfg(feature = "crypto")]
use ring_like::digest;
pub use sign_algo::algo::*;
pub use sign_algo::SignatureAlgorithm;
pub use string_types::*;
mod certificate;
mod crl;
mod csr;
mod error;
mod key_pair;
mod oid;
mod ring_like;
mod sign_algo;
mod string_types;
#[deprecated(
note = "Renamed to `Error`. We recommend to refer to it by fully-qualifying the crate: `rcgen::Error`."
)]
pub type RcgenError = Error;
pub struct CertifiedKey {
pub cert: Certificate,
pub key_pair: KeyPair,
}
#[cfg(feature = "crypto")]
#[cfg_attr(
feature = "pem",
doc = r##"
## Example
```
use rcgen::{generate_simple_self_signed, CertifiedKey};
# fn main () {
// Generate a certificate that's valid for "localhost" and "hello.world.example"
let subject_alt_names = vec!["hello.world.example".to_string(),
"localhost".to_string()];
let CertifiedKey { cert, key_pair } = generate_simple_self_signed(subject_alt_names).unwrap();
// The certificate is now valid for localhost and the domain "hello.world.example"
println!("{}", cert.pem());
println!("{}", key_pair.serialize_pem());
# }
```
"##
)]
pub fn generate_simple_self_signed(
subject_alt_names: impl Into<Vec<String>>,
) -> Result<CertifiedKey, Error> {
let key_pair = KeyPair::generate()?;
let cert = CertificateParams::new(subject_alt_names)?.self_signed(&key_pair)?;
Ok(CertifiedKey { cert, key_pair })
}
struct Issuer<'a> {
distinguished_name: &'a DistinguishedName,
key_identifier_method: &'a KeyIdMethod,
key_usages: &'a [KeyUsagePurpose],
key_pair: &'a KeyPair,
}
#[cfg(feature = "pem")]
const ENCODE_CONFIG: pem::EncodeConfig = {
let line_ending = match cfg!(target_family = "windows") {
true => pem::LineEnding::CRLF,
false => pem::LineEnding::LF,
};
pem::EncodeConfig::new().set_line_ending(line_ending)
};
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[allow(missing_docs)]
#[non_exhaustive]
pub enum SanType {
Rfc822Name(Ia5String),
DnsName(Ia5String),
URI(Ia5String),
IpAddress(IpAddr),
OtherName((Vec<u64>, OtherNameValue)),
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[non_exhaustive]
pub enum OtherNameValue {
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")]
fn ip_addr_from_octets(octets: &[u8]) -> Result<IpAddr, Error> {
if let Ok(ipv6_octets) = <&[u8; 16]>::try_from(octets) {
Ok(Ipv6Addr::from(*ipv6_octets).into())
} else if let Ok(ipv4_octets) = <&[u8; 4]>::try_from(octets) {
Ok(Ipv4Addr::from(*ipv4_octets).into())
} else {
Err(Error::InvalidIpAddressOctetLength(octets.len()))
}
}
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()?)
},
x509_parser::extensions::GeneralName::DNSName(name) => {
SanType::DnsName((*name).try_into()?)
},
x509_parser::extensions::GeneralName::URI(name) => SanType::URI((*name).try_into()?),
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)?;
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),
};
SanType::OtherName((oid.collect(), other_name_value))
},
_ => return Err(Error::InvalidNameType),
})
}
fn tag(&self) -> u64 {
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 {
SanType::Rfc822Name(_name) => TAG_RFC822_NAME,
SanType::DnsName(_name) => TAG_DNS_NAME,
SanType::URI(_name) => TAG_URI,
SanType::IpAddress(_addr) => TAG_IP_ADDRESS,
Self::OtherName(_oid) => TAG_OTHER_NAME,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[non_exhaustive]
pub enum DnValue {
BmpString(BmpString),
Ia5String(Ia5String),
PrintableString(PrintableString),
TeletexString(TeletexString),
UniversalString(UniversalString),
Utf8String(String),
}
impl<T> From<T> for DnValue
where
T: Into<String>,
{
fn from(t: T) -> Self {
DnValue::Utf8String(t.into())
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct DistinguishedName {
entries: HashMap<DnType, DnValue>,
order: Vec<DnType>,
}
impl DistinguishedName {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self, ty: &DnType) -> Option<&DnValue> {
self.entries.get(ty)
}
pub fn remove(&mut self, ty: DnType) -> bool {
let removed = self.entries.remove(&ty).is_some();
if removed {
self.order.retain(|ty_o| &ty != ty_o);
}
removed
}
pub fn push(&mut self, ty: DnType, s: impl Into<DnValue>) {
if !self.entries.contains_key(&ty) {
self.order.push(ty.clone());
}
self.entries.insert(ty, s.into());
}
pub fn iter(&self) -> DistinguishedNameIterator<'_> {
DistinguishedNameIterator {
distinguished_name: self,
iter: self.order.iter(),
}
}
#[cfg(feature = "x509-parser")]
fn from_name(name: &x509_parser::x509::X509Name) -> Result<Self, Error> {
use x509_parser::der_parser::asn1_rs::Tag;
let mut dn = DistinguishedName::new();
for rdn in name.iter() {
let mut rdn_iter = rdn.iter();
let dn_opt = rdn_iter.next();
let attr = if let Some(dn) = dn_opt {
if rdn_iter.next().is_some() {
return Err(Error::CouldNotParseCertificate);
} else {
dn
}
} else {
panic!("x509-parser distinguished name set is empty");
};
let attr_type_oid = attr
.attr_type()
.iter()
.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(BmpString::from_utf16be(data.to_vec())?),
Tag::Ia5String => DnValue::Ia5String(try_str(data)?.try_into()?),
Tag::PrintableString => DnValue::PrintableString(try_str(data)?.try_into()?),
Tag::T61String => DnValue::TeletexString(try_str(data)?.try_into()?),
Tag::UniversalString => {
DnValue::UniversalString(UniversalString::from_utf32be(data.to_vec())?)
},
Tag::Utf8String => DnValue::Utf8String(try_str(data)?.to_owned()),
_ => return Err(Error::CouldNotParseCertificate),
};
dn.push(dn_type, dn_value);
}
Ok(dn)
}
}
pub struct DistinguishedNameIterator<'a> {
distinguished_name: &'a DistinguishedName,
iter: std::slice::Iter<'a, DnType>,
}
impl<'a> Iterator for DistinguishedNameIterator<'a> {
type Item = (&'a DnType, &'a DnValue);
fn next(&mut self) -> Option<Self::Item> {
self.iter
.next()
.and_then(|ty| self.distinguished_name.entries.get(ty).map(|v| (ty, v)))
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum KeyUsagePurpose {
DigitalSignature,
ContentCommitment,
KeyEncipherment,
DataEncipherment,
KeyAgreement,
KeyCertSign,
CrlSign,
EncipherOnly,
DecipherOnly,
}
impl KeyUsagePurpose {
fn to_u16(&self) -> u16 {
const FLAG: u16 = 0b1000_0000_0000_0000;
FLAG >> match self {
KeyUsagePurpose::DigitalSignature => 0,
KeyUsagePurpose::ContentCommitment => 1,
KeyUsagePurpose::KeyEncipherment => 2,
KeyUsagePurpose::DataEncipherment => 3,
KeyUsagePurpose::KeyAgreement => 4,
KeyUsagePurpose::KeyCertSign => 5,
KeyUsagePurpose::CrlSign => 6,
KeyUsagePurpose::EncipherOnly => 7,
KeyUsagePurpose::DecipherOnly => 8,
}
}
#[cfg(feature = "x509-parser")]
fn from_u16(value: u16) -> Vec<Self> {
[
KeyUsagePurpose::DigitalSignature,
KeyUsagePurpose::ContentCommitment,
KeyUsagePurpose::KeyEncipherment,
KeyUsagePurpose::DataEncipherment,
KeyUsagePurpose::KeyAgreement,
KeyUsagePurpose::KeyCertSign,
KeyUsagePurpose::CrlSign,
KeyUsagePurpose::EncipherOnly,
KeyUsagePurpose::DecipherOnly,
]
.iter()
.filter_map(|key_usage| {
let present = key_usage.to_u16() & value != 0;
present.then_some(*key_usage)
})
.collect()
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[non_exhaustive]
pub enum KeyIdMethod {
#[cfg(feature = "crypto")]
Sha256,
#[cfg(feature = "crypto")]
Sha384,
#[cfg(feature = "crypto")]
Sha512,
PreSpecified(Vec<u8>),
}
impl KeyIdMethod {
#[allow(unused_variables)]
pub(crate) fn derive(&self, subject_public_key_info: impl AsRef<[u8]>) -> Vec<u8> {
let digest_method = match &self {
#[cfg(feature = "crypto")]
Self::Sha256 => &digest::SHA256,
#[cfg(feature = "crypto")]
Self::Sha384 => &digest::SHA384,
#[cfg(feature = "crypto")]
Self::Sha512 => &digest::SHA512,
Self::PreSpecified(b) => {
return b.to_vec();
},
};
#[cfg(feature = "crypto")]
{
let digest = digest::digest(digest_method, subject_public_key_info.as_ref());
digest.as_ref()[0..20].to_vec()
}
}
}
fn dt_strip_nanos(dt: OffsetDateTime) -> OffsetDateTime {
let time =
Time::from_hms(dt.hour(), dt.minute(), dt.second()).expect("invalid or out-of-range time");
dt.replace_time(time)
}
fn dt_to_generalized(dt: OffsetDateTime) -> GeneralizedTime {
let date_time = dt_strip_nanos(dt);
GeneralizedTime::from_datetime(date_time)
}
fn write_dt_utc_or_generalized(writer: DERWriter, dt: OffsetDateTime) {
if (1950..2050).contains(&dt.year()) {
let date_time = dt_strip_nanos(dt);
let ut = UTCTime::from_datetime(date_time);
writer.write_utctime(&ut);
} else {
let gt = dt_to_generalized(dt);
writer.write_generalized_time(>);
}
}
fn write_distinguished_name(writer: DERWriter, dn: &DistinguishedName) {
writer.write_sequence(|writer| {
for (ty, content) in dn.iter() {
writer.next().write_set(|writer| {
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.as_bytes())
}),
DnValue::Ia5String(s) => writer.next().write_ia5_string(s.as_str()),
DnValue::PrintableString(s) => {
writer.next().write_printable_string(s.as_str())
},
DnValue::TeletexString(s) => writer
.next()
.write_tagged_implicit(TAG_TELETEXSTRING, |writer| {
writer.write_bytes(s.as_bytes())
}),
DnValue::UniversalString(s) => writer
.next()
.write_tagged_implicit(TAG_UNIVERSALSTRING, |writer| {
writer.write_bytes(s.as_bytes())
}),
DnValue::Utf8String(s) => writer.next().write_utf8_string(s),
}
});
});
}
});
}
fn write_x509_extension(
writer: DERWriter,
extension_oid: &[u64],
is_critical: bool,
value_serializer: impl FnOnce(DERWriter),
) {
writer.write_sequence(|writer| {
let oid = ObjectIdentifier::from_slice(extension_oid);
writer.next().write_oid(&oid);
if is_critical {
writer.next().write_bool(true);
}
let bytes = yasna::construct_der(value_serializer);
writer.next().write_bytes(&bytes);
})
}
fn write_x509_authority_key_identifier(writer: DERWriter, aki: Vec<u8>) {
write_x509_extension(writer, oid::AUTHORITY_KEY_IDENTIFIER, false, |writer| {
writer.write_sequence(|writer| {
writer
.next()
.write_tagged_implicit(Tag::context(0), |writer| writer.write_bytes(&aki))
});
});
}
#[cfg(feature = "zeroize")]
impl zeroize::Zeroize for KeyPair {
fn zeroize(&mut self) {
self.serialized_der.zeroize();
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct SerialNumber {
inner: Vec<u8>,
}
impl SerialNumber {
pub fn from_slice(bytes: &[u8]) -> SerialNumber {
let inner = bytes.to_vec();
SerialNumber { inner }
}
pub fn to_bytes(&self) -> Vec<u8> {
self.inner.clone()
}
pub fn len(&self) -> usize {
self.inner.len()
}
}
impl fmt::Display for SerialNumber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
let hex: Vec<_> = self.inner.iter().map(|b| format!("{:02x}", b)).collect();
write!(f, "{}", hex.join(":"))
}
}
impl From<Vec<u8>> for SerialNumber {
fn from(inner: Vec<u8>) -> SerialNumber {
SerialNumber { inner }
}
}
impl From<u64> for SerialNumber {
fn from(u: u64) -> SerialNumber {
let inner = u.to_be_bytes().into();
SerialNumber { inner }
}
}
impl AsRef<[u8]> for SerialNumber {
fn as_ref(&self) -> &[u8] {
&self.inner
}
}
#[cfg(test)]
mod tests {
use std::panic::catch_unwind;
use time::{Date, Month, PrimitiveDateTime};
use super::*;
fn times() -> [OffsetDateTime; 2] {
let dt_nanos = {
let date = Date::from_calendar_date(2020, Month::December, 3).unwrap();
let time = Time::from_hms_nano(0, 0, 1, 444).unwrap();
PrimitiveDateTime::new(date, time).assume_utc()
};
let dt_zero = {
let date = Date::from_calendar_date(2020, Month::December, 3).unwrap();
let time = Time::from_hms_nano(0, 0, 1, 0).unwrap();
PrimitiveDateTime::new(date, time).assume_utc()
};
[dt_nanos, dt_zero]
}
#[test]
fn test_dt_utc_strip_nanos() {
let times = times();
let res = catch_unwind(|| UTCTime::from_datetime(times[0]));
assert!(res.is_err());
for dt in times {
let date_time = dt_strip_nanos(dt);
assert_eq!(date_time.time().nanosecond(), 0);
let _ut = UTCTime::from_datetime(date_time);
}
}
#[test]
fn test_dt_to_generalized() {
let times = times();
for dt in times {
let _gt = dt_to_generalized(dt);
}
}
#[test]
fn signature_algos_different() {
for (i, alg_i) in SignatureAlgorithm::iter().enumerate() {
for (j, alg_j) in SignatureAlgorithm::iter().enumerate() {
assert_eq!(
alg_i == alg_j,
i == j,
"Algorighm relationship mismatch for algorithm index pair {} and {}",
i,
j
);
}
}
}
#[cfg(feature = "x509-parser")]
mod test_ip_address_from_octets {
use super::super::ip_addr_from_octets;
use super::super::Error;
use std::net::IpAddr;
#[test]
fn ipv4() {
let octets = [10, 20, 30, 40];
let actual = ip_addr_from_octets(&octets).unwrap();
assert_eq!(IpAddr::from(octets), actual)
}
#[test]
fn ipv6() {
let octets = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let actual = ip_addr_from_octets(&octets).unwrap();
assert_eq!(IpAddr::from(octets), actual)
}
#[test]
fn mismatch() {
let incorrect = Vec::from_iter(0..10);
let actual = ip_addr_from_octets(&incorrect).unwrap_err();
assert_eq!(Error::InvalidIpAddressOctetLength(10), actual);
}
#[test]
fn none() {
let actual = ip_addr_from_octets(&[]).unwrap_err();
assert_eq!(Error::InvalidIpAddressOctetLength(0), actual);
}
#[test]
fn too_many() {
let incorrect = Vec::from_iter(0..20);
let actual = ip_addr_from_octets(&incorrect).unwrap_err();
assert_eq!(Error::InvalidIpAddressOctetLength(20), actual);
}
}
#[cfg(feature = "x509-parser")]
mod test_san_type_from_general_name {
use crate::SanType;
use std::net::IpAddr;
use x509_parser::extensions::GeneralName;
#[test]
fn with_ipv4() {
let octets = [1, 2, 3, 4];
let value = GeneralName::IPAddress(&octets);
let actual = SanType::try_from_general(&value).unwrap();
assert_eq!(SanType::IpAddress(IpAddr::from(octets)), actual);
}
}
}