Skip to content

Commit

Permalink
refactor: adjust certificate of acme and self signed
Browse files Browse the repository at this point in the history
  • Loading branch information
vicanso committed Dec 20, 2024
1 parent 32efe09 commit 69e7f37
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 267 deletions.
3 changes: 2 additions & 1 deletion src/certificate/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ const R10: &[u8] = include_bytes!("../assets/r10.pem");
const R11: &[u8] = include_bytes!("../assets/r11.pem");

fn parse_chain_certificate(data: &[u8]) -> Option<X509> {
let expired = util::now().as_secs() + 30 * 24 * 3600;
if let Ok(info) = Certificate::new(
std::string::String::from_utf8_lossy(data).to_string(),
"".to_string(),
) {
if info.not_after > util::now().as_secs() as i64 {
if info.not_after > expired as i64 {
return X509::from_pem(data).ok();
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/certificate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

mod chain;
mod self_signed;
mod tls_certificate;
mod validity_checker;

#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("X509 error, category: {category}, {message}"))]
X509 { category: String, message: String },
#[snafu(display("Invalid error, category: {category}, {message}"))]
Invalid { message: String, category: String },
}

type Result<T, E = Error> = std::result::Result<T, E>;
Expand Down Expand Up @@ -118,9 +121,6 @@ impl Certificate {
}
}

pub use chain::get_lets_encrypt_chain_certificate;
pub use self_signed::{
add_self_signed_certificate, get_self_signed_certificate,
new_self_signed_certificate_validity_service, SelfSignedCertificate,
};
pub use self_signed::new_self_signed_certificate_validity_service;
pub use tls_certificate::TlsCertificate;
pub use validity_checker::new_certificate_validity_service;
16 changes: 16 additions & 0 deletions src/certificate/self_signed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

use crate::service::SimpleServiceTaskFuture;
use crate::util;
use ahash::AHashMap;
use arc_swap::ArcSwap;
use once_cell::sync::Lazy;
Expand All @@ -26,6 +27,7 @@ pub struct SelfSignedCertificate {
pub key: PKey<Private>,
stale: AtomicBool,
count: AtomicU32,
not_after: i64,
}

type SelfSignedCertificateMap = AHashMap<String, Arc<SelfSignedCertificate>>;
Expand All @@ -39,15 +41,26 @@ async fn do_self_signed_certificate_validity(count: u32) -> Result<(), String> {
return Ok(());
}
let mut m = AHashMap::new();

// two days
let expired = (util::now().as_secs() - 2 * 24 * 3600) as i64;

for (k, v) in SELF_SIGNED_CERTIFICATE_MAP.load().iter() {
// certificate is expired
if v.not_after < expired {
continue;
}
let count = v.count.load(Ordering::Relaxed);
let stale = v.stale.load(Ordering::Relaxed);
// certificate is stale and use count is 0
if stale && count == 0 {
continue;
}
if count == 0 {
// set stale
v.stale.store(true, Ordering::Relaxed);
} else {
// reset
v.stale.store(false, Ordering::Relaxed);
v.count.store(0, Ordering::Relaxed);
}
Expand Down Expand Up @@ -76,10 +89,12 @@ pub fn get_self_signed_certificate(
None
}

// Add self signed certificate to global map
pub fn add_self_signed_certificate(
name: &str,
x509: X509,
key: PKey<Private>,
not_after: i64,
) -> Arc<SelfSignedCertificate> {
let mut m = AHashMap::new();
for (k, v) in SELF_SIGNED_CERTIFICATE_MAP.load().iter() {
Expand All @@ -88,6 +103,7 @@ pub fn add_self_signed_certificate(
let v = Arc::new(SelfSignedCertificate {
x509,
key,
not_after,
stale: AtomicBool::new(false),
count: AtomicU32::new(0),
});
Expand Down
206 changes: 206 additions & 0 deletions src/certificate/tls_certificate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
// Copyright 2024 Tree xie.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use super::chain::get_lets_encrypt_chain_certificate;
use super::self_signed::{
add_self_signed_certificate, get_self_signed_certificate,
SelfSignedCertificate,
};
use super::{Certificate, Error, Result};
use crate::config::CertificateConf;
use crate::util;
use pingora::tls::pkey::{PKey, Private};
use pingora::tls::x509::X509;
use std::sync::Arc;
use tracing::info;

#[derive(Debug, Clone, Default)]
pub struct TlsCertificate {
pub name: Option<String>,
pub chain_certificate: Option<X509>,
pub certificate: Option<(X509, PKey<Private>)>,
pub domains: Vec<String>,
pub info: Option<Certificate>,
pub hash_key: String,
pub is_ca: bool,
}

impl TryFrom<&CertificateConf> for TlsCertificate {
type Error = Error;
fn try_from(value: &CertificateConf) -> Result<Self, Self::Error> {
// parse certificate
let info = Certificate::new(
value.tls_cert.clone().unwrap_or_default(),
value.tls_key.clone().unwrap_or_default(),
)
.map_err(|e| Error::Invalid {
message: e.to_string(),
category: "certificate".to_string(),
})?;
let category = if value.acme.is_some() {
LETS_ENCRYPT
} else {
""
};

let hash_key = value.hash_key();

let tls_chain = util::convert_certificate_bytes(&value.tls_chain);
let chain_certificate = if let Some(value) = &tls_chain {
// ignore chain error
X509::from_pem(value).ok()
} else if category == LETS_ENCRYPT {
get_lets_encrypt_chain_certificate(
info.get_issuer_common_name().as_str(),
)
} else {
None
};
let cert =
X509::from_pem(&info.get_cert()).map_err(|e| Error::Invalid {
category: "x509_from_pem".to_string(),
message: e.to_string(),
})?;

let key = PKey::private_key_from_pem(&info.get_key()).map_err(|e| {
Error::Invalid {
category: "private_key_from_pem".to_string(),
message: e.to_string(),
}
})?;
Ok(TlsCertificate {
hash_key,
chain_certificate,
domains: info.domains.clone(),
certificate: Some((cert, key)),
info: Some(info),
is_ca: value.is_ca.unwrap_or_default(),
..Default::default()
})
}
}

static LETS_ENCRYPT: &str = "lets_encrypt";

fn new_certificate_with_ca(
root_ca: &TlsCertificate,
cn: &str,
) -> Result<(X509, PKey<Private>, i64)> {
let Some(info) = &root_ca.info else {
return Err(Error::Invalid {
message: "root ca is invalid".to_string(),
category: "ca".to_string(),
});
};
let binding = info.get_cert();
let ca_pem = std::string::String::from_utf8_lossy(&binding);

let ca_params = rcgen::CertificateParams::from_ca_cert_pem(&ca_pem)
.map_err(|e| Error::Invalid {
message: e.to_string(),
category: "parse_ca".to_string(),
})?;

let binding = info.get_key();
let ca_key = std::string::String::from_utf8_lossy(&binding);

let ca_kp =
rcgen::KeyPair::from_pem(&ca_key).map_err(|e| Error::Invalid {
message: e.to_string(),
category: "parse_ca_key".to_string(),
})?;
let not_before = time::OffsetDateTime::now_utc() - time::Duration::days(1);
let two_years_from_now =
time::OffsetDateTime::now_utc() + time::Duration::days(365 * 2);
let not_after = ca_params.not_after.min(two_years_from_now);
let ca_cert =
ca_params.self_signed(&ca_kp).map_err(|e| Error::Invalid {
message: e.to_string(),
category: "self_sigined_ca".to_string(),
})?;

let mut params = rcgen::CertificateParams::new(vec![cn.to_string()])
.map_err(|e| Error::Invalid {
message: e.to_string(),
category: "new_cert_params".to_string(),
})?;
let mut dn = rcgen::DistinguishedName::new();
dn.push(rcgen::DnType::CommonName, cn.to_string());
if let Some(organ) = ca_cert
.params()
.distinguished_name
.get(&rcgen::DnType::OrganizationName)
{
dn.push(rcgen::DnType::OrganizationName, organ.clone());
};
if let Some(unit) = ca_cert
.params()
.distinguished_name
.get(&rcgen::DnType::OrganizationalUnitName)
{
dn.push(rcgen::DnType::OrganizationalUnitName, unit.clone());
};

params.distinguished_name = dn;
params.not_before = not_before;
params.not_after = not_after;

let cert_key = rcgen::KeyPair::generate().map_err(|e| Error::Invalid {
message: e.to_string(),
category: "key_pair_generate".to_string(),
})?;

let cert = params.signed_by(&cert_key, &ca_cert, &ca_kp).map_err(|e| {
Error::Invalid {
message: e.to_string(),
category: "signed_by_ca".to_string(),
}
})?;

let cert =
X509::from_pem(cert.pem().as_bytes()).map_err(|e| Error::Invalid {
category: "x509_from_pem".to_string(),
message: e.to_string(),
})?;

let key = PKey::private_key_from_pem(cert_key.serialize_pem().as_bytes())
.map_err(|e| Error::Invalid {
category: "private_key_from_pem".to_string(),
message: e.to_string(),
})?;

Ok((cert, key, not_after.unix_timestamp()))
}

impl TlsCertificate {
/// Get self signed certificate
pub fn get_self_signed_certtificate(
&self,
server_name: &str,
) -> Result<Arc<SelfSignedCertificate>> {
let arr: Vec<&str> = server_name.split('.').collect();
let cn = if arr.len() > 2 {
format!("*.{}", arr[1..].join("."))
} else {
server_name.to_string()
};
let k = format!("{:?}:{}", self.name, cn);
if let Some(v) = get_self_signed_certificate(&k) {
return Ok(v);
}
let (cert, key, not_after) = new_certificate_with_ca(self, &cn)?;
info!(common_name = cn, "new self sigined cert",);
Ok(add_self_signed_certificate(&k, cert, key, not_after))
}
}
Loading

0 comments on commit 69e7f37

Please sign in to comment.