12. 从零用Rust编写正反向代理, TLS的双向认证信息及token验证

2023-12-17 00:04

本文主要是介绍12. 从零用Rust编写正反向代理, TLS的双向认证信息及token验证,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

wmproxy

wmproxy是由Rust编写,已实现http/https代理,socks5代理, 反向代理,静态文件服务器,内网穿透,配置热更新等, 后续将实现websocket代理等,同时会将实现过程分享出来, 感兴趣的可以一起造个轮子法

项目 ++wmproxy++

gite: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

什么是TLS双向认证

TLS双向认证是指客户端和服务器端都需要验证对方的身份,也称mTLS

在建立Https连接的过程中,握手的流程比单向认证多了几步。

  • 单向认证的过程,客户端从服务器端下载服务器端公钥证书进行验证,然后建立安全通信通道。
  • 双向通信流程,客户端除了需要从服务器端下载服务器的公钥证书进行验证外,还需要把客户端的公钥证书上传到服务器端给服务器端进行验证,等双方都认证通过了,才开始建立安全通信通道进行数据传输。

TLS是安全套接层(SSL)的继任者,叫传输层安全(transport layer security)。说直白点,就是在明文的上层和TCP层之间加上一层加密,这样就保证上层信息传输的安全,然后解密完后又以原样的数据回传给应用层,做到与应用层无关,所以http加个s就成了https,ws加个s就成了wss,ftp加个s就成了ftps,都是从普通tcp传输转换成tls传输实现安全加密,应用相当广泛。

单向与双向的差别

SSL单向验证

单向通讯的示意图如下

Client Server Client Hello 包含SSL/TLS版本,对称加密算法列表,随机数A Server Hello,服务端先进行选择 双方都支持的SSL/TLS协议版本,对称加密算法 公钥证书,服务端生成的随机数B Change Cipher Spec,收到这消息后开始密文传输 验证证书,是否过期,是否被吊销,是否可信,域名是否一致 Change Cipher Spec 应用数据(客户端加密) 应用数据(服务端加密) Client Server

双向通讯的示意图如下,差别

Client Server Client Hello Server Hello 额外要求客户端提供客户端证书 验证证书 客户端证书 客户端证书验证信息(CertificateVerify message) 验证客户端证书是否有效 验证客户端证书验证消息的签名是否有效 握手结束 握手结束 Client Server

备注:客户端将之前所有收到的和发送的消息组合起来,并用hash算法得到一个hash值,然后用客户端密钥库的私钥对这个hash进行签名,这个签名就是CertificateVerify message;

代码实现

将原来的rustls中的TlsAcceptor和TlsConnector进行相应的改造,变成可支持双向认证的加密结构。

获取TlsAcceptor的认证

/// 获取服务端https的证书信息
pub async fn get_tls_accept(&mut self) -> ProxyResult<TlsAcceptor> {if !self.tc {return Err(ProxyError::ProtNoSupport);}let certs = Self::load_certs(&self.cert)?;let key = Self::load_keys(&self.key)?;let config = rustls::ServerConfig::builder().with_safe_defaults();// 开始双向认证,需要客户端提供证书信息let config = if self.two_way_tls {let mut client_auth_roots = rustls::RootCertStore::empty();for root in &certs {client_auth_roots.add(&root).unwrap();}let client_auth = rustls::server::AllowAnyAuthenticatedClient::new(client_auth_roots);config.with_client_cert_verifier(client_auth.boxed()).with_single_cert(certs, key).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?} else {config.with_no_client_auth().with_single_cert(certs, key).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?};let acceptor = TlsAcceptor::from(Arc::new(config));Ok(acceptor)
}

获取TlsAcceptor的认证

/// 获取客户端https的Config配置
pub async fn get_tls_request(&mut self) -> ProxyResult<Arc<rustls::ClientConfig>> {if !self.ts {return Err(ProxyError::ProtNoSupport);}let certs = Self::load_certs(&self.cert)?;let mut root_cert_store = rustls::RootCertStore::empty();// 信任通用的签名商root_cert_store.add_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| {rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(ta.subject,ta.spki,ta.name_constraints,)}),);for cert in &certs {let _ = root_cert_store.add(cert);}let config = rustls::ClientConfig::builder().with_safe_defaults().with_root_certificates(root_cert_store);if self.two_way_tls {let key = Self::load_keys(&self.key)?;Ok(Arc::new(config.with_client_auth_cert(certs, key).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err),)?))} else {Ok(Arc::new(config.with_no_client_auth()))}
}

这里默认信任的通用的CA签发证书平台,像系统证书,浏览器信任的证书,只有第一步把基础的被信任才有资格做签发证书平台。

至此双向TLS的能力已经达成,感谢前人的经典代码才能如此轻松。

token验证

首先先定义协议的Token结构,只有sock_map为0接收此消息

/// 进行身份的认证
#[derive(Debug)]
pub struct ProtToken {username: String,password: String,
}

下面是编码解码,密码要求不超过255个字符,即长度为1字节编码

pub fn parse<T: Buf>(_header: ProtFrameHeader, mut buf: T) -> ProxyResult<ProtToken> {let username = read_short_string(&mut buf)?;let password = read_short_string(&mut buf)?;Ok(Self { username, password })
}pub fn encode<B: Buf + BufMut>(self, buf: &mut B) -> ProxyResult<usize> {let mut head = ProtFrameHeader::new(ProtKind::Token, ProtFlag::zero(), 0);head.length = self.username.as_bytes().len() as u32 + 1 + self.password.as_bytes().len() as u32 + 1;let mut size = 0;size += head.encode(buf)?;size += write_short_string(buf, &self.username)?;size += write_short_string(buf, &self.password)?;Ok(size)
}
服务端处理

如果服务端启动的时候配置了usernamepassword则表示他需要密码验证,

let mut verify_succ = option.username.is_none() && option.password.is_none();

如果verify_succ不为true,那么我们接下来的第一条消息必须为ProtToken,否则客户端不合法,关闭
收到该消息则进行验证

match &p {ProtFrame::Token(p) => {if !verify_succ&& p.is_check_succ(&option.username, &option.password){verify_succ = true;continue;}}_ => {}
}
if !verify_succ {ProtFrame::new_close_reason(0, "not verify so close".to_string()).encode(&mut write_buf)?;is_ready_shutdown = true;break;
}

认证通过后消息处理和之前的一样,验证流程完成

这篇关于12. 从零用Rust编写正反向代理, TLS的双向认证信息及token验证的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/502391

相关文章

JSON Web Token在登陆中的使用过程

《JSONWebToken在登陆中的使用过程》:本文主要介绍JSONWebToken在登陆中的使用过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录JWT 介绍微服务架构中的 JWT 使用结合微服务网关的 JWT 验证1. 用户登录,生成 JWT2. 自定义过滤

opencv图像处理之指纹验证的实现

《opencv图像处理之指纹验证的实现》本文主要介绍了opencv图像处理之指纹验证的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录一、简介二、具体案例实现1. 图像显示函数2. 指纹验证函数3. 主函数4、运行结果三、总结一、

springboot security使用jwt认证方式

《springbootsecurity使用jwt认证方式》:本文主要介绍springbootsecurity使用jwt认证方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录前言代码示例依赖定义mapper定义用户信息的实体beansecurity相关的类提供登录接口测试提供一

一文详解SQL Server如何跟踪自动统计信息更新

《一文详解SQLServer如何跟踪自动统计信息更新》SQLServer数据库中,我们都清楚统计信息对于优化器来说非常重要,所以本文就来和大家简单聊一聊SQLServer如何跟踪自动统计信息更新吧... SQL Server数据库中,我们都清楚统计信息对于优化器来说非常重要。一般情况下,我们会开启"自动更新

SpringSecurity 认证、注销、权限控制功能(注销、记住密码、自定义登入页)

《SpringSecurity认证、注销、权限控制功能(注销、记住密码、自定义登入页)》SpringSecurity是一个强大的Java框架,用于保护应用程序的安全性,它提供了一套全面的安全解决方案... 目录简介认识Spring Security“认证”(Authentication)“授权” (Auth

Python如何获取域名的SSL证书信息和到期时间

《Python如何获取域名的SSL证书信息和到期时间》在当今互联网时代,SSL证书的重要性不言而喻,它不仅为用户提供了安全的连接,还能提高网站的搜索引擎排名,那我们怎么才能通过Python获取域名的S... 目录了解SSL证书的基本概念使用python库来抓取SSL证书信息安装必要的库编写获取SSL证书信息

Win32下C++实现快速获取硬盘分区信息

《Win32下C++实现快速获取硬盘分区信息》这篇文章主要为大家详细介绍了Win32下C++如何实现快速获取硬盘分区信息,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 实现代码CDiskDriveUtils.h#pragma once #include <wtypesbase

一文详解kafka开启kerberos认证的完整步骤

《一文详解kafka开启kerberos认证的完整步骤》这篇文章主要为大家详细介绍了kafka开启kerberos认证的完整步骤,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、kerberos安装部署二、准备机器三、Kerberos Server 安装1、配置krb5.con

基于.NET编写工具类解决JSON乱码问题

《基于.NET编写工具类解决JSON乱码问题》在开发过程中,我们经常会遇到JSON数据处理的问题,尤其是在数据传输和解析过程中,很容易出现编码错误导致的乱码问题,下面我们就来编写一个.NET工具类来解... 目录问题背景核心原理工具类实现使用示例总结在开发过程中,我们经常会遇到jsON数据处理的问题,尤其是

Rust中的注释使用解读

《Rust中的注释使用解读》本文介绍了Rust中的行注释、块注释和文档注释的使用方法,通过示例展示了如何在实际代码中应用这些注释,以提高代码的可读性和可维护性... 目录Rust 中的注释使用指南1. 行注释示例:行注释2. 块注释示例:块注释3. 文档注释示例:文档注释4. 综合示例总结Rust 中的注释