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

相关文章

MySQL 主从复制部署及验证(示例详解)

《MySQL主从复制部署及验证(示例详解)》本文介绍MySQL主从复制部署步骤及学校管理数据库创建脚本,包含表结构设计、示例数据插入和查询语句,用于验证主从同步功能,感兴趣的朋友一起看看吧... 目录mysql 主从复制部署指南部署步骤1.环境准备2. 主服务器配置3. 创建复制用户4. 获取主服务器状态5

一文详解如何使用Java获取PDF页面信息

《一文详解如何使用Java获取PDF页面信息》了解PDF页面属性是我们在处理文档、内容提取、打印设置或页面重组等任务时不可或缺的一环,下面我们就来看看如何使用Java语言获取这些信息吧... 目录引言一、安装和引入PDF处理库引入依赖二、获取 PDF 页数三、获取页面尺寸(宽高)四、获取页面旋转角度五、判断

Java中读取YAML文件配置信息常见问题及解决方法

《Java中读取YAML文件配置信息常见问题及解决方法》:本文主要介绍Java中读取YAML文件配置信息常见问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录1 使用Spring Boot的@ConfigurationProperties2. 使用@Valu

Java通过驱动包(jar包)连接MySQL数据库的步骤总结及验证方式

《Java通过驱动包(jar包)连接MySQL数据库的步骤总结及验证方式》本文详细介绍如何使用Java通过JDBC连接MySQL数据库,包括下载驱动、配置Eclipse环境、检测数据库连接等关键步骤,... 目录一、下载驱动包二、放jar包三、检测数据库连接JavaJava 如何使用 JDBC 连接 mys

Spring Security中用户名和密码的验证完整流程

《SpringSecurity中用户名和密码的验证完整流程》本文给大家介绍SpringSecurity中用户名和密码的验证完整流程,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定... 首先创建了一个UsernamePasswordAuthenticationTChina编程oken对象,这是S

python编写朋克风格的天气查询程序

《python编写朋克风格的天气查询程序》这篇文章主要为大家详细介绍了一个基于Python的桌面应用程序,使用了tkinter库来创建图形用户界面并通过requests库调用Open-MeteoAPI... 目录工具介绍工具使用说明python脚本内容如何运行脚本工具介绍这个天气查询工具是一个基于 Pyt

MyBatis编写嵌套子查询的动态SQL实践详解

《MyBatis编写嵌套子查询的动态SQL实践详解》在Java生态中,MyBatis作为一款优秀的ORM框架,广泛应用于数据库操作,本文将深入探讨如何在MyBatis中编写嵌套子查询的动态SQL,并结... 目录一、Myhttp://www.chinasem.cnBATis动态SQL的核心优势1. 灵活性与可

Mybatis嵌套子查询动态SQL编写实践

《Mybatis嵌套子查询动态SQL编写实践》:本文主要介绍Mybatis嵌套子查询动态SQL编写方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、实体类1、主类2、子类二、Mapper三、XML四、详解总结前言MyBATis的xml文件编写动态SQL

SpringBoot整合Sa-Token实现RBAC权限模型的过程解析

《SpringBoot整合Sa-Token实现RBAC权限模型的过程解析》:本文主要介绍SpringBoot整合Sa-Token实现RBAC权限模型的过程解析,本文给大家介绍的非常详细,对大家的学... 目录前言一、基础概念1.1 RBAC模型核心概念1.2 Sa-Token核心功能1.3 环境准备二、表结

Linux查看系统盘和SSD盘的容量、型号及挂载信息的方法

《Linux查看系统盘和SSD盘的容量、型号及挂载信息的方法》在Linux系统中,管理磁盘设备和分区是日常运维工作的重要部分,而lsblk命令是一个强大的工具,它用于列出系统中的块设备(blockde... 目录1. 查看所有磁盘的物理信息方法 1:使用 lsblk(推荐)方法 2:使用 fdisk -l(