| //! 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 = SubjectPublicKeyInfoOwned::from_key(verifying_key)?; |
| |
| 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 = SubjectPublicKeyInfoOwned::from_key(verifying_key)?; |
| 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, |
| }) |
| } |
| } |