

12. 用Rust手把手编写一个wmproxy(代理,内网穿透等), TLS的双向认证信息及token验证...
source link: https://www.cnblogs.com/luojiawaf/p/17763661.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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单向验证
单向通讯的示意图如下
双向通讯的示意图如下,差别
备注:客户端将之前所有收到的和发送的消息组合起来,并用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;
}
认证通过后消息处理和之前的一样,验证流程完成
Recommend
-
99
你似乎来到了没有知识存在的荒原3 秒后自动跳转至知乎首页去往首页
-
94
穿透内网防线|USB自动渗透手法总结 GeekOnline 2018-01-07 09:00:01 1320168 10 ...
-
33
自去年毕业来到杭州,想想也该有大半年了。本身是软件工程的科班出身,在校时理论掌握的还可以。但应用到实践当中去,有些还是不大理解,于是,不停地向带我的人请教,毕竟,三人行,必有我师焉。经过一段时间理论加实践,多少也掌握了其中的门路。
-
7
frp:一个内网穿透神器 最近在折腾家里的NAS和树莓派,一切配置配置妥当后发现一个难以解决的问题:那就是小区的网络并没有提供公网ip。虽然可以打电话给运营商投诉申请,...
-
6
钉钉机器人回调内网穿透代理--使用篇 2020-03-252020-11-17 292 ...
-
7
FRP 内网穿透、反向代理frp 是一个专注于内网穿透的...
-
19
受审查网络下的内网穿透:通过v2ray代理nps/frp通道 ...
-
8
V2EX › 宽带症候群 求一个可以满速带宽的内网穿透方案
-
5
V2EX › 问与答 求一个内网穿透方案。
-
4
11. 用Rust手把手编写一个wmproxy(代理,内网穿透等), 实现健康检查 项目 ++wmproxy++ gite: ht...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK