经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Rust » 查看文章
51从零开始用Rust编写nginx,江湖救急,TLS证书快过期了
来源:cnblogs  作者:问蒙服务框架  时间:2024/3/1 8:45:47  对本文有异议

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”(证书)的缩写,通常用于表示网络安全和加密领域中的数字证书。这些证书是用于证明身份和保障安全性的重要工具,包含了许多关键信息。

一般来说,证书中存放的信息主要包括:

  1. 证书颁发机构(Certificate Authority,CA)的信息:这包括CA的名称、公钥和证书颁发者的数字签名等。这些信息用于验证证书的合法性和真实性。
  2. 证书持有者的信息:这通常包括组织或个人的名称、域名、公钥和证书持有者的数字签名等。这些信息用于标识证书的所有者和验证其身份。
  3. 证书的有效期:证书通常有一个有效期限,包括开始日期和结束日期。这用于确定证书是否仍在有效期内。

此外,证书中还可能包含其他信息,例如证书的序列号、扩展字段等。这些信息对于特定的应用场景可能具有重要意义。

总之,Cert中存放的信息是数字证书的重要组成部分,对于保障网络安全和身份认证具有重要意义。

私钥部分

服务器专用的信息,称为私钥,在nginx体系中通常以.key结尾

私钥的主要作用是在TLS加密通信过程中,对从服务器发送到客户端的数据进行加密,以确保数据的机密性和安全性。当客户端向服务器发送请求时,服务器会使用其私钥对响应数据进行加密,然后发送给客户端。客户端在接收到加密数据后,会使用服务器公钥进行解密,从而获取到原始数据。

由于私钥的非公开性,如果私钥被泄露,将会对TLS加密通信的安全性造成严重威胁。因此,私钥的生成、存储和使用都需要遵循严格的安全标准和最佳实践。通常,私钥应该在安全的环境中生成,并且只由授权的人员管理和使用。

在TLS证书的生命周期中,私钥的管理和使用也是非常重要的。一旦私钥丢失或泄露,就需要重新生成新的密钥对和证书,以确保加密通信的安全性。因此,对于TLS证书的私钥部分,必须采取严格的安全措施,以确保其机密性和安全性。

证书无效的可能

SSL证书可能会因为多种原因而无效。以下是一些常见的情况:

  1. 证书过期:SSL证书有有效期限,一旦过期,浏览器会拒绝连接并显示证书无效的警告。为了避免这种情况,管理员需要定期检查证书的到期日期,并在必要时进行更新或续订。
  2. 域名不匹配:SSL证书是针对特定的域名颁发的,如果证书中的域名与实际访问的域名不匹配,浏览器也会显示证书无效。这可能是因为证书是为另一个域名颁发的,或者证书中包含的域名拼写错误。
  3. 证书链不完整:SSL证书通常依赖于一个证书颁发机构(CA)的证书链。如果证书链中的任何证书丢失或损坏,浏览器可能无法验证证书的有效性,并显示证书无效。
  4. 浏览器不受信任:如果证书颁发机构(CA)的证书被浏览器标记为不受信任或被撤销,那么使用该CA颁发的SSL证书也将被视为无效。

此篇中主要介绍证书过期如何维护的可能。

获取过期时间

关于tls的处理库,这里选择的是rustls,查询其相关Api及源码,发现其并未提供Cert的过期时间。这里选择用第三方库x509-certificate来获取证书的过期时间,他并不依赖于openssl,可以在不加载openssl的情况下获取到证书的过期时间。

api相关函数:

  1. pub fn validity_not_after(&self) -> DateTime<Utc>
  2. // Obtain the certificate validity “not after” time.

设计要点

  1. 区分是否为acme的证书(只有acme证书才能自动获取)
  2. 读取证书的时候获取过期时间
  3. 在接受证书时判断是否过期
    1. 未过期,直接继续执行
    2. 将过期或者已过期未加载,请求新的证书
    3. 已有新的证书,进行加载
    4. 保证不会频繁加载
  4. 用有效的证书进行tls操作

源码相关设计

新设计类

  1. /// 包装tls accepter, 用于适应acme及自有证书两种
  2. #[derive(Clone)]
  3. pub struct WrapTlsAccepter {
  4. /// 最后请求的时间
  5. pub last: Instant,
  6. /// 最后成功加载证书的时间
  7. pub last_success: Instant,
  8. /// 域名
  9. pub domain: Option<String>,
  10. pub accepter: Option<TlsAcceptor>,
  11. /// 证书的过期时间,将加载证书的时候同步读取
  12. pub expired: Option<DateTime<Utc>>,
  13. pub is_acme: bool,
  14. }

添加最后成功加载的时间,与全局的加载成功时间做比对。

  1. lazy_static! {
  2. // 成功加载时间记录,以方便将过期的数据做更新
  3. static ref SUCCESS_CERT: Mutex<HashMap<String, Instant>> = Mutex::new(HashMap::new());
  4. }

判断是否即将到期,到期前一天将自动更新

  1. fn is_tls_will_expired(&self) -> bool {
  2. if let Some(expire) = &self.expired {
  3. let now = Utc::now();
  4. if now.timestamp() > expire.timestamp() - 86400 {
  5. return true;
  6. }
  7. }
  8. false
  9. }

将过期时将重新触发加载:

  1. if self.is_acme && self.is_tls_will_expired() {
  2. let _ = self.check_and_request_cert();
  3. }
  4. #[cfg(feature = "acme-lib")]
  5. fn check_and_request_cert(&self) -> Result<(), Error> {
  6. if self.domain.is_none() {
  7. return Err(io::Error::new(io::ErrorKind::Other, "未知域名").into());
  8. }
  9. {
  10. let mut map = CACHE_REQUEST
  11. .lock()
  12. .map_err(|_| io::Error::new(io::ErrorKind::Other, "Fail get Lock"))?;
  13. if let Some(last) = map.get(self.domain.as_ref().unwrap()) {
  14. if last.elapsed() < self.get_delay_time() {
  15. return Err(io::Error::new(io::ErrorKind::Other, "等待上次请求结束").into());
  16. }
  17. }
  18. map.insert(self.domain.clone().unwrap(), Instant::now());
  19. };
  20. let obj = self.clone();
  21. std::thread::spawn(move || {
  22. let _ = obj.request_cert();
  23. });
  24. Ok(())
  25. }

最后在加载成功后,下一轮的处理中将尝试的加载ssl证书

  1. pub fn update_last(&mut self) {
  2. if self.accepter.is_none() {
  3. if self.last.elapsed() > Duration::from_secs(5) {
  4. self.try_load_cert();
  5. self.last = Instant::now();
  6. }
  7. } else {
  8. if self.domain.is_none() {
  9. return;
  10. }
  11. let map = SUCCESS_CERT.lock().unwrap();
  12. let doamin = &self.domain.clone().unwrap();
  13. if !map.contains_key(doamin) {
  14. return;
  15. }
  16. if self.last_success < map[doamin] && self.last < map[doamin] {
  17. self.try_load_cert();
  18. self.last = map[doamin];
  19. }
  20. }
  21. }

如此一个拥有自动请求且自动更新的acme请求已完成。
如果有细心的已发现相关代码用了feature,基本上等于Cpp中的#ifdef xxx也是用来控制代码是否启用相关的。

关于条件编译 Features

Cargo Feature 是非常强大的机制,可以为大家提供条件编译和可选依赖的高级特性。

相关链接可以参考features

将其中的依赖改成了

  1. acme-lib = { version = "^0.9.1", default-features = true, optional = true}
  2. openssl = { version = "0.10.32", default-features = false, features = ["vendored"], optional = true }

因为acme-lib依赖于openssl库,在编译方面可能会相对比较麻烦,需要额外的依赖,此处openssl配置是覆盖acme-lib中的默认features,达到可以不依赖外部openssl库的能力,使用源码编译,所以如果要启用acme-lib能力可以使用

  1. cargo build --features "acme-lib"
  2. 如果openssl不好依赖可以使用来编译系统
  3. cargo build --features "acme-lib openssl"

总结

现在免费证书只能申请三个月(之前还能申请十二个月),拥有acme能力对于小的站点来说就比较需要,可以比较好的部署也不用关心TLS带来的烦恼。

点击 [关注][在看][点赞] 是对作者最大的支持

原文链接:https://www.cnblogs.com/wmproxy/p/18042594/wmproxy51

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号