aboutsummaryrefslogtreecommitdiff
path: root/src/builder.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/builder.rs')
-rw-r--r--src/builder.rs526
1 files changed, 526 insertions, 0 deletions
diff --git a/src/builder.rs b/src/builder.rs
new file mode 100644
index 0000000..ec51a7e
--- /dev/null
+++ b/src/builder.rs
@@ -0,0 +1,526 @@
+//! X509 Certificate builder
+
+use alloc::vec;
+use core::fmt;
+use der::{asn1::BitString, referenced::OwnedToRef, Encode};
+use signature::{rand_core::CryptoRngCore, Keypair, RandomizedSigner, Signer};
+use spki::{
+ DynSignatureAlgorithmIdentifier, EncodePublicKey, SignatureBitStringEncoding,
+ SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef,
+};
+
+use crate::{
+ 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,
+};
+
+/// Error type
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum Error {
+ /// ASN.1 DER-related errors.
+ Asn1(der::Error),
+
+ /// Public key errors propagated from the [`spki::Error`] type.
+ PublicKey(spki::Error),
+
+ /// Signing error propagated for the [`signature::Error`] type.
+ Signature(signature::Error),
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for Error {}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Error::Asn1(err) => write!(f, "ASN.1 error: {}", err),
+ Error::PublicKey(err) => write!(f, "public key error: {}", err),
+ Error::Signature(err) => write!(f, "signature error: {}", err),
+ }
+ }
+}
+
+impl From<der::Error> for Error {
+ fn from(err: der::Error) -> Error {
+ Error::Asn1(err)
+ }
+}
+
+impl From<spki::Error> for Error {
+ fn from(err: spki::Error) -> Error {
+ Error::PublicKey(err)
+ }
+}
+
+impl From<signature::Error> for Error {
+ fn from(err: signature::Error) -> Error {
+ Error::Signature(err)
+ }
+}
+
+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,
+ /// should the key agreement flag of KeyUsage be enabled
+ enable_key_agreement: bool,
+ /// should the key encipherment flag of KeyUsage be enabled
+ enable_key_encipherment: bool,
+ /// 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 {
+ enable_key_agreement,
+ enable_key_encipherment,
+ ..
+ } => {
+ let mut key_usage = KeyUsages::DigitalSignature | KeyUsages::NonRepudiation;
+ if *enable_key_encipherment {
+ key_usage |= KeyUsages::KeyEncipherment;
+ }
+ if *enable_key_agreement {
+ key_usage |= KeyUsages::KeyAgreement;
+ }
+
+ extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?);
+ }
+ #[cfg(feature = "hazmat")]
+ Profile::Manual { .. } => unreachable!(),
+ }
+
+ Ok(extensions)
+ }
+}
+
+/// X509 Certificate builder
+///
+/// ```
+/// use der::Decode;
+/// use x509_cert::spki::SubjectPublicKeyInfoOwned;
+/// use x509_cert::builder::{CertificateBuilder, Profile};
+/// use x509_cert::name::Name;
+/// use x509_cert::serial_number::SerialNumber;
+/// use x509_cert::time::Validity;
+/// use std::str::FromStr;
+///
+/// # const RSA_2048_DER: &[u8] = include_bytes!("../tests/examples/rsa2048-pub.der");
+/// # const RSA_2048_PRIV_DER: &[u8] = include_bytes!("../tests/examples/rsa2048-priv.der");
+/// # use rsa::{pkcs1v15::SigningKey, pkcs1::DecodeRsaPrivateKey};
+/// # use sha2::Sha256;
+/// # use std::time::Duration;
+/// # use der::referenced::RefToOwned;
+/// # fn rsa_signer() -> SigningKey<Sha256> {
+/// # let private_key = rsa::RsaPrivateKey::from_pkcs1_der(RSA_2048_PRIV_DER).unwrap();
+/// # let signing_key = SigningKey::<Sha256>::new_with_prefix(private_key);
+/// # signing_key
+/// # }
+///
+/// let serial_number = SerialNumber::from(42u32);
+/// let validity = Validity::from_now(Duration::new(5, 0)).unwrap();
+/// let profile = Profile::Root;
+/// let subject = Name::from_str("CN=World domination corporation,O=World domination Inc,C=US").unwrap();
+///
+/// let pub_key = SubjectPublicKeyInfoOwned::try_from(RSA_2048_DER).expect("get rsa pub key");
+///
+/// let mut signer = rsa_signer();
+/// let mut builder = CertificateBuilder::new(
+/// profile,
+/// serial_number,
+/// validity,
+/// subject,
+/// pub_key,
+/// &signer,
+/// )
+/// .expect("Create certificate");
+/// ```
+pub struct CertificateBuilder<'s, S> {
+ tbs: TbsCertificate,
+ extensions: Extensions,
+ cert_signer: &'s S,
+}
+
+impl<'s, S> CertificateBuilder<'s, S>
+where
+ S: Keypair + DynSignatureAlgorithmIdentifier,
+ S::VerifyingKey: EncodePublicKey,
+{
+ /// Creates a new certificate builder
+ pub fn new(
+ profile: Profile,
+ serial_number: SerialNumber,
+ mut validity: Validity,
+ subject: Name,
+ subject_public_key_info: SubjectPublicKeyInfoOwned,
+ cert_signer: &'s S,
+ ) -> Result<Self> {
+ let verifying_key = cert_signer.verifying_key();
+ let signer_pub = verifying_key
+ .to_public_key_der()?
+ .decode_msg::<SubjectPublicKeyInfoOwned>()?;
+
+ let signature_alg = cert_signer.signature_algorithm_identifier()?;
+ let issuer = profile.get_issuer(&subject);
+
+ validity.not_before.rfc5280_adjust_utc_time()?;
+ validity.not_after.rfc5280_adjust_utc_time()?;
+
+ let tbs = TbsCertificate {
+ version: Version::V3,
+ serial_number,
+ signature: signature_alg,
+ issuer,
+ validity,
+ subject,
+ subject_public_key_info,
+ extensions: None,
+
+ // We will not generate unique identifier because as per RFC5280 Section 4.1.2.8:
+ // CAs conforming to this profile MUST NOT generate
+ // certificates with unique identifiers.
+ //
+ // https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.8
+ issuer_unique_id: None,
+ subject_unique_id: None,
+ };
+
+ let extensions = profile.build_extensions(
+ tbs.subject_public_key_info.owned_to_ref(),
+ signer_pub.owned_to_ref(),
+ &tbs,
+ )?;
+ Ok(Self {
+ tbs,
+ extensions,
+ cert_signer,
+ })
+ }
+
+ /// Add an extension to this certificate
+ pub fn add_extension<E: AsExtension>(&mut self, extension: &E) -> Result<()> {
+ let ext = extension.to_extension(&self.tbs.subject, &self.extensions)?;
+ self.extensions.push(ext);
+
+ Ok(())
+ }
+}
+
+/// Builder for X509 Certificate Requests
+///
+/// ```
+/// # use p256::{pkcs8::DecodePrivateKey, NistP256, ecdsa::DerSignature};
+/// # const PKCS8_PRIVATE_KEY_DER: &[u8] = include_bytes!("../tests/examples/p256-priv.der");
+/// # fn ecdsa_signer() -> ecdsa::SigningKey<NistP256> {
+/// # let secret_key = p256::SecretKey::from_pkcs8_der(PKCS8_PRIVATE_KEY_DER).unwrap();
+/// # ecdsa::SigningKey::from(secret_key)
+/// # }
+/// use x509_cert::{
+/// builder::{Builder, RequestBuilder},
+/// ext::pkix::{name::GeneralName, SubjectAltName},
+/// name::Name,
+/// };
+/// use std::str::FromStr;
+///
+/// use std::net::{IpAddr, Ipv4Addr};
+/// let subject = Name::from_str("CN=service.domination.world").unwrap();
+///
+/// let signer = ecdsa_signer();
+/// let mut builder = RequestBuilder::new(subject, &signer).expect("Create certificate request");
+/// builder
+/// .add_extension(&SubjectAltName(vec![GeneralName::from(IpAddr::V4(
+/// Ipv4Addr::new(192, 0, 2, 0),
+/// ))]))
+/// .unwrap();
+///
+/// let cert_req = builder.build::<DerSignature>().unwrap();
+/// ```
+pub struct RequestBuilder<'s, S> {
+ info: CertReqInfo,
+ extension_req: ExtensionReq,
+ req_signer: &'s S,
+}
+
+impl<'s, S> RequestBuilder<'s, S>
+where
+ S: Keypair + DynSignatureAlgorithmIdentifier,
+ S::VerifyingKey: EncodePublicKey,
+{
+ /// Creates a new certificate request builder
+ pub fn new(subject: Name, req_signer: &'s S) -> Result<Self> {
+ let version = Default::default();
+ let verifying_key = req_signer.verifying_key();
+ let public_key = verifying_key
+ .to_public_key_der()?
+ .decode_msg::<SubjectPublicKeyInfoOwned>()?;
+ let attributes = Default::default();
+ let extension_req = Default::default();
+
+ Ok(Self {
+ info: CertReqInfo {
+ version,
+ subject,
+ public_key,
+ attributes,
+ },
+ extension_req,
+ req_signer,
+ })
+ }
+
+ /// Add an extension to this certificate request
+ pub fn add_extension<E: AsExtension>(&mut self, extension: &E) -> Result<()> {
+ let ext = extension.to_extension(&self.info.subject, &self.extension_req.0)?;
+
+ self.extension_req.0.push(ext);
+
+ Ok(())
+ }
+
+ /// Add an attribute to this certificate request
+ pub fn add_attribute<A: AsAttribute>(&mut self, attribute: &A) -> Result<()> {
+ let attr = attribute.to_attribute()?;
+
+ self.info.attributes.insert(attr)?;
+ Ok(())
+ }
+}
+
+/// Trait for X509 builders
+///
+/// This trait defines the interface between builder and the signers.
+pub trait Builder: Sized {
+ /// The builder's object signer
+ type Signer;
+
+ /// Type built by this builder
+ type Output: Sized;
+
+ /// Return a reference to the signer.
+ fn signer(&self) -> &Self::Signer;
+
+ /// Assemble the final object from signature.
+ fn assemble(self, signature: BitString) -> Result<Self::Output>;
+
+ /// Finalize and return a serialization of the object for signature.
+ fn finalize(&mut self) -> der::Result<vec::Vec<u8>>;
+
+ /// Run the object through the signer and build it.
+ fn build<Signature>(mut self) -> Result<Self::Output>
+ where
+ Self::Signer: Signer<Signature>,
+ Signature: SignatureBitStringEncoding,
+ {
+ let blob = self.finalize()?;
+
+ let signature = self.signer().try_sign(&blob)?.to_bitstring()?;
+
+ self.assemble(signature)
+ }
+
+ /// Run the object through the signer and build it.
+ fn build_with_rng<Signature>(mut self, rng: &mut impl CryptoRngCore) -> Result<Self::Output>
+ where
+ Self::Signer: RandomizedSigner<Signature>,
+ Signature: SignatureBitStringEncoding,
+ {
+ let blob = self.finalize()?;
+
+ let signature = self
+ .signer()
+ .try_sign_with_rng(rng, &blob)?
+ .to_bitstring()?;
+
+ self.assemble(signature)
+ }
+}
+
+impl<'s, S> Builder for CertificateBuilder<'s, S>
+where
+ S: Keypair + DynSignatureAlgorithmIdentifier,
+ S::VerifyingKey: EncodePublicKey,
+{
+ type Signer = S;
+ type Output = Certificate;
+
+ fn signer(&self) -> &Self::Signer {
+ self.cert_signer
+ }
+
+ fn finalize(&mut self) -> der::Result<vec::Vec<u8>> {
+ if !self.extensions.is_empty() {
+ self.tbs.extensions = Some(self.extensions.clone());
+ }
+
+ if self.tbs.extensions.is_none() {
+ if self.tbs.issuer_unique_id.is_some() || self.tbs.subject_unique_id.is_some() {
+ self.tbs.version = Version::V2;
+ } else {
+ self.tbs.version = Version::V1;
+ }
+ }
+
+ self.tbs.to_der()
+ }
+
+ fn assemble(self, signature: BitString) -> Result<Self::Output> {
+ let signature_algorithm = self.tbs.signature.clone();
+
+ Ok(Certificate {
+ tbs_certificate: self.tbs,
+ signature_algorithm,
+ signature,
+ })
+ }
+}
+
+impl<'s, S> Builder for RequestBuilder<'s, S>
+where
+ S: Keypair + DynSignatureAlgorithmIdentifier,
+ S::VerifyingKey: EncodePublicKey,
+{
+ type Signer = S;
+ type Output = CertReq;
+
+ fn signer(&self) -> &Self::Signer {
+ self.req_signer
+ }
+
+ fn finalize(&mut self) -> der::Result<vec::Vec<u8>> {
+ self.info
+ .attributes
+ .insert(self.extension_req.clone().try_into()?)?;
+
+ self.info.to_der()
+ }
+
+ fn assemble(self, signature: BitString) -> Result<Self::Output> {
+ let algorithm = self.req_signer.signature_algorithm_identifier()?;
+
+ Ok(CertReq {
+ info: self.info,
+ algorithm,
+ signature,
+ })
+ }
+}