12. 用Rust手把手编写一个wmproxy(代理,内网穿透等), TLS的双向认证信息及token验证

rust,手把手,编写,一个,wmproxy,代理,内网,穿透,tls,双向,认证,信息,token,验证 · 浏览次数 : 12

小编点评

```rust use std::io::{Result}; use protobuf::Message; #[derive(Debug)] pub struct ProtToken { username: String, password: String, } impl ProtToken { fn parse(&self, header: &mut ProtoFrameHeader, buf: &mut impl BufRead) -> Result { let username = read_short_string(buf)?; let password = read_short_string(buf)?; Ok(Self { username, password }) } fn encode(&self, buf: &mut impl BufMut) -> Result { 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 size = 0; size += head.encode(buf)?; size += write_short_string(buf, &self.username)?; size += write_short_string(buf, &self.password)?; Ok(size) } } fn main() -> Result<()> { let mut proto_data = PROTOBUF_DATA; let mut message = ProtToken::parse(&mut proto_data, &mut []); println!("{}", message); Ok(()) } ```

正文

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单向验证

单向通讯的示意图如下

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

/// 获取客户端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手把手编写一个wmproxy(代理,内网穿透等), TLS的双向认证信息及token验证相似的内容:

12. 用Rust手把手编写一个wmproxy(代理,内网穿透等), TLS的双向认证信息及token验证

TLS双向认证的基本原理及示意流程图,帮助更好的理解TLS的加密功能,及安全能力,此外还给出了部分源码的实现及Token实现在的方案及能力

数据库连接池长时间不用,乍一用还用不了,结果是防火墙的锅

前言 我们的程序,在实际的网络部署时,一般比较复杂,会经过很多的网络设备,防火墙就是其中的一种。做开发的同事,一般对这块了解不多,也很可能被防火墙坑到。比如,应用一般需要访问数据库,为了避免频繁建立连接,一般是会提前建立一个连接池,每次来一个请求,就从连接池取一个连接来用,用完再归还到池子里。 连接

如何有效的进行用例评审

用例评审对于质量同学是再熟悉不过的一个重要环节,用例评审也是非常有效的保障测试质量的手段,但我们质量同学做了这么多次的评审,有没有去思考怎样去进一步提升用例评审的质量,使用例评审更加有效呢,这里呢抛砖引玉,总结一下对用例评审的思考,希望能给大家带来一些启发。

SpringCloud-Config配置中心搭建保姆级教程

一、分布式配置中⼼ 在使⽤微服务架构开发的项⽬中,每个服务都有⾃⼰的配置⽂件(application.yml),如果将每个服务的配置⽂件直接写在对应的服务中,存在以下问题: 1. 服务开发完成之后,需要打包部署,配置⽂件也会打包在jar⽂件中,不便于项⽬部署之后的配置修改(在源码中修改——重新打包—

K8S 1.20 弃用 Docker 评估之 Docker CLI 的替代产品

背景 2020 年 12 月初,Kubernetes 在其最新的 Changelog 中宣布,自 Kubernetes 1.20 之后将弃用 Docker 作为容器运行时。 弃用 Docker 带来的,可能是一系列的改变,包括不限于: 容器镜像构建工具 容器 CLI 容器镜像仓库 容器运行时 专题文

K8S 1.20 弃用 Docker 评估之 Docker CLI 的替代产品 nerdctl

背景 2020 年 12 月初,Kubernetes 在其最新的 Changelog 中宣布,自 Kubernetes 1.20 之后将弃用 Docker 作为容器运行时。 弃用 Docker 带来的,可能是一系列的改变,包括不限于: 容器镜像构建工具 容器 CLI 容器镜像仓库 容器运行时 专题文

K8S 1.20 弃用 Docker 评估之 Docker 和 OCI 镜像格式的差别

背景 2020 年 12 月初,Kubernetes 在其最新的 Changelog 中宣布,自 Kubernetes 1.20 之后将弃用 Docker 作为容器运行时。 弃用 Docker 带来的,可能是一系列的改变,包括不限于: 容器镜像构建工具 容器 CLI 容器镜像仓库 容器运行时 专题文

以开发之名 | 小红书:用年轻人的方式开发年轻人喜欢的应用

2013年,小红书在上海成立,同年12月,小红书推出海外购物分享社区。一个开放式的体验型分享社区走进了数亿用户的生活。每个人都能在这个开放社区,分享自己的生活笔记,给有同样需求的人种草。 小红书用户“一只雪梨酱”的车胎出现了裂痕,她拍了一张照片并附上文字 “这种情况需要换胎吗?”发了一篇小红书笔记,

《最新出炉》系列初窥篇-Python+Playwright自动化测试-12-playwright操作iframe-中篇

1.简介 按照计划今天就要用实际的例子进行iframe自动化测试。经过宏哥长时间的查找,终于找到了一个含有iframe的网页(QQ邮箱和163邮箱),别的邮箱宏哥就没有细看了。所以今天这一篇的主要内容就是用这两个网页的iframe结合上一篇的理论知识,宏哥给小伙伴或者童鞋们演示一下。 2.QQ邮箱

Linux系统中如何查看磁盘情况

Linux不像windows系统那样方便的图形界面,特别是作为服务器使用的时候,只有命令行可以使用。 我有个云服务器平时用来做一些数据分享用的,最近想看看磁盘和其中文件的占用情况,于是搜索并学习了一些查看磁盘空间信息的命令,命令虽然简单,但对我自己来说还是有些新的东西值得记录。 1. df 首先,登