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

相关文章

Python如何实现PDF隐私信息检测

《Python如何实现PDF隐私信息检测》随着越来越多的个人信息以电子形式存储和传输,确保这些信息的安全至关重要,本文将介绍如何使用Python检测PDF文件中的隐私信息,需要的可以参考下... 目录项目背景技术栈代码解析功能说明运行结php果在当今,数据隐私保护变得尤为重要。随着越来越多的个人信息以电子形

修改若依框架Token的过期时间问题

《修改若依框架Token的过期时间问题》本文介绍了如何修改若依框架中Token的过期时间,通过修改`application.yml`文件中的配置来实现,默认单位为分钟,希望此经验对大家有所帮助,也欢迎... 目录修改若依框架Token的过期时间修改Token的过期时间关闭Token的过期时js间总结修改若依

java如何通过Kerberos认证方式连接hive

《java如何通过Kerberos认证方式连接hive》该文主要介绍了如何在数据源管理功能中适配不同数据源(如MySQL、PostgreSQL和Hive),特别是如何在SpringBoot3框架下通过... 目录Java实现Kerberos认证主要方法依赖示例续期连接hive遇到的问题分析解决方式扩展思考总

在Rust中要用Struct和Enum组织数据的原因解析

《在Rust中要用Struct和Enum组织数据的原因解析》在Rust中,Struct和Enum是组织数据的核心工具,Struct用于将相关字段封装为单一实体,便于管理和扩展,Enum用于明确定义所有... 目录为什么在Rust中要用Struct和Enum组织数据?一、使用struct组织数据:将相关字段绑

浅析Rust多线程中如何安全的使用变量

《浅析Rust多线程中如何安全的使用变量》这篇文章主要为大家详细介绍了Rust如何在线程的闭包中安全的使用变量,包括共享变量和修改变量,文中的示例代码讲解详细,有需要的小伙伴可以参考下... 目录1. 向线程传递变量2. 多线程共享变量引用3. 多线程中修改变量4. 总结在Rust语言中,一个既引人入胜又可

C#实现系统信息监控与获取功能

《C#实现系统信息监控与获取功能》在C#开发的众多应用场景中,获取系统信息以及监控用户操作有着广泛的用途,比如在系统性能优化工具中,需要实时读取CPU、GPU资源信息,本文将详细介绍如何使用C#来实现... 目录前言一、C# 监控键盘1. 原理与实现思路2. 代码实现二、读取 CPU、GPU 资源信息1.

在C#中获取端口号与系统信息的高效实践

《在C#中获取端口号与系统信息的高效实践》在现代软件开发中,尤其是系统管理、运维、监控和性能优化等场景中,了解计算机硬件和网络的状态至关重要,C#作为一种广泛应用的编程语言,提供了丰富的API来帮助开... 目录引言1. 获取端口号信息1.1 获取活动的 TCP 和 UDP 连接说明:应用场景:2. 获取硬

SpringBoot使用Apache Tika检测敏感信息

《SpringBoot使用ApacheTika检测敏感信息》ApacheTika是一个功能强大的内容分析工具,它能够从多种文件格式中提取文本、元数据以及其他结构化信息,下面我们来看看如何使用Ap... 目录Tika 主要特性1. 多格式支持2. 自动文件类型检测3. 文本和元数据提取4. 支持 OCR(光学

利用Python编写一个简单的聊天机器人

《利用Python编写一个简单的聊天机器人》这篇文章主要为大家详细介绍了如何利用Python编写一个简单的聊天机器人,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 使用 python 编写一个简单的聊天机器人可以从最基础的逻辑开始,然后逐步加入更复杂的功能。这里我们将先实现一个简单的

C#实现获取电脑中的端口号和硬件信息

《C#实现获取电脑中的端口号和硬件信息》这篇文章主要为大家详细介绍了C#实现获取电脑中的端口号和硬件信息的相关方法,文中的示例代码讲解详细,有需要的小伙伴可以参考一下... 我们经常在使用一个串口软件的时候,发现软件中的端口号并不是普通的COM1,而是带有硬件信息的。那么如果我们使用C#编写软件时候,如