编辑
2024-01-16
网络
00
请注意,本文编写于 236 天前,最后修改于 236 天前,其中某些信息可能已经过时。

目录

wmproxy
项目 ++wmproxy++
什么是TLS双向认证
单向与双向的差别
SSL单向验证
代码实现
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单向验证

单向通讯的示意图如下

sequenceDiagram
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: 应用数据(服务端加密)

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

sequenceDiagram
    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的认证

rust
/// 获取客户端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接收此消息

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

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

rust
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则表示他需要密码验证,

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

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

rust
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; }

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

本文作者:问蒙服务框架

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!