#![cfg_attr(
feature = "pem",
doc = r##"
## Example
```
extern crate rcgen;
use rcgen::generate_simple_self_signed;
# 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 cert = generate_simple_self_signed(subject_alt_names).unwrap();
println!("{}", cert.serialize_pem().unwrap());
println!("{}", cert.serialize_private_key_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))]
#[cfg(feature = "pem")]
use pem::Pem;
use ring::digest;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt;
use std::hash::Hash;
use std::net::IpAddr;
#[cfg(feature = "x509-parser")]
use std::net::{Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
use time::{Date, Month, OffsetDateTime, PrimitiveDateTime, 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 crate::crl::{
CertificateRevocationList, CertificateRevocationListParams, CrlDistributionPoint,
CrlIssuingDistributionPoint, CrlScope, RevocationReason, RevokedCertParams,
};
pub use crate::csr::{CertificateSigningRequest, PublicKey};
pub use crate::error::RcgenError;
use crate::key_pair::PublicKeyData;
pub use crate::key_pair::{KeyPair, RemoteKeyPair};
use crate::oid::*;
pub use crate::sign_algo::algo::*;
pub use crate::sign_algo::SignatureAlgorithm;
pub struct Certificate {
params: CertificateParams,
key_pair: KeyPair,
}
#[cfg_attr(
feature = "pem",
doc = r##"
## Example
```
extern crate rcgen;
use rcgen::generate_simple_self_signed;
# fn main () {
let subject_alt_names :&[_] = &["hello.world.example".to_string(),
"localhost".to_string()];
let cert = generate_simple_self_signed(subject_alt_names).unwrap();
// The certificate is now valid for localhost and the domain "hello.world.example"
println!("{}", cert.serialize_pem().unwrap());
println!("{}", cert.serialize_private_key_pem());
# }
```
"##
)]
pub fn generate_simple_self_signed(
subject_alt_names: impl Into<Vec<String>>,
) -> Result<Certificate, RcgenError> {
Certificate::from_params(CertificateParams::new(subject_alt_names))
}
mod crl;
mod csr;
mod error;
mod key_pair;
mod oid;
mod sign_algo;
#[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(String),
DnsName(String),
URI(String),
IpAddress(IpAddr),
}
#[cfg(feature = "x509-parser")]
fn ip_addr_from_octets(octets: &[u8]) -> Result<IpAddr, RcgenError> {
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(RcgenError::InvalidIpAddressOctetLength(octets.len()))
}
}
impl SanType {
#[cfg(feature = "x509-parser")]
fn try_from_general(
name: &x509_parser::extensions::GeneralName<'_>,
) -> Result<Self, RcgenError> {
Ok(match name {
x509_parser::extensions::GeneralName::RFC822Name(name) => {
SanType::Rfc822Name((*name).into())
},
x509_parser::extensions::GeneralName::DNSName(name) => SanType::DnsName((*name).into()),
x509_parser::extensions::GeneralName::URI(name) => SanType::URI((*name).into()),
x509_parser::extensions::GeneralName::IPAddress(octets) => {
SanType::IpAddress(ip_addr_from_octets(octets)?)
},
_ => return Err(RcgenError::InvalidNameType),
})
}
fn tag(&self) -> u64 {
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,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[allow(missing_docs)]
#[non_exhaustive]
pub enum GeneralSubtree {
Rfc822Name(String),
DnsName(String),
DirectoryName(DistinguishedName),
IpAddress(CidrSubnet),
}
impl GeneralSubtree {
fn tag(&self) -> u64 {
const TAG_RFC822_NAME: u64 = 1;
const TAG_DNS_NAME: u64 = 2;
const TAG_DIRECTORY_NAME: u64 = 4;
const TAG_IP_ADDRESS: u64 = 7;
match self {
GeneralSubtree::Rfc822Name(_name) => TAG_RFC822_NAME,
GeneralSubtree::DnsName(_name) => TAG_DNS_NAME,
GeneralSubtree::DirectoryName(_name) => TAG_DIRECTORY_NAME,
GeneralSubtree::IpAddress(_addr) => TAG_IP_ADDRESS,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[allow(missing_docs)]
pub enum CidrSubnet {
V4([u8; 4], [u8; 4]),
V6([u8; 16], [u8; 16]),
}
macro_rules! mask {
($t:ty, $d:expr) => {{
let v = <$t>::max_value();
let v = v.checked_shr($d as u32).unwrap_or(0);
(!v).to_be_bytes()
}};
}
impl CidrSubnet {
pub fn from_str(s: &str) -> Result<Self, ()> {
let mut iter = s.split('/');
if let (Some(addr_s), Some(prefix_s)) = (iter.next(), iter.next()) {
let addr = IpAddr::from_str(addr_s).map_err(|_| ())?;
let prefix = u8::from_str(prefix_s).map_err(|_| ())?;
Ok(Self::from_addr_prefix(addr, prefix))
} else {
Err(())
}
}
pub fn from_addr_prefix(addr: IpAddr, prefix: u8) -> Self {
match addr {
IpAddr::V4(addr) => Self::from_v4_prefix(addr.octets(), prefix),
IpAddr::V6(addr) => Self::from_v6_prefix(addr.octets(), prefix),
}
}
pub fn from_v4_prefix(addr: [u8; 4], prefix: u8) -> Self {
CidrSubnet::V4(addr, mask!(u32, prefix))
}
pub fn from_v6_prefix(addr: [u8; 16], prefix: u8) -> Self {
CidrSubnet::V6(addr, mask!(u128, prefix))
}
fn to_bytes(&self) -> Vec<u8> {
let mut res = Vec::new();
match self {
CidrSubnet::V4(addr, mask) => {
res.extend_from_slice(addr);
res.extend_from_slice(mask);
},
CidrSubnet::V6(addr, mask) => {
res.extend_from_slice(addr);
res.extend_from_slice(mask);
},
}
res
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[non_exhaustive]
pub enum DnType {
CountryName,
LocalityName,
StateOrProvinceName,
OrganizationName,
OrganizationalUnitName,
CommonName,
CustomDnType(Vec<u64>),
}
impl DnType {
fn to_oid(&self) -> ObjectIdentifier {
let sl = match self {
DnType::CountryName => OID_COUNTRY_NAME,
DnType::LocalityName => OID_LOCALITY_NAME,
DnType::StateOrProvinceName => OID_STATE_OR_PROVINCE_NAME,
DnType::OrganizationName => OID_ORG_NAME,
DnType::OrganizationalUnitName => OID_ORG_UNIT_NAME,
DnType::CommonName => OID_COMMON_NAME,
DnType::CustomDnType(ref oid) => oid.as_slice(),
};
ObjectIdentifier::from_slice(sl)
}
pub fn from_oid(slice: &[u64]) -> Self {
match slice {
OID_COUNTRY_NAME => DnType::CountryName,
OID_LOCALITY_NAME => DnType::LocalityName,
OID_STATE_OR_PROVINCE_NAME => DnType::StateOrProvinceName,
OID_ORG_NAME => DnType::OrganizationName,
OID_ORG_UNIT_NAME => DnType::OrganizationalUnitName,
OID_COMMON_NAME => DnType::CommonName,
oid => DnType::CustomDnType(oid.into()),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[non_exhaustive]
pub enum DnValue {
TeletexString(Vec<u8>),
PrintableString(String),
UniversalString(Vec<u8>),
Utf8String(String),
BmpString(Vec<u8>),
}
impl<T> From<T> for DnValue
where
T: Into<String>,
{
fn from(t: T) -> Self {
DnValue::Utf8String(t.into())
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DistinguishedName {
entries: HashMap<DnType, DnValue>,
order: Vec<DnType>,
}
impl DistinguishedName {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
order: Vec::new(),
}
}
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, RcgenError> {
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(RcgenError::CouldNotParseCertificate);
} else {
dn
}
} else {
panic!("x509-parser distinguished name set is empty");
};
let attr_type_oid = attr
.attr_type()
.iter()
.ok_or(RcgenError::CouldNotParseCertificate)?;
let dn_type = DnType::from_oid(&attr_type_oid.collect::<Vec<_>>());
let data = attr.attr_value().data;
let dn_value = match attr.attr_value().header.tag() {
Tag::T61String => DnValue::TeletexString(data.into()),
Tag::PrintableString => {
let data = std::str::from_utf8(data)
.map_err(|_| RcgenError::CouldNotParseCertificate)?;
DnValue::PrintableString(data.to_owned())
},
Tag::UniversalString => DnValue::UniversalString(data.into()),
Tag::Utf8String => {
let data = std::str::from_utf8(data)
.map_err(|_| RcgenError::CouldNotParseCertificate)?;
DnValue::Utf8String(data.to_owned())
},
Tag::BmpString => DnValue::BmpString(data.into()),
_ => return Err(RcgenError::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)))
}
}
#[allow(missing_docs)]
#[non_exhaustive]
pub struct CertificateParams {
pub alg: &'static SignatureAlgorithm,
pub not_before: OffsetDateTime,
pub not_after: OffsetDateTime,
pub serial_number: Option<SerialNumber>,
pub subject_alt_names: Vec<SanType>,
pub distinguished_name: DistinguishedName,
pub is_ca: IsCa,
pub key_usages: Vec<KeyUsagePurpose>,
pub extended_key_usages: Vec<ExtendedKeyUsagePurpose>,
pub name_constraints: Option<NameConstraints>,
pub crl_distribution_points: Vec<CrlDistributionPoint>,
pub custom_extensions: Vec<CustomExtension>,
pub key_pair: Option<KeyPair>,
pub use_authority_key_identifier_extension: bool,
pub key_identifier_method: KeyIdMethod,
}
impl Default for CertificateParams {
fn default() -> Self {
let not_before = date_time_ymd(1975, 01, 01);
let not_after = date_time_ymd(4096, 01, 01);
let mut distinguished_name = DistinguishedName::new();
distinguished_name.push(DnType::CommonName, "rcgen self signed cert");
CertificateParams {
alg: &PKCS_ECDSA_P256_SHA256,
not_before,
not_after,
serial_number: None,
subject_alt_names: Vec::new(),
distinguished_name,
is_ca: IsCa::NoCa,
key_usages: Vec::new(),
extended_key_usages: Vec::new(),
name_constraints: None,
crl_distribution_points: Vec::new(),
custom_extensions: Vec::new(),
key_pair: None,
use_authority_key_identifier_extension: false,
key_identifier_method: KeyIdMethod::Sha256,
}
}
}
impl CertificateParams {
#[cfg(all(feature = "pem", feature = "x509-parser"))]
pub fn from_ca_cert_pem(pem_str: &str, key_pair: KeyPair) -> Result<Self, RcgenError> {
let certificate = pem::parse(pem_str).or(Err(RcgenError::CouldNotParseCertificate))?;
Self::from_ca_cert_der(certificate.contents(), key_pair)
}
#[cfg(feature = "x509-parser")]
pub fn from_ca_cert_der(ca_cert: &[u8], key_pair: KeyPair) -> Result<Self, RcgenError> {
let (_remainder, x509) = x509_parser::parse_x509_certificate(ca_cert)
.or(Err(RcgenError::CouldNotParseCertificate))?;
let alg_oid = x509
.signature_algorithm
.algorithm
.iter()
.ok_or(RcgenError::CouldNotParseCertificate)?;
let alg = SignatureAlgorithm::from_oid(&alg_oid.collect::<Vec<_>>())?;
let dn = DistinguishedName::from_name(&x509.tbs_certificate.subject)?;
let is_ca = Self::convert_x509_is_ca(&x509)?;
let validity = x509.validity();
let subject_alt_names = Self::convert_x509_subject_alternative_name(&x509)?;
let key_usages = Self::convert_x509_key_usages(&x509)?;
let extended_key_usages = Self::convert_x509_extended_key_usages(&x509)?;
let name_constraints = Self::convert_x509_name_constraints(&x509)?;
let serial_number = Some(x509.serial.to_bytes_be().into());
Ok(CertificateParams {
alg,
is_ca,
subject_alt_names,
key_usages,
extended_key_usages,
name_constraints,
serial_number,
distinguished_name: dn,
key_pair: Some(key_pair),
not_before: validity.not_before.to_datetime(),
not_after: validity.not_after.to_datetime(),
..Default::default()
})
}
#[cfg(feature = "x509-parser")]
fn convert_x509_is_ca(
x509: &x509_parser::certificate::X509Certificate<'_>,
) -> Result<IsCa, RcgenError> {
use x509_parser::extensions::BasicConstraints as B;
let basic_constraints = x509
.basic_constraints()
.or(Err(RcgenError::CouldNotParseCertificate))?
.map(|ext| ext.value);
let is_ca = match basic_constraints {
Some(B {
ca: true,
path_len_constraint: Some(n),
}) if *n <= u8::MAX as u32 => IsCa::Ca(BasicConstraints::Constrained(*n as u8)),
Some(B {
ca: true,
path_len_constraint: Some(_),
}) => return Err(RcgenError::CouldNotParseCertificate),
Some(B {
ca: true,
path_len_constraint: None,
}) => IsCa::Ca(BasicConstraints::Unconstrained),
Some(B { ca: false, .. }) => IsCa::ExplicitNoCa,
None => IsCa::NoCa,
};
Ok(is_ca)
}
#[cfg(feature = "x509-parser")]
fn convert_x509_subject_alternative_name(
x509: &x509_parser::certificate::X509Certificate<'_>,
) -> Result<Vec<SanType>, RcgenError> {
let sans = x509
.subject_alternative_name()
.or(Err(RcgenError::CouldNotParseCertificate))?
.map(|ext| &ext.value.general_names);
if let Some(sans) = sans {
let mut subject_alt_names = Vec::with_capacity(sans.len());
for san in sans {
subject_alt_names.push(SanType::try_from_general(san)?);
}
Ok(subject_alt_names)
} else {
Ok(Vec::new())
}
}
#[cfg(feature = "x509-parser")]
fn convert_x509_key_usages(
x509: &x509_parser::certificate::X509Certificate<'_>,
) -> Result<Vec<KeyUsagePurpose>, RcgenError> {
let key_usage = x509
.key_usage()
.or(Err(RcgenError::CouldNotParseCertificate))?
.map(|ext| ext.value);
let mut key_usages = Vec::new();
if let Some(key_usage) = key_usage {
if key_usage.digital_signature() {
key_usages.push(KeyUsagePurpose::DigitalSignature);
}
if key_usage.non_repudiation() {
key_usages.push(KeyUsagePurpose::ContentCommitment);
}
if key_usage.key_encipherment() {
key_usages.push(KeyUsagePurpose::KeyEncipherment);
}
if key_usage.data_encipherment() {
key_usages.push(KeyUsagePurpose::DataEncipherment);
}
if key_usage.key_agreement() {
key_usages.push(KeyUsagePurpose::KeyAgreement);
}
if key_usage.key_cert_sign() {
key_usages.push(KeyUsagePurpose::KeyCertSign);
}
if key_usage.crl_sign() {
key_usages.push(KeyUsagePurpose::CrlSign);
}
if key_usage.encipher_only() {
key_usages.push(KeyUsagePurpose::EncipherOnly);
}
if key_usage.decipher_only() {
key_usages.push(KeyUsagePurpose::DecipherOnly);
}
}
Ok(key_usages)
}
#[cfg(feature = "x509-parser")]
fn convert_x509_extended_key_usages(
x509: &x509_parser::certificate::X509Certificate<'_>,
) -> Result<Vec<ExtendedKeyUsagePurpose>, RcgenError> {
let extended_key_usage = x509
.extended_key_usage()
.or(Err(RcgenError::CouldNotParseCertificate))?
.map(|ext| ext.value);
let mut extended_key_usages = Vec::new();
if let Some(extended_key_usage) = extended_key_usage {
if extended_key_usage.any {
extended_key_usages.push(ExtendedKeyUsagePurpose::Any);
}
if extended_key_usage.server_auth {
extended_key_usages.push(ExtendedKeyUsagePurpose::ServerAuth);
}
if extended_key_usage.client_auth {
extended_key_usages.push(ExtendedKeyUsagePurpose::ClientAuth);
}
if extended_key_usage.code_signing {
extended_key_usages.push(ExtendedKeyUsagePurpose::CodeSigning);
}
if extended_key_usage.email_protection {
extended_key_usages.push(ExtendedKeyUsagePurpose::EmailProtection);
}
if extended_key_usage.time_stamping {
extended_key_usages.push(ExtendedKeyUsagePurpose::TimeStamping);
}
if extended_key_usage.ocsp_signing {
extended_key_usages.push(ExtendedKeyUsagePurpose::OcspSigning);
}
}
Ok(extended_key_usages)
}
#[cfg(feature = "x509-parser")]
fn convert_x509_name_constraints(
x509: &x509_parser::certificate::X509Certificate<'_>,
) -> Result<Option<NameConstraints>, RcgenError> {
let constraints = x509
.name_constraints()
.or(Err(RcgenError::CouldNotParseCertificate))?
.map(|ext| ext.value);
if let Some(constraints) = constraints {
let permitted_subtrees = if let Some(permitted) = &constraints.permitted_subtrees {
Self::convert_x509_general_subtrees(&permitted)?
} else {
Vec::new()
};
let excluded_subtrees = if let Some(excluded) = &constraints.excluded_subtrees {
Self::convert_x509_general_subtrees(&excluded)?
} else {
Vec::new()
};
let name_constraints = NameConstraints {
permitted_subtrees,
excluded_subtrees,
};
Ok(Some(name_constraints))
} else {
Ok(None)
}
}
#[cfg(feature = "x509-parser")]
fn convert_x509_general_subtrees(
subtrees: &[x509_parser::extensions::GeneralSubtree<'_>],
) -> Result<Vec<GeneralSubtree>, RcgenError> {
use x509_parser::extensions::GeneralName;
let mut result = Vec::new();
for subtree in subtrees {
let subtree = match &subtree.base {
GeneralName::RFC822Name(s) => GeneralSubtree::Rfc822Name(s.to_string()),
GeneralName::DNSName(s) => GeneralSubtree::DnsName(s.to_string()),
GeneralName::DirectoryName(n) => {
GeneralSubtree::DirectoryName(DistinguishedName::from_name(&n)?)
},
GeneralName::IPAddress(bytes) if bytes.len() == 8 => {
let addr: [u8; 4] = bytes[..4].try_into().unwrap();
let mask: [u8; 4] = bytes[4..].try_into().unwrap();
GeneralSubtree::IpAddress(CidrSubnet::V4(addr, mask))
},
GeneralName::IPAddress(bytes) if bytes.len() == 32 => {
let addr: [u8; 16] = bytes[..16].try_into().unwrap();
let mask: [u8; 16] = bytes[16..].try_into().unwrap();
GeneralSubtree::IpAddress(CidrSubnet::V6(addr, mask))
},
_ => continue,
};
result.push(subtree);
}
Ok(result)
}
fn write_subject_alt_names(&self, writer: DERWriter) {
write_x509_extension(writer, OID_SUBJECT_ALT_NAME, false, |writer| {
writer.write_sequence(|writer| {
for san in self.subject_alt_names.iter() {
writer.next().write_tagged_implicit(
Tag::context(san.tag()),
|writer| match san {
SanType::Rfc822Name(name)
| SanType::DnsName(name)
| SanType::URI(name) => writer.write_ia5_string(name),
SanType::IpAddress(IpAddr::V4(addr)) => {
writer.write_bytes(&addr.octets())
},
SanType::IpAddress(IpAddr::V6(addr)) => {
writer.write_bytes(&addr.octets())
},
},
);
}
});
});
}
fn write_request<K: PublicKeyData>(
&self,
pub_key: &K,
writer: DERWriter,
) -> Result<(), RcgenError> {
#[deny(unused)]
let Self {
alg,
not_before,
not_after,
serial_number,
subject_alt_names,
distinguished_name,
is_ca,
key_usages,
extended_key_usages,
name_constraints,
crl_distribution_points,
custom_extensions,
key_pair,
use_authority_key_identifier_extension,
key_identifier_method,
} = self;
let _ = (alg, key_pair, not_before, not_after, key_identifier_method);
if serial_number.is_some()
|| *is_ca != IsCa::NoCa
|| !key_usages.is_empty()
|| !extended_key_usages.is_empty()
|| name_constraints.is_some()
|| !crl_distribution_points.is_empty()
|| *use_authority_key_identifier_extension
{
return Err(RcgenError::UnsupportedInCsr);
}
writer.write_sequence(|writer| {
writer.next().write_u8(0);
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)
}),
}
});
});
}
});
pub_key.serialize_public_key_der(writer.next());
writer.next().write_tagged(Tag::context(0), |writer| {
if !subject_alt_names.is_empty() || !custom_extensions.is_empty() {
writer.write_sequence(|writer| {
let oid = ObjectIdentifier::from_slice(OID_PKCS_9_AT_EXTENSION_REQUEST);
writer.next().write_oid(&oid);
writer.next().write_set(|writer| {
writer.next().write_sequence(|writer| {
self.write_subject_alt_names(writer.next());
for ext in custom_extensions {
write_x509_extension(
writer.next(),
&ext.oid,
ext.critical,
|writer| writer.write_der(ext.content()),
);
}
});
});
});
}
});
});
Ok(())
}
fn write_cert<K: PublicKeyData>(
&self,
writer: DERWriter,
pub_key: &K,
ca: &Certificate,
) -> Result<(), RcgenError> {
writer.write_sequence(|writer| {
writer.next().write_tagged(Tag::context(0), |writer| {
writer.write_u8(2);
});
if let Some(ref serial) = self.serial_number {
writer.next().write_bigint_bytes(serial.as_ref(), true);
} else {
let hash = digest::digest(&digest::SHA256, pub_key.raw_bytes());
let sl = &hash.as_ref()[0..20];
writer.next().write_bigint_bytes(sl, true);
};
ca.params.alg.write_alg_ident(writer.next());
write_distinguished_name(writer.next(), &ca.params.distinguished_name);
writer.next().write_sequence(|writer| {
write_dt_utc_or_generalized(writer.next(), self.not_before);
write_dt_utc_or_generalized(writer.next(), self.not_after);
Ok::<(), RcgenError>(())
})?;
write_distinguished_name(writer.next(), &self.distinguished_name);
pub_key.serialize_public_key_der(writer.next());
let should_write_exts = self.use_authority_key_identifier_extension
|| !self.subject_alt_names.is_empty()
|| !self.extended_key_usages.is_empty()
|| self.name_constraints.iter().any(|c| !c.is_empty())
|| matches!(self.is_ca, IsCa::ExplicitNoCa)
|| matches!(self.is_ca, IsCa::Ca(_))
|| !self.custom_extensions.is_empty();
if should_write_exts {
writer.next().write_tagged(Tag::context(3), |writer| {
writer.write_sequence(|writer| {
if self.use_authority_key_identifier_extension {
write_x509_authority_key_identifier(writer.next(), ca)
}
if !self.subject_alt_names.is_empty() {
self.write_subject_alt_names(writer.next());
}
if !self.key_usages.is_empty() {
write_x509_extension(writer.next(), OID_KEY_USAGE, true, |writer| {
let mut bits: u16 = 0;
for entry in self.key_usages.iter() {
let index = match entry {
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,
};
bits |= 1 << index;
}
let msb = 16 - bits.leading_zeros();
let nb = if msb <= 8 { 1 } else { 2 };
let bits = bits.reverse_bits().to_be_bytes();
let bits = &bits[..nb];
writer.write_bitvec_bytes(&bits, msb as usize)
});
}
if !self.extended_key_usages.is_empty() {
write_x509_extension(
writer.next(),
OID_EXT_KEY_USAGE,
false,
|writer| {
writer.write_sequence(|writer| {
for usage in self.extended_key_usages.iter() {
let oid = ObjectIdentifier::from_slice(usage.oid());
writer.next().write_oid(&oid);
}
});
},
);
}
if let Some(name_constraints) = &self.name_constraints {
if !name_constraints.is_empty() {
write_x509_extension(
writer.next(),
OID_NAME_CONSTRAINTS,
true,
|writer| {
writer.write_sequence(|writer| {
if !name_constraints.permitted_subtrees.is_empty() {
write_general_subtrees(
writer.next(),
0,
&name_constraints.permitted_subtrees,
);
}
if !name_constraints.excluded_subtrees.is_empty() {
write_general_subtrees(
writer.next(),
1,
&name_constraints.excluded_subtrees,
);
}
});
},
);
}
}
if !self.crl_distribution_points.is_empty() {
write_x509_extension(
writer.next(),
OID_CRL_DISTRIBUTION_POINTS,
false,
|writer| {
writer.write_sequence(|writer| {
for distribution_point in &self.crl_distribution_points {
distribution_point.write_der(writer.next());
}
})
},
);
}
match self.is_ca {
IsCa::Ca(ref constraint) => {
write_x509_extension(
writer.next(),
OID_SUBJECT_KEY_IDENTIFIER,
false,
|writer| {
let key_identifier = self.key_identifier(pub_key);
writer.write_bytes(key_identifier.as_ref());
},
);
write_x509_extension(
writer.next(),
OID_BASIC_CONSTRAINTS,
true,
|writer| {
writer.write_sequence(|writer| {
writer.next().write_bool(true); if let BasicConstraints::Constrained(
path_len_constraint,
) = constraint
{
writer.next().write_u8(*path_len_constraint);
}
});
},
);
},
IsCa::ExplicitNoCa => {
write_x509_extension(
writer.next(),
OID_SUBJECT_KEY_IDENTIFIER,
false,
|writer| {
let key_identifier = self.key_identifier(pub_key);
writer.write_bytes(key_identifier.as_ref());
},
);
write_x509_extension(
writer.next(),
OID_BASIC_CONSTRAINTS,
true,
|writer| {
writer.write_sequence(|writer| {
writer.next().write_bool(false); });
},
);
},
IsCa::NoCa => {},
}
for ext in &self.custom_extensions {
write_x509_extension(writer.next(), &ext.oid, ext.critical, |writer| {
writer.write_der(ext.content())
});
}
});
});
}
Ok(())
})
}
fn key_identifier<K: PublicKeyData>(&self, pub_key: &K) -> Vec<u8> {
let digest_method = match self.key_identifier_method {
KeyIdMethod::Sha256 => &digest::SHA256,
KeyIdMethod::Sha384 => &digest::SHA384,
KeyIdMethod::Sha512 => &digest::SHA512,
};
let digest = digest::digest(digest_method, pub_key.raw_bytes());
let truncated_digest = &digest.as_ref()[0..20];
truncated_digest.to_vec()
}
fn serialize_der_with_signer<K: PublicKeyData>(
&self,
pub_key: &K,
ca: &Certificate,
) -> Result<Vec<u8>, RcgenError> {
yasna::try_construct_der(|writer| {
writer.write_sequence(|writer| {
let tbs_cert_list_serialized = yasna::try_construct_der(|writer| {
self.write_cert(writer, pub_key, ca)?;
Ok::<(), RcgenError>(())
})?;
writer.next().write_der(&tbs_cert_list_serialized);
ca.params.alg.write_alg_ident(writer.next());
ca.key_pair.sign(&tbs_cert_list_serialized, writer.next())?;
Ok(())
})
})
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum IsCa {
NoCa,
ExplicitNoCa,
Ca(BasicConstraints),
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum BasicConstraints {
Unconstrained,
Constrained(u8),
}
impl CertificateParams {
pub fn new(subject_alt_names: impl Into<Vec<String>>) -> Self {
let subject_alt_names = subject_alt_names
.into()
.into_iter()
.map(|s| match s.parse() {
Ok(ip) => SanType::IpAddress(ip),
Err(_) => SanType::DnsName(s),
})
.collect::<Vec<_>>();
CertificateParams {
subject_alt_names,
..Default::default()
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct NameConstraints {
pub permitted_subtrees: Vec<GeneralSubtree>,
pub excluded_subtrees: Vec<GeneralSubtree>,
}
impl NameConstraints {
fn is_empty(&self) -> bool {
self.permitted_subtrees.is_empty() && self.excluded_subtrees.is_empty()
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum KeyUsagePurpose {
DigitalSignature,
ContentCommitment,
KeyEncipherment,
DataEncipherment,
KeyAgreement,
KeyCertSign,
CrlSign,
EncipherOnly,
DecipherOnly,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum ExtendedKeyUsagePurpose {
Any,
ServerAuth,
ClientAuth,
CodeSigning,
EmailProtection,
TimeStamping,
OcspSigning,
}
impl ExtendedKeyUsagePurpose {
fn oid(&self) -> &'static [u64] {
use ExtendedKeyUsagePurpose::*;
match self {
Any => &[2, 5, 29, 37, 0],
ServerAuth => &[1, 3, 6, 1, 5, 5, 7, 3, 1],
ClientAuth => &[1, 3, 6, 1, 5, 5, 7, 3, 2],
CodeSigning => &[1, 3, 6, 1, 5, 5, 7, 3, 3],
EmailProtection => &[1, 3, 6, 1, 5, 5, 7, 3, 4],
TimeStamping => &[1, 3, 6, 1, 5, 5, 7, 3, 8],
OcspSigning => &[1, 3, 6, 1, 5, 5, 7, 3, 9],
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct CustomExtension {
oid: Vec<u64>,
critical: bool,
content: Vec<u8>,
}
impl CustomExtension {
pub fn new_acme_identifier(sha_digest: &[u8]) -> Self {
assert_eq!(sha_digest.len(), 32, "wrong size of sha_digest");
let content = yasna::construct_der(|writer| {
writer.write_bytes(sha_digest);
});
Self {
oid: OID_PE_ACME.to_owned(),
critical: true,
content,
}
}
pub fn from_oid_content(oid: &[u64], content: Vec<u8>) -> Self {
Self {
oid: oid.to_owned(),
critical: false,
content,
}
}
pub fn set_criticality(&mut self, criticality: bool) {
self.critical = criticality;
}
pub fn criticality(&self) -> bool {
self.critical
}
pub fn content(&self) -> &[u8] {
&self.content
}
pub fn oid_components(&self) -> impl Iterator<Item = u64> + '_ {
self.oid.iter().copied()
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[non_exhaustive]
pub enum KeyIdMethod {
Sha256,
Sha384,
Sha512,
}
pub fn date_time_ymd(year: i32, month: u8, day: u8) -> OffsetDateTime {
let month = Month::try_from(month).expect("out-of-range month");
let primitive_dt = PrimitiveDateTime::new(
Date::from_calendar_date(year, month, day).expect("invalid or out-of-range date"),
Time::MIDNIGHT,
);
primitive_dt.assume_utc()
}
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::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)),
}
});
});
}
});
}
fn write_general_subtrees(writer: DERWriter, tag: u64, general_subtrees: &[GeneralSubtree]) {
writer.write_tagged_implicit(Tag::context(tag), |writer| {
writer.write_sequence(|writer| {
for subtree in general_subtrees.iter() {
writer.next().write_sequence(|writer| {
writer
.next()
.write_tagged_implicit(
Tag::context(subtree.tag()),
|writer| match subtree {
GeneralSubtree::Rfc822Name(name)
| GeneralSubtree::DnsName(name) => writer.write_ia5_string(name),
GeneralSubtree::DirectoryName(name) => {
write_distinguished_name(writer, name)
},
GeneralSubtree::IpAddress(subnet) => {
writer.write_bytes(&subnet.to_bytes())
},
},
);
});
}
});
});
}
impl Certificate {
pub fn from_params(mut params: CertificateParams) -> Result<Self, RcgenError> {
let key_pair = if let Some(key_pair) = params.key_pair.take() {
if !key_pair.is_compatible(¶ms.alg) {
return Err(RcgenError::CertificateKeyPairMismatch);
}
key_pair
} else {
KeyPair::generate(¶ms.alg)?
};
Ok(Certificate { params, key_pair })
}
pub fn get_params(&self) -> &CertificateParams {
&self.params
}
pub fn get_key_identifier(&self) -> Vec<u8> {
self.params.key_identifier(&self.key_pair)
}
pub fn serialize_der(&self) -> Result<Vec<u8>, RcgenError> {
self.serialize_der_with_signer(&self)
}
pub fn serialize_der_with_signer(&self, ca: &Certificate) -> Result<Vec<u8>, RcgenError> {
self.params.serialize_der_with_signer(&self.key_pair, ca)
}
pub fn serialize_request_der(&self) -> Result<Vec<u8>, RcgenError> {
yasna::try_construct_der(|writer| {
writer.write_sequence(|writer| {
let cert_data = yasna::try_construct_der(|writer| {
self.params.write_request(&self.key_pair, writer)
})?;
writer.next().write_der(&cert_data);
self.params.alg.write_alg_ident(writer.next());
self.key_pair.sign(&cert_data, writer.next())?;
Ok(())
})
})
}
pub fn get_key_pair(&self) -> &KeyPair {
&self.key_pair
}
#[cfg(feature = "pem")]
pub fn serialize_pem(&self) -> Result<String, RcgenError> {
let contents = self.serialize_der()?;
let p = Pem::new("CERTIFICATE", contents);
Ok(pem::encode_config(&p, ENCODE_CONFIG))
}
#[cfg(feature = "pem")]
pub fn serialize_pem_with_signer(&self, ca: &Certificate) -> Result<String, RcgenError> {
let contents = self.serialize_der_with_signer(ca)?;
let p = Pem::new("CERTIFICATE", contents);
Ok(pem::encode_config(&p, ENCODE_CONFIG))
}
#[cfg(feature = "pem")]
pub fn serialize_request_pem(&self) -> Result<String, RcgenError> {
let contents = self.serialize_request_der()?;
let p = Pem::new("CERTIFICATE REQUEST", contents);
Ok(pem::encode_config(&p, ENCODE_CONFIG))
}
pub fn serialize_private_key_der(&self) -> Vec<u8> {
self.key_pair.serialize_der()
}
#[cfg(feature = "pem")]
pub fn serialize_private_key_pem(&self) -> String {
self.key_pair.serialize_pem()
}
}
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, ca: &Certificate) {
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(ca.get_key_identifier().as_ref())
})
});
});
}
#[cfg(feature = "zeroize")]
impl zeroize::Zeroize for KeyPair {
fn zeroize(&mut self) {
self.serialized_der.zeroize();
}
}
#[cfg(feature = "zeroize")]
impl zeroize::Zeroize for Certificate {
fn zeroize(&mut self) {
self.params.zeroize();
self.key_pair.zeroize();
}
}
#[cfg(feature = "zeroize")]
impl zeroize::Zeroize for CertificateSigningRequest {
fn zeroize(&mut self) {
self.params.zeroize();
}
}
#[cfg(feature = "zeroize")]
impl zeroize::Zeroize for CertificateParams {
fn zeroize(&mut self) {
self.key_pair.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 super::*;
use std::panic::catch_unwind;
fn get_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 = get_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 = get_times();
for dt in times {
let _gt = dt_to_generalized(dt);
}
}
#[test]
fn test_with_key_usages() {
let mut params: CertificateParams = Default::default();
params.key_usages = vec![
KeyUsagePurpose::DigitalSignature,
KeyUsagePurpose::KeyEncipherment,
KeyUsagePurpose::ContentCommitment,
];
params.is_ca = IsCa::Ca(BasicConstraints::Constrained(0));
let cert = Certificate::from_params(params).unwrap();
let der = cert.serialize_der().unwrap();
let (_rem, cert) = x509_parser::parse_x509_certificate(&der).unwrap();
let key_usage_oid_str = "2.5.29.15";
let mut found = false;
for ext in cert.extensions() {
if key_usage_oid_str == ext.oid.to_id_string() {
match ext.parsed_extension() {
x509_parser::extensions::ParsedExtension::KeyUsage(usage) => {
assert!(usage.flags == 7);
found = true;
},
_ => {},
}
}
}
assert!(found);
}
#[test]
fn test_with_key_usages_decipheronly_only() {
let mut params: CertificateParams = Default::default();
params.key_usages = vec![KeyUsagePurpose::DecipherOnly];
params.is_ca = IsCa::Ca(BasicConstraints::Constrained(0));
let cert = Certificate::from_params(params).unwrap();
let der = cert.serialize_der().unwrap();
let (_rem, cert) = x509_parser::parse_x509_certificate(&der).unwrap();
let key_usage_oid_str = "2.5.29.15";
let mut found = false;
for ext in cert.extensions() {
if key_usage_oid_str == ext.oid.to_id_string() {
match ext.parsed_extension() {
x509_parser::extensions::ParsedExtension::KeyUsage(usage) => {
assert!(usage.flags == 256);
found = true;
},
_ => {},
}
}
}
assert!(found);
}
#[test]
fn test_with_extended_key_usages_any() {
let mut params: CertificateParams = Default::default();
params.extended_key_usages = vec![ExtendedKeyUsagePurpose::Any];
let cert = Certificate::from_params(params).unwrap();
let der = cert.serialize_der().unwrap();
let (_rem, cert) = x509_parser::parse_x509_certificate(&der).unwrap();
let maybe_extension = cert.extended_key_usage().unwrap();
let extension = maybe_extension.unwrap();
assert!(extension.value.any);
}
#[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 = "pem")]
mod test_pem_serialization {
use crate::Certificate;
use crate::CertificateParams;
#[test]
#[cfg(windows)]
fn test_windows_line_endings() {
let cert = Certificate::from_params(CertificateParams::default()).unwrap();
let pem = cert.serialize_pem().expect("Failed to serialize pem");
assert!(pem.contains("\r\n"));
}
#[test]
#[cfg(not(windows))]
fn test_not_windows_line_endings() {
let cert = Certificate::from_params(CertificateParams::default()).unwrap();
let pem = cert.serialize_pem().expect("Failed to serialize pem");
assert!(!pem.contains("\r"));
}
}
#[cfg(feature = "x509-parser")]
mod test_ip_address_from_octets {
use super::super::ip_addr_from_octets;
use super::super::RcgenError;
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<u8> = (0..10).into_iter().collect();
let actual = ip_addr_from_octets(&incorrect).unwrap_err();
assert_eq!(RcgenError::InvalidIpAddressOctetLength(10), actual);
}
#[test]
fn none() {
let actual = ip_addr_from_octets(&[]).unwrap_err();
assert_eq!(RcgenError::InvalidIpAddressOctetLength(0), actual);
}
#[test]
fn too_many() {
let incorrect: Vec<u8> = (0..20).into_iter().collect();
let actual = ip_addr_from_octets(&incorrect).unwrap_err();
assert_eq!(RcgenError::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);
}
}
}