use alloc::vec;
use const_oid::db::{
rfc4519,
rfc5280::{ID_KP_CLIENT_AUTH, ID_KP_SERVER_AUTH},
};
use der::asn1::SetOfVec;
#[cfg(feature = "hazmat")]
use const_oid::db::rfc5912;
use crate::{
attr::AttributeTypeAndValue,
builder::{BuilderProfile, Result},
certificate::TbsCertificate,
ext::{
Extension, ToExtension,
pkix::{
AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage, KeyUsages,
SubjectKeyIdentifier, name::GeneralNames,
},
},
name::{Name, RelativeDistinguishedName},
};
use spki::SubjectPublicKeyInfoRef;
pub struct Subordinate {
pub issuer: Name,
pub subject: Name,
pub path_len_constraint: Option<u8>,
pub emits_ocsp_response: bool,
pub client_auth: bool,
}
impl BuilderProfile for Subordinate {
fn get_issuer(&self, _subject: &Name) -> Name {
self.issuer.clone()
}
fn get_subject(&self) -> Name {
self.subject.clone()
}
fn build_extensions(
&self,
spk: SubjectPublicKeyInfoRef<'_>,
issuer_spk: SubjectPublicKeyInfoRef<'_>,
tbs: &TbsCertificate,
) -> Result<vec::Vec<Extension>> {
let mut extensions: vec::Vec<Extension> = vec::Vec::new();
extensions.push(
AuthorityKeyIdentifier::try_from(issuer_spk.clone())?
.to_extension(&tbs.subject, &extensions)?,
);
extensions.push(
BasicConstraints {
ca: true,
path_len_constraint: self.path_len_constraint,
}
.to_extension(&tbs.subject, &extensions)?,
);
let mut key_usage = KeyUsages::KeyCertSign | KeyUsages::CRLSign;
if self.emits_ocsp_response {
key_usage |= KeyUsages::DigitalSignature;
}
extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?);
let ski = SubjectKeyIdentifier::try_from(spk)?;
extensions.push(ski.to_extension(&tbs.subject, &extensions)?);
let mut eku = ExtendedKeyUsage(vec![ID_KP_SERVER_AUTH]);
if self.client_auth {
eku.0.push(ID_KP_CLIENT_AUTH);
}
extensions.push(eku.to_extension(&tbs.subject, &extensions)?);
Ok(extensions)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum CertificateType {
DomainValidated(DomainValidated),
IndividualValidated,
OrganizationValidated,
ExtendedValidation,
}
impl CertificateType {
pub fn domain_validated(subject: Name, names: GeneralNames) -> Result<Self> {
let rdns: vec::Vec<RelativeDistinguishedName> = subject
.iter_rdn()
.filter_map(|rdn| {
let out = SetOfVec::<AttributeTypeAndValue>::from_iter(
rdn.iter()
.filter(|attr_value| {
attr_value.oid == rfc4519::COUNTRY_NAME
|| attr_value.oid == rfc4519::COMMON_NAME
})
.cloned(),
)
.ok()?;
Some(RelativeDistinguishedName(out))
})
.filter(|rdn| !rdn.is_empty())
.collect();
let subject: Name = Name(rdns.into());
Ok(Self::DomainValidated(DomainValidated { subject, names }))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DomainValidated {
subject: Name,
names: GeneralNames,
}
pub struct Subscriber {
pub certificate_type: CertificateType,
pub issuer: Name,
pub client_auth: bool,
#[cfg(feature = "hazmat")]
pub tls12_options: Tls12Options,
#[cfg(feature = "hazmat")]
pub enable_data_encipherment: bool,
}
#[derive(Default)]
pub struct Tls12Options {
pub enable_key_encipherment: bool,
pub enable_key_agreement: bool,
}
impl BuilderProfile for Subscriber {
fn get_issuer(&self, _subject: &Name) -> Name {
self.issuer.clone()
}
fn get_subject(&self) -> Name {
match &self.certificate_type {
CertificateType::DomainValidated(DomainValidated { subject, .. }) => subject.clone(),
_ => todo!(),
}
}
#[cfg_attr(not(feature = "hazmat"), allow(unused_variables))]
fn build_extensions(
&self,
spk: SubjectPublicKeyInfoRef<'_>,
issuer_spk: SubjectPublicKeyInfoRef<'_>,
tbs: &TbsCertificate,
) -> Result<vec::Vec<Extension>> {
let mut extensions: vec::Vec<Extension> = vec::Vec::new();
extensions.push(
AuthorityKeyIdentifier::try_from(issuer_spk.clone())?
.to_extension(&tbs.subject, &extensions)?,
);
let mut eku = ExtendedKeyUsage(vec![ID_KP_SERVER_AUTH]);
if self.client_auth {
eku.0.push(ID_KP_CLIENT_AUTH);
}
extensions.push(eku.to_extension(&tbs.subject, &extensions)?);
extensions.push(
BasicConstraints {
ca: false,
path_len_constraint: None,
}
.to_extension(&tbs.subject, &extensions)?,
);
#[cfg_attr(not(feature = "hazmat"), allow(unused_mut))]
let mut key_usage = KeyUsages::DigitalSignature.into();
#[cfg(feature = "hazmat")]
{
if spk.is_rsa() {
if self.enable_data_encipherment {
key_usage |= KeyUsages::DataEncipherment;
}
if self.tls12_options.enable_key_encipherment {
key_usage |= KeyUsages::KeyEncipherment;
}
}
if self.tls12_options.enable_key_agreement && spk.is_ecc() {
key_usage |= KeyUsages::KeyAgreement;
}
}
extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?);
Ok(extensions)
}
}
#[cfg(feature = "hazmat")]
trait KeyType {
fn is_rsa(&self) -> bool;
fn is_ecc(&self) -> bool;
}
#[cfg(feature = "hazmat")]
impl KeyType for SubjectPublicKeyInfoRef<'_> {
fn is_rsa(&self) -> bool {
self.algorithm.oid == rfc5912::RSA_ENCRYPTION
}
fn is_ecc(&self) -> bool {
self.algorithm.oid == rfc5912::ID_EC_PUBLIC_KEY
}
}