12. 用Rust手把手编写一个wmproxy(代理,内网穿透等), TLS的双向认证信息及token验证
12. 用Rust手把手编写一个wmproxy(代理,内网穿透等), TLS的双向认证信息及token验证
项目 ++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
Client->>Server: 包含SSL/TLS版本,对称加密算法列表,随机数A
Server-->>Client: Server Hello,服务端先进行选择
Server-->>Client: 双方都支持的SSL/TLS协议版本,对称加密算法
Server-->>Client: 公钥证书,服务端生成的随机数B
Server-->>Client: Change Cipher Spec,收到这消息后开始密文传输
Client-)Client: 验证证书,是否过期,是否被吊销,是否可信,域名是否一致
Client->>Server: Change Cipher Spec
Client->>Server: 应用数据(客户端加密)
Server-->>Client: 应用数据(服务端加密)
双向通讯的示意图如下,差别
Client->>Server: Client Hello
Server-->>Client: Server Hello
rect rgba(0, 0, 255, 0.5)
Server-->>Client: 额外要求客户端提供客户端证书
end
Client-)Client: 验证证书
rect rgba(0, 0, 255, 0.5)
Client-->>Server: 客户端证书
Client-->>Server: 客户端证书验证信息(CertificateVerify message)
Server->Server: 验证客户端证书是否有效
Server->Server: 验证客户端证书验证消息的签名是否有效
end
Server-->>Client: 握手结束
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)
}
服务端处理
如果服务端启动的时候配置了username 及 password则表示他需要密码验证,
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手把手编写一个wmproxy(代理,内网穿透等), TLS的双向认证信息及token验证的更多相关文章
- 借助FRP反向代理实现内网穿透
一.frp 是什么? frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP.UDP.HTTP.HTTPS 等多种协议.可以将内网服务以安全.便捷的方式通过具有公网 IP 节点的中转暴露到公 ...
- 分享一个内网穿透工具frp
首先简单介绍一下内网穿透: 内网穿透:通过公网,访问局域网里的IP地址与端口,这需要将局域网里的电脑端口映射到公网的端口上:这就需要用到反向代理,即在公网服务器上必须运行一个服务程序,然后在局域网中需 ...
- 【新晋开源项目】内网穿透神器[中微子代理] 加入 Dromara 开源社区
1.关于作者 dromara开源组织成员,dromara/neutrino-proxy项目作者 名称:傲世孤尘.雨韵诗泽 名言: 扎根土壤,心向太阳.积蓄能量,绽放微光. 拘浊酒邀明月,借赤日暖苍穹. ...
- 代理内网上网-iptables
代理内网上网-iptables 1.1 环境说明 主机A:(能上网) ip:内172.16.1.7/24 外10.0.0.7/24 系统CentOS 6.9 主机B:(不能上网) ip:内172.16 ...
- 【代理】内网穿透工具 frp&frps
frp 是一个高性能的反向代理应用,可以帮助您轻松地进行内网穿透,对外网提供服务,支持 tcp, http, https 等协议类型,并且 web 服务支持根据域名进行路由转发. ### frp 的作 ...
- frp实现基于反向代理的内网穿透
个人博客主页: xzajyjs.cn frp是什么 简单地说,frp就是一个反向代理软件,它体积轻量但功能很强大,可以使处于内网或防火墙后的设备对外界提供服务,它支持HTTP.TCP.UDP等众多协议 ...
- [原创]K8飞刀20150725 支持SOCKS5代理(内网渗透)
工具: K8飞刀编译: 自己查壳组织: K8搞基大队[K8team]作者: K8拉登哥哥博客: http://qqhack8.blog.163.com发布: 2015/7/26 3:41:11 简介: ...
- Mysql-proxy代理内网数据库
Mysql-proxy 参考:https://segmentfault.com/q/1010000000394160 情景分析:首先您需要正在使用UCloud云主机(uhoust)以及云数据库(udb ...
- ssh后门反向代理实现内网穿透
如图所示,内网主机ginger 无公网IP地址,防火墙只允许ginger连接blackbox.example.com主机 假如你是ginger的管理员root,你想要用tech主机连接ginger主机 ...
- CentOS squid代理内网主机上网 openVpn配置
随机推荐
- .NET Core 允许跨域的两种方式实现(IIS 配置、C# 代码实现)
〇.前言 当把开发好的 WebApi 接口,部署到 Windows 服务器 IIS 后,postman 可以直接访问到接口并正确返回,这并不意味着任务完成,毕竟接口嘛是要有交互的,最常见的问题莫过于跨 ...
- 聊天室(二)__ unipush 推送实现详细教程
一.推送作用 推送作用我就不废话了,能做推送的都知道作用,直接上干货. 二.unipush 快速开通 Dcloud 开发者实名认证注册账号,绑定对应的 app 信息. uni-push产品有2个入 ...
- java查询sql动态查询需要的字段
方法一:使用"trim"标签. <select id="selTest" parameterType="mocha.framework.enti ...
- 二、GCC编译器工作过程
从更直观的角度来说,编译器是一种工具,将高级语言转化为机器语言.举个例子,我们可以使用编译器将用C++语言编写的程序转换为机器可执行的指令和数据.之前提到过,用机器指令或汇编语言编写程序非常繁琐和乏味 ...
- 图像阈值_有cv2.threshold,cv2.adaptiveThreshold 等。
1.简单阈值 使用的函数:cv2.threshold (src, thresh, maxval, type) 注释: 与名字一样,这种方法非常简单.但像素值高于阈值时,我们给这个像素赋予一个新值(可能 ...
- 【高并发】SimpleDateFormat类到底为啥不是线程安全的?(附六种解决方案,建议收藏)
大家好,我是冰河~~ 首先问下大家:你使用的SimpleDateFormat类还安全吗?为什么说SimpleDateFormat类不是线程安全的?带着问题从本文中寻求答案. 提起SimpleDateF ...
- go语言实用工具编写要这样学
写作目的 本篇章写作有以下目的: 介绍go语言的基础知识,这里你会发现go语言学习成本较低,与python语言相似. 介绍go语言的常用标准库,这里你会发现go语言的标准库已经非常强大,python语 ...
- docker网络 bridge 与overlay 模式
转载请注明出处: 1.bridge网络模式 工作原理: 在Bridge模式中,Docker通过创建一个虚拟网络桥接器(bridge)将容器连接到主机上的物理网络接口.每个容器都会被分配一个IP地址, ...
- Java应用堆外内存泄露问题排查
问题是怎么发现的 最近有个java应用在做压力测试 压测环境配置: CentOS系统 4核CPU 8g内存 jdk1.6.0_25,jvm配置-server -Xms2048m -Xmx2048m 出 ...
- react项目搭建-路由封装
router v6 路由统一管理与添加,对是否登录进行判断. 1.使用脚手架创建项目 新建一个文件夹 ,在文件夹内部打开命令行工具. 执行命令:npx create-react-app 项目名字 将项 ...