From 42dd20fdd17567a4bb73b716e97c86e985b7280c Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Thu, 14 Dec 2023 11:11:01 -0800 Subject: [PATCH] x509-cert: introduce a `builder::AsyncBuilder` trait --- Cargo.lock | 102 +++++++++++++++++++++++++++++++++++++ x509-cert/Cargo.toml | 4 +- x509-cert/src/builder.rs | 84 ++++++++++++++++++++++++++++++ x509-cert/tests/builder.rs | 28 +++++++++- 4 files changed, 216 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18ddf0ad4..f3d81ebd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aes" version = "0.8.3" @@ -86,12 +101,36 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "async-signature" +version = "0.6.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6e5fa81ca8ec32e14b1245e1b64370c3999d8550aae98adac787415303871f8" +dependencies = [ + "signature", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base16ct" version = "0.2.0" @@ -223,6 +262,15 @@ dependencies = [ "cipher", ] +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -711,6 +759,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "glob" version = "0.3.1" @@ -878,6 +932,15 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -926,6 +989,15 @@ dependencies = [ "libm", ] +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -1281,6 +1353,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc_version" version = "0.4.0" @@ -1642,6 +1720,28 @@ dependencies = [ "trybuild", ] +[[package]] +name = "tokio" +version = "1.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +dependencies = [ + "backtrace", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "toml" version = "0.8.8" @@ -1866,6 +1966,7 @@ name = "x509-cert" version = "0.3.0-pre" dependencies = [ "arbitrary", + "async-signature", "const-oid", "der", "ecdsa", @@ -1880,6 +1981,7 @@ dependencies = [ "spki", "tempfile", "tls_codec", + "tokio", "x509-cert-test-support", ] diff --git a/x509-cert/Cargo.toml b/x509-cert/Cargo.toml index b6d7b2131..5866e7cee 100644 --- a/x509-cert/Cargo.toml +++ b/x509-cert/Cargo.toml @@ -21,6 +21,7 @@ spki = { version = "=0.8.0-pre.0", features = ["alloc"] } # optional dependencies arbitrary = { version = "1.3", features = ["derive"], optional = true } +async-signature = { version = "=0.6.0-pre.0", features = ["digest", "rand_core"], optional = true } sha1 = { version = "0.11.0-pre.2", optional = true } signature = { version = "=2.3.0-pre.2", features = ["rand_core"], optional = true } tls_codec = { version = "0.4.0", default-features = false, features = ["derive"], optional = true } @@ -34,6 +35,7 @@ p256 = "=0.14.0-pre.0" rstest = "0.18" sha2 = { version = "=0.11.0-pre.2", features = ["oid"] } tempfile = "3.5.0" +tokio = { version = "1.35.0", features = ["macros", "rt"] } x509-cert-test-support = { path = "./test-support" } [features] @@ -41,7 +43,7 @@ default = ["pem", "std"] std = ["const-oid/std", "der/std", "spki/std", "tls_codec?/std"] arbitrary = ["dep:arbitrary", "std", "der/arbitrary", "spki/arbitrary"] -builder = ["std", "sha1/default", "signature"] +builder = ["async-signature", "std", "sha1/default", "signature"] hazmat = [] pem = ["der/pem", "spki/pem"] sct = ["dep:tls_codec"] diff --git a/x509-cert/src/builder.rs b/x509-cert/src/builder.rs index a637f8c5c..ba83db0d0 100644 --- a/x509-cert/src/builder.rs +++ b/x509-cert/src/builder.rs @@ -1,6 +1,7 @@ //! X509 Certificate builder use alloc::vec; +use async_signature::{AsyncRandomizedSigner, AsyncSigner}; use core::fmt; use der::{asn1::BitString, referenced::OwnedToRef, Encode}; use signature::{rand_core::CryptoRngCore, Keypair, RandomizedSigner, Signer}; @@ -538,3 +539,86 @@ impl Builder for RequestBuilder { }) } } + +/// Trait for async X509 builders +/// +/// This trait defines the interface between builder and the signers. +/// +/// This is the async counterpart of [`Builder`]. +#[allow(async_fn_in_trait)] +pub trait AsyncBuilder: Sized { + /// Type built by this builder + type Output: Sized; + + /// Assemble the final object from signature. + fn assemble(self, signature: BitString, signer: &S) -> Result + where + S: Keypair + DynSignatureAlgorithmIdentifier, + S::VerifyingKey: EncodePublicKey; + + /// Finalize and return a serialization of the object for signature. + fn finalize(&mut self, signer: &S) -> Result> + where + S: Keypair + DynSignatureAlgorithmIdentifier, + S::VerifyingKey: EncodePublicKey; + + /// Run the object through the signer and build it. + async fn build_async(mut self, signer: &S) -> Result + where + S: AsyncSigner, + S: Keypair + DynSignatureAlgorithmIdentifier, + S::VerifyingKey: EncodePublicKey, + Signature: SignatureBitStringEncoding, + { + let blob = self.finalize(signer)?; + + let signature = signer.sign_async(&blob).await?.to_bitstring()?; + + self.assemble(signature, signer) + } + + /// Run the object through the signer and build it. + async fn build_with_rng_async( + mut self, + signer: &S, + rng: &mut impl CryptoRngCore, + ) -> Result + where + S: AsyncRandomizedSigner, + S: Keypair + DynSignatureAlgorithmIdentifier, + S::VerifyingKey: EncodePublicKey, + Signature: SignatureBitStringEncoding, + { + let blob = self.finalize(signer)?; + + let signature = signer + .try_sign_with_rng_async(rng, &blob) + .await? + .to_bitstring()?; + + self.assemble(signature, signer) + } +} + +impl AsyncBuilder for T +where + T: Builder, +{ + type Output = ::Output; + + fn assemble(self, signature: BitString, signer: &S) -> Result + where + S: Keypair + DynSignatureAlgorithmIdentifier, + S::VerifyingKey: EncodePublicKey, + { + ::assemble(self, signature, signer) + } + + fn finalize(&mut self, signer: &S) -> Result> + where + S: Keypair + DynSignatureAlgorithmIdentifier, + S::VerifyingKey: EncodePublicKey, + { + ::finalize(self, signer) + } +} diff --git a/x509-cert/tests/builder.rs b/x509-cert/tests/builder.rs index 8ae1de912..f1adfb682 100644 --- a/x509-cert/tests/builder.rs +++ b/x509-cert/tests/builder.rs @@ -9,7 +9,7 @@ use sha2::Sha256; use spki::SubjectPublicKeyInfoOwned; use std::{str::FromStr, time::Duration}; use x509_cert::{ - builder::{Builder, CertificateBuilder, Profile, RequestBuilder}, + builder::{AsyncBuilder, Builder, CertificateBuilder, Profile, RequestBuilder}, ext::pkix::{ name::{DirectoryString, GeneralName}, SubjectAltName, @@ -333,3 +333,29 @@ fn dynamic_signer() { println!("{}", csr_pem); } + +#[tokio::test] +async fn async_builder() { + 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() + .to_der() + .unwrap(); + let subject = Name::from_der(&subject).unwrap(); + let pub_key = + SubjectPublicKeyInfoOwned::try_from(PKCS8_PUBLIC_KEY_DER).expect("get ecdsa pub key"); + + let signer = ecdsa_signer(); + let builder = CertificateBuilder::new(profile, serial_number, validity, subject, pub_key) + .expect("Create certificate"); + + let certificate = builder + .build_async::<_, DerSignature>(&signer) + .await + .unwrap(); + + let pem = certificate.to_pem(LineEnding::LF).expect("generate pem"); + println!("{}", openssl::check_certificate(pem.as_bytes())); +}