From 4aaec3aa9513413e0d7cf5432b0bd8325a50e0e2 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sat, 30 Mar 2024 11:07:17 +0800 Subject: [PATCH] feat: issue certificate success --- Cargo.lock | 251 +++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 + src/cert.rs | 109 ++++++++++++++++++++++ src/main.rs | 6 +- src/main2.rs | 64 ------------- 5 files changed, 368 insertions(+), 66 deletions(-) create mode 100644 src/cert.rs delete mode 100644 src/main2.rs diff --git a/Cargo.lock b/Cargo.lock index ac09e94..f247be9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,6 +179,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.21.7" @@ -191,6 +197,12 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" @@ -309,6 +321,12 @@ dependencies = [ "cc", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "core-foundation" version = "0.9.4" @@ -325,6 +343,15 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.4.0" @@ -358,6 +385,18 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -393,6 +432,17 @@ dependencies = [ "uuid", ] +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "der-parser" version = "8.2.0" @@ -423,6 +473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -438,6 +489,41 @@ dependencies = [ "syn 2.0.55", ] +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.33" @@ -466,6 +552,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "flate2" version = "1.0.28" @@ -604,6 +700,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -623,6 +720,17 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "h2" version = "0.3.25" @@ -707,6 +815,24 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "hostname" version = "0.3.1" @@ -1129,6 +1255,44 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core", + "sha2", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -1168,6 +1332,15 @@ dependencies = [ "serde", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1402,6 +1575,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.30" @@ -1430,6 +1613,15 @@ dependencies = [ "log", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1492,6 +1684,10 @@ dependencies = [ "base64 0.22.0", "http 1.1.0", "log", + "once_cell", + "p256", + "p384", + "p521", "pingora", "pretty_env_logger", "rcgen", @@ -1632,6 +1828,16 @@ dependencies = [ "winreg", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.16.20" @@ -1798,6 +2004,20 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "1.0.22" @@ -1950,6 +2170,17 @@ dependencies = [ "rust_decimal", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1959,6 +2190,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "slab" version = "0.4.9" @@ -1996,6 +2237,16 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "strsim" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 32798a6..5518432 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,7 @@ structopt = "0.3" base64 = "0.22" rcgen = { version = "0.12", features = ["zeroize", "x509-parser"] } time = "0.3" +p256 = "0.13.2" +p384 = "0.13.0" +p521 = "0.13.3" +once_cell = "1.19.0" diff --git a/src/cert.rs b/src/cert.rs new file mode 100644 index 0000000..1840a15 --- /dev/null +++ b/src/cert.rs @@ -0,0 +1,109 @@ +use std::collections::HashMap; +use std::fs; +use std::sync::RwLock; + +use once_cell::sync::Lazy; +use rcgen::{Certificate, CertificateParams, DnType, ExtendedKeyUsagePurpose, IsCa, KeyPair, KeyUsagePurpose}; +use time::{Duration, OffsetDateTime}; + +const INTERMEDIATE_CERT_ENV_VAR: &str = "INTERMEDIATE_CERT"; +const INTERMEDIATE_KEY_ENV_VAR: &str = "INTERMEDIATE_KEY"; + +static INTERMEDIATE_CA: Lazy = Lazy::new(|| { + let cert_fn = std::env::var(INTERMEDIATE_CERT_ENV_VAR) + .unwrap_or("__ignore_intermediate_cert.pem".to_string()); + let key_fn = std::env::var(INTERMEDIATE_KEY_ENV_VAR) + .unwrap_or("__ignore_intermediate_pri_key.pem".to_string()); + let cert_pem = fs::read_to_string(cert_fn).expect("Read cert file failed"); + let key_pem = fs::read_to_string(key_fn).expect("Read key file failed"); + let key_pem = parse_pkcs8(&key_pem); + let key_pair = KeyPair::from_pem(&key_pem).expect("Parse keypair failed"); + // 底层逻辑限制,P256 与 SHA256 搭配,P384 与 SHA384 搭配 + let certificate_params = CertificateParams::from_ca_cert_pem(&cert_pem, key_pair) + .expect("Cert and keypair mismatch"); + Certificate::from_params(certificate_params) + .expect("Parse cert params failed") +}); + +#[derive(Debug, Clone)] +pub struct Cert { + pub cert_pem: String, + pub intermediate_pem: String, + pub key_pem: String, +} + +static CERTIFICATE_CACHE_MAP: Lazy>> = Lazy::new(|| { + RwLock::new(HashMap::new()) +}); + +pub fn issue_certificate(domain: &str) -> Cert { + { + if let Some(cert) = CERTIFICATE_CACHE_MAP.read().unwrap().get(domain) { + return cert.clone(); + } + } + let cert = new_end_entity(domain); + let cert_pem = cert.serialize_pem_with_signer(&INTERMEDIATE_CA).expect("Sign cert failed"); + let key_pem = cert.serialize_private_key_pem(); + let intermediate_pem = INTERMEDIATE_CA.serialize_pem().expect("Ser intermediate cert failed"); + let cert = Cert { + cert_pem, + intermediate_pem, + key_pem, + }; + { + CERTIFICATE_CACHE_MAP.write().unwrap().insert(domain.to_string(), cert.clone()); + } + cert +} + +fn parse_pkcs8(pem: &str) -> String { + { + use p256::{pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding}, SecretKey}; + let secret_key = SecretKey::from_pkcs8_pem(pem); + if let Ok(secret_key) = secret_key { + if let Ok(pem) = secret_key.to_pkcs8_pem(LineEnding::CR) { + return pem.to_string(); + } + } + } + { + use p384::{pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding}, SecretKey}; + let secret_key = SecretKey::from_pkcs8_pem(pem); + if let Ok(secret_key) = secret_key { + if let Ok(pem) = secret_key.to_pkcs8_pem(LineEnding::CR) { + return pem.to_string(); + } + } + } + { + use p521::{pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding}, SecretKey}; + let secret_key = SecretKey::from_pkcs8_pem(pem); + if let Ok(secret_key) = secret_key { + if let Ok(pem) = secret_key.to_pkcs8_pem(LineEnding::CR) { + return pem.to_string(); + } + } + } + pem.to_string() +} + +fn new_end_entity(domain: &str) -> Certificate { + let mut params = CertificateParams::new(vec![domain.into()]); + let (start, end) = validity_period(); + params.distinguished_name.push(DnType::CommonName, domain); + params.use_authority_key_identifier_extension = true; + params.key_usages.push(KeyUsagePurpose::DigitalSignature); + params.is_ca = IsCa::NoCa; + params.extended_key_usages.push(ExtendedKeyUsagePurpose::ServerAuth); + params.extended_key_usages.push(ExtendedKeyUsagePurpose::ClientAuth); + params.not_before = start; + params.not_after = end; + Certificate::from_params(params).expect("Generate domain certificate failed") +} + +fn validity_period() -> (OffsetDateTime, OffsetDateTime) { + let start = OffsetDateTime::now_utc().checked_sub(Duration::hours(1)).unwrap(); + let end = OffsetDateTime::now_utc().checked_add(Duration::days(90)).unwrap(); + (start, end) +} diff --git a/src/main.rs b/src/main.rs index e257375..3cd906b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,11 +8,13 @@ use structopt::StructOpt; mod app; mod service; -mod main2; +mod cert; pub fn main() { init_logger(); - main2::test_main(); + let cert = cert::issue_certificate("example.com"); + println!("{:#?}", cert); + println!("{}", cert.cert_pem); panic!("END"); let opt = Some(Opt::from_args()); diff --git a/src/main2.rs b/src/main2.rs deleted file mode 100644 index 7face26..0000000 --- a/src/main2.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::fs; - -use rcgen::{BasicConstraints, Certificate, CertificateParams, DnType, DnValue::PrintableString, ExtendedKeyUsagePurpose, IsCa, KeyPair, KeyUsagePurpose}; -use time::{Duration, OffsetDateTime}; - -/// Example demonstrating signing end-endity certificate with ca -pub fn test_main() { - let ca_pem = fs::read_to_string("__ignore_intermediate_cert.pem").unwrap(); - let key_pem = fs::read_to_string("__ignore_intermediate_pri_key.pem").unwrap(); - let k = KeyPair::from_pem(&key_pem).unwrap(); - - // let k = KeyPair::from_pem_and_sign_algo(&key_pem, &PKCS_ECDSA_P384_SHA384).unwrap(); - - let certificate_params = CertificateParams::from_ca_cert_pem(&ca_pem, k).unwrap(); - let ca = Certificate::from_params(certificate_params).unwrap(); - - // let ca = new_ca(); - let end_entity = new_end_entity(); - - let end_entity_pem = end_entity.serialize_pem_with_signer(&ca).unwrap(); - println!("directly signed end-entity certificate: {end_entity_pem}"); - let end_entity_key_pem = end_entity.serialize_private_key_pem(); - println!("directly signed end-entity key: {end_entity_key_pem}"); - - let ca_cert_pem = ca.serialize_pem().unwrap(); - println!("ca certificate: {ca_cert_pem}", ); -} - -fn new_ca() -> Certificate { - let mut params = CertificateParams::new(Vec::default()); - let (start, end) = validity_period(); - params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); - params.distinguished_name.get(&DnType::CommonName); - params.distinguished_name.push(DnType::CommonName, "Hatter Test CA"); - params.distinguished_name.push(DnType::CountryName, PrintableString("CN".into())); - params.distinguished_name.push(DnType::OrganizationName, "Hatter Ink"); - params.key_usages.push(KeyUsagePurpose::DigitalSignature); - params.key_usages.push(KeyUsagePurpose::KeyCertSign); - params.key_usages.push(KeyUsagePurpose::CrlSign); - params.not_before = start; - params.not_after = end; - Certificate::from_params(params).unwrap() -} - -fn new_end_entity() -> Certificate { - let name = "demo.example.com"; - let mut params = CertificateParams::new(vec![name.into()]); - let (start, end) = validity_period(); - params.distinguished_name.push(DnType::CommonName, name); - params.use_authority_key_identifier_extension = true; - params.key_usages.push(KeyUsagePurpose::DigitalSignature); - params.is_ca = IsCa::NoCa; - params.extended_key_usages.push(ExtendedKeyUsagePurpose::ServerAuth); - params.extended_key_usages.push(ExtendedKeyUsagePurpose::ClientAuth); - params.not_before = start; - params.not_after = end; - Certificate::from_params(params).unwrap() -} - -fn validity_period() -> (OffsetDateTime, OffsetDateTime) { - let start = OffsetDateTime::now_utc().checked_sub(Duration::hours(1)).unwrap(); - let end = OffsetDateTime::now_utc().checked_add(Duration::days(90)).unwrap(); - (start, end) -}