現在免費證書只能申請三個月(之前還能申請十二個月),擁有acme能力對於小的站點來說就比較需要,可以比較好的部署也不用關心TLS帶來的煩惱。 ...
wmproxy
wmproxy
已用Rust
實現http/https
代理, socks5
代理, 反向代理, 負載均衡, 靜態文件伺服器,websocket
代理,四層TCP/UDP轉發,內網穿透等,會將實現過程分享出來,感興趣的可以一起造個輪子
項目地址
國內: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
設計目標
證書的自動續期,讓系統免除證書過期的煩惱,保證系統的正確運行。
關於證書的驗證
證書的組成部分:公鑰,私鑰
公鑰部分
公開的信息cert,也稱公鑰,在nginx體系中通常以
.pem
結尾
Cert,作為“Certificate”(證書)的縮寫,通常用於表示網路安全和加密領域中的數字證書。這些證書是用於證明身份和保障安全性的重要工具,包含了許多關鍵信息。
一般來說,證書中存放的信息主要包括:
- 證書頒發機構(Certificate Authority,CA)的信息:這包括CA的名稱、公鑰和證書頒發者的數字簽名等。這些信息用於驗證證書的合法性和真實性。
- 證書持有者的信息:這通常包括組織或個人的名稱、功能變數名稱、公鑰和證書持有者的數字簽名等。這些信息用於標識證書的所有者和驗證其身份。
- 證書的有效期:證書通常有一個有效期限,包括開始日期和結束日期。這用於確定證書是否仍在有效期內。
此外,證書中還可能包含其他信息,例如證書的序列號、擴展欄位等。這些信息對於特定的應用場景可能具有重要意義。
總之,Cert中存放的信息是數字證書的重要組成部分,對於保障網路安全和身份認證具有重要意義。
私鑰部分
伺服器專用的信息,稱為私鑰,在nginx體系中通常以
.key
結尾
私鑰的主要作用是在TLS加密通信過程中,對從伺服器發送到客戶端的數據進行加密,以確保數據的機密性和安全性。當客戶端向伺服器發送請求時,伺服器會使用其私鑰對響應數據進行加密,然後發送給客戶端。客戶端在接收到加密數據後,會使用伺服器公鑰進行解密,從而獲取到原始數據。
由於私鑰的非公開性,如果私鑰被泄露,將會對TLS加密通信的安全性造成嚴重威脅。因此,私鑰的生成、存儲和使用都需要遵循嚴格的安全標準和最佳實踐。通常,私鑰應該在安全的環境中生成,並且只由授權的人員管理和使用。
在TLS證書的生命周期中,私鑰的管理和使用也是非常重要的。一旦私鑰丟失或泄露,就需要重新生成新的密鑰對和證書,以確保加密通信的安全性。因此,對於TLS證書的私鑰部分,必須採取嚴格的安全措施,以確保其機密性和安全性。
證書無效的可能
SSL證書可能會因為多種原因而無效。以下是一些常見的情況:
- 證書過期:SSL證書有有效期限,一旦過期,瀏覽器會拒絕連接並顯示證書無效的警告。為了避免這種情況,管理員需要定期檢查證書的到期日期,併在必要時進行更新或續訂。
- 功能變數名稱不匹配:SSL證書是針對特定的功能變數名稱頒發的,如果證書中的功能變數名稱與實際訪問的功能變數名稱不匹配,瀏覽器也會顯示證書無效。這可能是因為證書是為另一個功能變數名稱頒發的,或者證書中包含的功能變數名稱拼寫錯誤。
- 證書鏈不完整:SSL證書通常依賴於一個證書頒發機構(CA)的證書鏈。如果證書鏈中的任何證書丟失或損壞,瀏覽器可能無法驗證證書的有效性,並顯示證書無效。
- 瀏覽器不受信任:如果證書頒發機構(CA)的證書被瀏覽器標記為不受信任或被撤銷,那麼使用該CA頒發的SSL證書也將被視為無效。
此篇中主要介紹證書過期如何維護的可能。
獲取過期時間
關於tls的處理庫,這裡選擇的是rustls,查詢其相關Api及源碼,發現其並未提供Cert的過期時間。這裡選擇用第三方庫x509-certificate
來獲取證書的過期時間,他並不依賴於openssl,可以在不載入openssl的情況下獲取到證書的過期時間。
api相關函數:
pub fn validity_not_after(&self) -> DateTime<Utc>
// Obtain the certificate validity “not after” time.
設計要點
- 區分是否為acme的證書(只有acme證書才能自動獲取)
- 讀取證書的時候獲取過期時間
- 在接受證書時判斷是否過期
- 未過期,直接繼續執行
- 將過期或者已過期未載入,請求新的證書
- 已有新的證書,進行載入
- 保證不會頻繁載入
- 用有效的證書進行tls操作
源碼相關設計
新設計類
/// 包裝tls accepter, 用於適應acme及自有證書兩種
#[derive(Clone)]
pub struct WrapTlsAccepter {
/// 最後請求的時間
pub last: Instant,
/// 最後成功載入證書的時間
pub last_success: Instant,
/// 功能變數名稱
pub domain: Option<String>,
pub accepter: Option<TlsAcceptor>,
/// 證書的過期時間,將載入證書的時候同步讀取
pub expired: Option<DateTime<Utc>>,
pub is_acme: bool,
}
添加最後成功載入的時間,與全局的載入成功時間做比對。
lazy_static! {
// 成功載入時間記錄,以方便將過期的數據做更新
static ref SUCCESS_CERT: Mutex<HashMap<String, Instant>> = Mutex::new(HashMap::new());
}
判斷是否即將到期,到期前一天將自動更新
fn is_tls_will_expired(&self) -> bool {
if let Some(expire) = &self.expired {
let now = Utc::now();
if now.timestamp() > expire.timestamp() - 86400 {
return true;
}
}
false
}
將過期時將重新觸發載入:
if self.is_acme && self.is_tls_will_expired() {
let _ = self.check_and_request_cert();
}
#[cfg(feature = "acme-lib")]
fn check_and_request_cert(&self) -> Result<(), Error> {
if self.domain.is_none() {
return Err(io::Error::new(io::ErrorKind::Other, "未知功能變數名稱").into());
}
{
let mut map = CACHE_REQUEST
.lock()
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Fail get Lock"))?;
if let Some(last) = map.get(self.domain.as_ref().unwrap()) {
if last.elapsed() < self.get_delay_time() {
return Err(io::Error::new(io::ErrorKind::Other, "等待上次請求結束").into());
}
}
map.insert(self.domain.clone().unwrap(), Instant::now());
};
let obj = self.clone();
std::thread::spawn(move || {
let _ = obj.request_cert();
});
Ok(())
}
最後在載入成功後,下一輪的處理中將嘗試的載入ssl證書
pub fn update_last(&mut self) {
if self.accepter.is_none() {
if self.last.elapsed() > Duration::from_secs(5) {
self.try_load_cert();
self.last = Instant::now();
}
} else {
if self.domain.is_none() {
return;
}
let map = SUCCESS_CERT.lock().unwrap();
let doamin = &self.domain.clone().unwrap();
if !map.contains_key(doamin) {
return;
}
if self.last_success < map[doamin] && self.last < map[doamin] {
self.try_load_cert();
self.last = map[doamin];
}
}
}
如此一個擁有自動請求且自動更新的acme請求已完成。
如果有細心的已發現相關代碼用了feature,基本上等於Cpp中的#ifdef xxx
也是用來控制代碼是否啟用相關的。
關於條件編譯 Features
Cargo Feature 是非常強大的機制,可以為大家提供條件編譯和可選依賴的高級特性。
相關鏈接可以參考features
將其中的依賴改成了
acme-lib = { version = "^0.9.1", default-features = true, optional = true}
openssl = { version = "0.10.32", default-features = false, features = ["vendored"], optional = true }
因為acme-lib
依賴於openssl庫,在編譯方面可能會相對比較麻煩,需要額外的依賴,此處openssl配置是覆蓋acme-lib中的預設features,達到可以不依賴外部openssl庫的能力,使用源碼編譯,所以如果要啟用acme-lib能力可以使用
cargo build --features "acme-lib"
如果openssl不好依賴可以使用來編譯系統
cargo build --features "acme-lib openssl"
總結
現在免費證書只能申請三個月(之前還能申請十二個月),擁有acme能力對於小的站點來說就比較需要,可以比較好的部署也不用關心TLS帶來的煩惱。
點擊 [關註],[在看],[點贊] 是對作者最大的支持