90 lines
3.7 KiB
Rust
90 lines
3.7 KiB
Rust
use std::fs;
|
||
|
||
use rcgen::{
|
||
Certificate, CertificateParams, DnType,
|
||
ExtendedKeyUsagePurpose, IsCa, KeyPair, KeyUsagePurpose,
|
||
};
|
||
use time::{Duration, OffsetDateTime};
|
||
|
||
#[derive(Debug, Clone)]
|
||
pub struct Cert {
|
||
pub cert_pem: String,
|
||
pub key_pem: String,
|
||
}
|
||
|
||
pub fn load_certificate(cert_fn: &str, key_fn: &str) -> Result<(Certificate, String), String> {
|
||
let cert_pem = fs::read_to_string(cert_fn).map_err(|e| format!("Read file: {} failed: {}", cert_fn, e))?;
|
||
let key_pem = fs::read_to_string(key_fn).map_err(|e| format!("Read file: {} failed: {}", key_fn, e))?;
|
||
let key_pem = parse_pkcs8(&key_pem);
|
||
let key_pair = KeyPair::from_pem(&key_pem).map_err(|e| format!("Parse key: {} failed: {}", key_fn, e))?;
|
||
// 底层逻辑限制,P256 与 SHA256 搭配,P384 与 SHA384 搭配
|
||
let certificate_params = CertificateParams::from_ca_cert_pem(&cert_pem, key_pair)
|
||
.map_err(|e| format!("Cert and keypair match failed: {}", e))?;
|
||
let cert = Certificate::from_params(certificate_params)
|
||
.map_err(|e| format!("Parse cert params failed: {}", e))?;
|
||
Ok((cert, cert_pem))
|
||
}
|
||
|
||
pub fn issue_certificate(intermediate_certificate: &Certificate, domain: &str) -> Result<Cert, String> {
|
||
let cert = new_end_entity(domain)?;
|
||
log::info!("New certificate for: {} -> {}", domain, hex::encode(cert.get_key_identifier()));
|
||
let cert_pem = cert.serialize_pem_with_signer(intermediate_certificate).map_err(|e| format!("Sign cert failed: {}", e))?;
|
||
let key_pem = cert.serialize_private_key_pem();
|
||
Ok(Cert {
|
||
cert_pem,
|
||
key_pem,
|
||
})
|
||
}
|
||
|
||
fn parse_pkcs8(pem: &str) -> String {
|
||
// KeyPair only support PKCS#8 private key with public key, though public key is optional
|
||
{
|
||
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) -> Result<Certificate, String> {
|
||
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).map_err(|e| format!("New cert failed: {}", e))
|
||
}
|
||
|
||
fn validity_period() -> Result<(OffsetDateTime, OffsetDateTime), String> {
|
||
let start = OffsetDateTime::now_utc().checked_sub(Duration::hours(1)).expect("SHOULD NOT HAPPEN!");
|
||
let end = OffsetDateTime::now_utc().checked_add(Duration::days(90)).expect("SHOULD NOT HAPPEN!");
|
||
Ok((start, end))
|
||
}
|