Skip to content

Commit

Permalink
refactor: adjust chain and self signed certificate
Browse files Browse the repository at this point in the history
  • Loading branch information
vicanso committed Dec 19, 2024
1 parent be8652d commit 32efe09
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 219 deletions.
7 changes: 6 additions & 1 deletion src/acme/lets_encrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use super::{get_token_path, Certificate, Error, Result, LOG_CATEGORY};
use super::{get_token_path, Error, Result, LOG_CATEGORY};
use crate::certificate::Certificate;
use crate::config::{
get_config_storage, get_current_config, load_config, save_config,
LoadConfigOptions, PingapConf, CATEGORY_CERTIFICATE,
Expand Down Expand Up @@ -142,6 +143,10 @@ pub fn get_lets_encrypt_certificate(name: &str) -> Result<Certificate> {
cert.tls_cert.clone().unwrap_or_default(),
cert.tls_key.clone().unwrap_or_default(),
)
.map_err(|e| Error::Fail {
category: "new_certificate".to_string(),
message: e.to_string(),
})
}

/// The proxy plugin for lets encrypt http-01.
Expand Down
98 changes: 1 addition & 97 deletions src/acme/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::util;
use serde::{Deserialize, Serialize};
use snafu::Snafu;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
pub static LOG_CATEGORY: &str = "acme";

#[derive(Debug, Snafu)]
Expand All @@ -34,114 +31,21 @@ pub enum Error {
NotFound { message: String },
#[snafu(display("Lets encrypt fail, category: {category}, {message}"))]
Fail { category: String, message: String },
#[snafu(display("X509 error, category: {category}, {message}"))]
X509 { category: String, message: String },
}

type Result<T, E = Error> = std::result::Result<T, E>;

fn parse_ip_addr(data: &[u8]) -> Result<IpAddr> {
let addr = if data.len() == 4 {
let arr: [u8; 4] = data.try_into().unwrap_or_default();
IpAddr::V4(Ipv4Addr::from(arr))
} else {
let arr: [u8; 16] = data.try_into().unwrap_or_default();
IpAddr::V6(Ipv6Addr::from(arr))
};
Ok(addr)
}

#[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct Certificate {
pub domains: Vec<String>,
pub pem: Vec<u8>,
pub key: Vec<u8>,
pub acme: Option<String>,
pub not_after: i64,
pub not_before: i64,
pub issuer: String,
}
impl Certificate {
pub fn new(pem: String, key: String) -> Result<Certificate> {
let pem_data =
util::convert_certificate_bytes(&Some(pem)).unwrap_or_default();
let (_, p) =
x509_parser::pem::parse_x509_pem(&pem_data).map_err(|e| {
Error::X509 {
category: "parse_x509_pem".to_string(),
message: e.to_string(),
}
})?;
let x509 = p.parse_x509().map_err(|e| Error::X509 {
category: "parse_x509".to_string(),
message: e.to_string(),
})?;
let mut dns_names = vec![];
if let Ok(Some(subject_alternative_name)) =
x509.subject_alternative_name()
{
for item in subject_alternative_name.value.general_names.iter() {
match item {
x509_parser::prelude::GeneralName::DNSName(name) => {
dns_names.push(name.to_string());
},
x509_parser::prelude::GeneralName::IPAddress(data) => {
if let Ok(addr) = parse_ip_addr(data) {
dns_names.push(addr.to_string());
}
},
_ => {},
};
}
};
let validity = x509.validity();
Ok(Self {
domains: dns_names,
pem: pem_data,
key: util::convert_certificate_bytes(&Some(key))
.unwrap_or_default(),
not_after: validity.not_after.timestamp(),
not_before: validity.not_before.timestamp(),
issuer: x509.issuer.to_string(),
..Default::default()
})
}
/// Get the common name of certificate issuer
pub fn get_issuer_common_name(&self) -> String {
let re = regex::Regex::new(r"CN=(?P<CN>[\S ]+?)($|,)").unwrap();
if let Some(caps) = re.captures(&self.issuer) {
return caps["CN"].to_string();
}
"".to_string()
}
/// Validate the cert is within the expiration date.
pub fn valid(&self) -> bool {
let ts = util::now().as_secs() as i64;
self.not_after - ts > 2 * 24 * 3600
}
/// Get the cert pem data.
pub fn get_cert(&self) -> Vec<u8> {
self.pem.clone()
}
/// Get the cert key data.
pub fn get_key(&self) -> Vec<u8> {
self.key.clone()
}
}

pub fn get_token_path(key: &str) -> String {
format!("pingap-acme-tokens/${key}")
}

mod lets_encrypt;
mod validity_checker;

pub use lets_encrypt::{handle_lets_encrypt, new_lets_encrypt_service};
pub use validity_checker::new_certificate_validity_service;

#[cfg(test)]
mod tests {
use super::Certificate;
use crate::certificate::Certificate;
use pretty_assertions::assert_eq;

#[test]
Expand Down
54 changes: 54 additions & 0 deletions src/certificate/chain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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::Certificate;
use crate::util;
use once_cell::sync::Lazy;
use pingora::tls::x509::X509;

// https://letsencrypt.org/certificates/
const E5: &[u8] = include_bytes!("../assets/e5.pem");
const E6: &[u8] = include_bytes!("../assets/e6.pem");
const R10: &[u8] = include_bytes!("../assets/r10.pem");
const R11: &[u8] = include_bytes!("../assets/r11.pem");

fn parse_chain_certificate(data: &[u8]) -> Option<X509> {
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 {
return X509::from_pem(data).ok();
}
}
None
}
static E5_CERTIFICATE: Lazy<Option<X509>> =
Lazy::new(|| parse_chain_certificate(E5));
static E6_CERTIFICATE: Lazy<Option<X509>> =
Lazy::new(|| parse_chain_certificate(E6));
static R10_CERTIFICATE: Lazy<Option<X509>> =
Lazy::new(|| parse_chain_certificate(R10));
static R11_CERTIFICATE: Lazy<Option<X509>> =
Lazy::new(|| parse_chain_certificate(R11));

pub fn get_lets_encrypt_chain_certificate(cn: &str) -> Option<X509> {
match cn {
"E5" => E5_CERTIFICATE.clone(),
"E6" => E6_CERTIFICATE.clone(),
"R10" => R10_CERTIFICATE.clone(),
"R11" => R11_CERTIFICATE.clone(),
_ => None,
}
}
126 changes: 126 additions & 0 deletions src/certificate/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// 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 crate::util;
use serde::{Deserialize, Serialize};
use snafu::Snafu;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

mod chain;
mod self_signed;
mod validity_checker;

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

type Result<T, E = Error> = std::result::Result<T, E>;

fn parse_ip_addr(data: &[u8]) -> Result<IpAddr> {
let addr = if data.len() == 4 {
let arr: [u8; 4] = data.try_into().unwrap_or_default();
IpAddr::V4(Ipv4Addr::from(arr))
} else {
let arr: [u8; 16] = data.try_into().unwrap_or_default();
IpAddr::V6(Ipv6Addr::from(arr))
};
Ok(addr)
}

#[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct Certificate {
pub domains: Vec<String>,
pub pem: Vec<u8>,
pub key: Vec<u8>,
pub acme: Option<String>,
pub not_after: i64,
pub not_before: i64,
pub issuer: String,
}
impl Certificate {
pub fn new(pem: String, key: String) -> Result<Certificate> {
let pem_data =
util::convert_certificate_bytes(&Some(pem)).unwrap_or_default();
let (_, p) =
x509_parser::pem::parse_x509_pem(&pem_data).map_err(|e| {
Error::X509 {
category: "parse_x509_pem".to_string(),
message: e.to_string(),
}
})?;
let x509 = p.parse_x509().map_err(|e| Error::X509 {
category: "parse_x509".to_string(),
message: e.to_string(),
})?;
let mut dns_names = vec![];
if let Ok(Some(subject_alternative_name)) =
x509.subject_alternative_name()
{
for item in subject_alternative_name.value.general_names.iter() {
match item {
x509_parser::prelude::GeneralName::DNSName(name) => {
dns_names.push(name.to_string());
},
x509_parser::prelude::GeneralName::IPAddress(data) => {
if let Ok(addr) = parse_ip_addr(data) {
dns_names.push(addr.to_string());
}
},
_ => {},
};
}
};
let validity = x509.validity();
Ok(Self {
domains: dns_names,
pem: pem_data,
key: util::convert_certificate_bytes(&Some(key))
.unwrap_or_default(),
not_after: validity.not_after.timestamp(),
not_before: validity.not_before.timestamp(),
issuer: x509.issuer.to_string(),
..Default::default()
})
}
/// Get the common name of certificate issuer
pub fn get_issuer_common_name(&self) -> String {
let re = regex::Regex::new(r"CN=(?P<CN>[\S ]+?)($|,)").unwrap();
if let Some(caps) = re.captures(&self.issuer) {
return caps["CN"].to_string();
}
"".to_string()
}
/// Validate the cert is within the expiration date.
pub fn valid(&self) -> bool {
let ts = util::now().as_secs() as i64;
self.not_after - ts > 2 * 24 * 3600
}
/// Get the cert pem data.
pub fn get_cert(&self) -> Vec<u8> {
self.pem.clone()
}
/// Get the cert key data.
pub fn get_key(&self) -> Vec<u8> {
self.key.clone()
}
}

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 validity_checker::new_certificate_validity_service;
Loading

0 comments on commit 32efe09

Please sign in to comment.