Skip to content

Commit

Permalink
rework CA builder profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
baloo committed Jan 17, 2024
1 parent fc19e66 commit b2b6b6d
Show file tree
Hide file tree
Showing 4 changed files with 407 additions and 178 deletions.
189 changes: 11 additions & 178 deletions x509-cert/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! X509 Certificate builder

use alloc::vec;
use core::fmt;
use core::{fmt, marker::PhantomData};
use der::{asn1::BitString, referenced::OwnedToRef, Encode};
use signature::{rand_core::CryptoRngCore, Keypair, RandomizedSigner, Signer};
use spki::{
Expand All @@ -23,6 +23,10 @@ use crate::{
time::Validity,
};

pub mod profile;

use self::profile::Profile;

/// Error type
#[derive(Debug)]
#[non_exhaustive]
Expand Down Expand Up @@ -70,180 +74,6 @@ impl From<signature::Error> for Error {

type Result<T> = core::result::Result<T, Error>;

/// The type of certificate to build
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Profile {
/// Build a root CA certificate
Root,
/// Build an intermediate sub CA certificate
SubCA {
/// issuer Name,
/// represents the name signing the certificate
issuer: Name,
/// pathLenConstraint INTEGER (0..MAX) OPTIONAL
/// BasicConstraints as defined in [RFC 5280 Section 4.2.1.9].
path_len_constraint: Option<u8>,
},
/// Build an end certificate
Leaf {
/// issuer Name,
/// represents the name signing the certificate
issuer: Name,
/// Usage of the leaf certificate
usage: Usage,
/// should the subject key identifier extension be included
///
/// From [RFC 5280 Section 4.2.1.2]:
/// For end entity certificates, subject key identifiers SHOULD be
/// derived from the public key. Two common methods for generating key
/// identifiers from the public key are identified above.
#[cfg(feature = "hazmat")]
include_subject_key_identifier: bool,
},
#[cfg(feature = "hazmat")]
/// Opt-out of the default extensions
Manual {
/// issuer Name,
/// represents the name signing the certificate
/// A `None` will make it a self-signed certificate
issuer: Option<Name>,
},
}

impl Profile {
fn get_issuer(&self, subject: &Name) -> Name {
match self {
Profile::Root => subject.clone(),
Profile::SubCA { issuer, .. } => issuer.clone(),
Profile::Leaf { issuer, .. } => issuer.clone(),
#[cfg(feature = "hazmat")]
Profile::Manual { issuer, .. } => issuer.as_ref().unwrap_or(subject).clone(),
}
}

fn build_extensions(
&self,
spk: SubjectPublicKeyInfoRef<'_>,
issuer_spk: SubjectPublicKeyInfoRef<'_>,
tbs: &TbsCertificate,
) -> Result<vec::Vec<Extension>> {
#[cfg(feature = "hazmat")]
// User opted out of default extensions set.
if let Profile::Manual { .. } = self {
return Ok(vec::Vec::default());
}

let mut extensions: vec::Vec<Extension> = vec::Vec::new();

match self {
#[cfg(feature = "hazmat")]
Profile::Leaf {
include_subject_key_identifier: false,
..
} => {}
_ => extensions.push(
SubjectKeyIdentifier::try_from(spk)?.to_extension(&tbs.subject, &extensions)?,
),
}

// Build Authority Key Identifier
match self {
Profile::Root => {}
_ => {
extensions.push(
AuthorityKeyIdentifier::try_from(issuer_spk.clone())?
.to_extension(&tbs.subject, &extensions)?,
);
}
}

// Build Basic Contraints extensions
extensions.push(match self {
Profile::Root => BasicConstraints {
ca: true,
path_len_constraint: None,
}
.to_extension(&tbs.subject, &extensions)?,
Profile::SubCA {
path_len_constraint,
..
} => BasicConstraints {
ca: true,
path_len_constraint: *path_len_constraint,
}
.to_extension(&tbs.subject, &extensions)?,
Profile::Leaf { .. } => BasicConstraints {
ca: false,
path_len_constraint: None,
}
.to_extension(&tbs.subject, &extensions)?,
#[cfg(feature = "hazmat")]
Profile::Manual { .. } => unreachable!(),
});

// Build Key Usage extension
match self {
Profile::Root | Profile::SubCA { .. } => {
extensions.push(
KeyUsage(KeyUsages::KeyCertSign | KeyUsages::CRLSign)
.to_extension(&tbs.subject, &extensions)?,
);
}
Profile::Leaf { usage, .. } => {
let key_usage = usage.key_usage().into();

extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?);
}
#[cfg(feature = "hazmat")]
Profile::Manual { .. } => unreachable!(),
}

Ok(extensions)
}
}

/// [`Usage`] describes the usage of a Leaf certificate.
///
/// This is designed in accordance with [ETSI EN 319 412-2 § 4.3.2 Key usage].
///
/// The various fields will refer to [RFC 5280 § 4.2.1.3 Key Usage] definitions.
///
/// [RFC 5280 § 4.2.1.3 Key Usage]: https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3
/// [ETSI EN 319 412-2 § 4.3.2 Key usage]: https://www.etsi.org/deliver/etsi_en/319400_319499/31941202/02.03.01_60/en_31941202v020301p.pdf#page=11
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Usage {
/// [`Usage::NonRepudiation`] will set the NonRepudiation (also known as
/// contentCommitment) bit of KeyUsage.
NonRepudiation,
/// [`Usage::DigitalSignature`] will set the digitalSignature bit.
///
/// This is meant to be used in an entity authentication service, a data
/// origin authentication service, and/or an integrity service.
DigitalSignature,
/// [`Usage::KeyAgreement`] will set the `keyAgreement` bit.
///
/// This is meant to be used on Certificates when a Diffie-Hellman key is
/// to be used for key management.
KeyAgreement,
/// [`Usage::KeyEncipherment`] will set the `keyEncipherment` bit.
///
/// This is meant to be used on Certificates when an RSA public
/// key is to be used for encrypting a symmetric content-decryption
/// key or an asymmetric private key.
KeyEncipherment,
}

impl Usage {
fn key_usage(&self) -> KeyUsages {
match self {
Self::NonRepudiation => KeyUsages::NonRepudiation,
Self::DigitalSignature => KeyUsages::DigitalSignature,
Self::KeyAgreement => KeyUsages::KeyAgreement,
Self::KeyEncipherment => KeyUsages::KeyEncipherment,
}
}
}

/// X509 Certificate builder
///
/// ```
Expand Down Expand Up @@ -297,14 +127,17 @@ where
S::VerifyingKey: EncodePublicKey,
{
/// Creates a new certificate builder
pub fn new(
profile: Profile,
pub fn new<P>(
profile: P,
serial_number: SerialNumber,
mut validity: Validity,
subject: Name,
subject_public_key_info: SubjectPublicKeyInfoOwned,
cert_signer: &'s S,
) -> Result<Self> {
) -> Result<Self>
where
P: Profile,
{
let verifying_key = cert_signer.verifying_key();
let signer_pub = SubjectPublicKeyInfoOwned::from_key(verifying_key)?;

Expand Down
60 changes: 60 additions & 0 deletions x509-cert/src/builder/profile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use alloc::vec;
use spki::{
DynSignatureAlgorithmIdentifier, EncodePublicKey, SignatureBitStringEncoding,
SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef,
};

use crate::{
builder::Result,
certificate::{Certificate, TbsCertificate, Version},
ext::{
pkix::{
AuthorityKeyIdentifier, BasicConstraints, KeyUsage, KeyUsages, SubjectKeyIdentifier,
},
AsExtension, Extension, Extensions,
},
name::Name,
request::{attributes::AsAttribute, CertReq, CertReqInfo, ExtensionReq},
serial_number::SerialNumber,
time::Validity,
};

pub mod cabf;
pub mod piv;

pub(crate) mod sealed {
use alloc::vec;
use spki::{
DynSignatureAlgorithmIdentifier, EncodePublicKey, SignatureBitStringEncoding,
SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef,
};

use crate::{
builder::Result,
certificate::{Certificate, TbsCertificate, Version},
ext::{
pkix::{
AuthorityKeyIdentifier, BasicConstraints, KeyUsage, KeyUsages, SubjectKeyIdentifier,
},
AsExtension, Extension, Extensions,
},
name::Name,
request::{attributes::AsAttribute, CertReq, CertReqInfo, ExtensionReq},
serial_number::SerialNumber,
time::Validity,
};

pub trait Profile {
fn get_issuer(&self, subject: &Name) -> Name;

fn build_extensions(
&self,
spk: SubjectPublicKeyInfoRef<'_>,
issuer_spk: SubjectPublicKeyInfoRef<'_>,
tbs: &TbsCertificate,
) -> Result<vec::Vec<Extension>>;
}
}

#[cfg(feature = "hazmat")]
pub use sealed::Profile;
105 changes: 105 additions & 0 deletions x509-cert/src/builder/profile/cabf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//! https://cabforum.org/wp-content/uploads/CA-Browser-Forum-BR-v2.0.1.pdf
use alloc::vec;
use core::marker::PhantomData;

use crate::{
builder::{Profile, Result},
certificate::{Certificate, TbsCertificate, Version},
ext::{
pkix::{
AuthorityKeyIdentifier, BasicConstraints, KeyUsage, KeyUsages, SubjectKeyIdentifier,
},
AsExtension, Extension, Extensions,
},
name::Name,
};
use spki::{EncodePublicKey, SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef};

pub struct Root {
pub emits_ocsp_response: bool,
}

impl Default for Root {
fn default() -> Self {
Self {
emits_ocsp_response: false,
}
}
}

impl Profile for Root {
fn get_issuer(&self, subject: &Name) -> Name {
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();

// 7.1.2.1.2 Root CA Extensions

// subjectKeyIdentifier MUST
//
// TODO: from 7.1.2.11.4 Subject Key Identifier
// The CA MUST generate a subjectKeyIdentifier that is unique within the scope of all
// Certificates it has issued for each unique public key (the subjectPublicKeyInfo field of the
// tbsCertificate). For example, CAs may generate the subject key identifier using an algorithm
// derived from the public key, or may generate a sufficiently‐large unique number, such by using a
// CSPRNG.
let ski = SubjectKeyIdentifier::try_from(spk)?;
extensions.push(ski.to_extension(&tbs.subject, &extensions)?);

// authorityKeyIdentifier RECOMMENDED
// 7.1.2.1.3 Root CA Authority Key Identifier
extensions.push(
AuthorityKeyIdentifier {
// KeyIdentifier must be the same as subjectKeyIdentifier
key_identifier: Some(ski.0),
// other fields must not be present.
..Default::default()
}
.to_extension(&tbs.subject, &extensions)?,
);

// Spec: 7.1.2.1.4 Root CA Basic Constraints
extensions.push(
BasicConstraints {
ca: true,
path_len_constraint: None,
}
.to_extension(&tbs.subject, &extensions)?,
);

// Spec: 7.1.2.10.7 CA Certificate Key Usage
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)?);

Ok(extensions)
}

// 7.1.2.1 Root CA Certificate Profile
// TODO:
// - issuerUniqueID MUST NOT be present
// - subjectUniqueID MUST NOT be present
// NOTE(baloo): we never build those?
//
// 7.1.2.1.1 Root CA Validity
// TODO:
// - Minimum 2922 days (approx. 8 years)
// - Max 9132 days (approx. 25 years)
//
//
}

pub mod tls;

pub mod codesigning {
//! https://cabforum.org/wp-content/uploads/Baseline-Requirements-for-the-Issuance-and-Management-of-Code-Signing.v2.8.pdf
}

0 comments on commit b2b6b6d

Please sign in to comment.