从 TLS 握手探索可编程代理 Pipy
传输层安全 (TLS) 是一种加密协议,旨在提供计算机网络的通信安全。该协议广泛用于电子邮件、即时消息和 IP 语音等应用,但最为常见的应用是 HTTPS。TLS 工作在 OSI 模型的第 6 层表示层,其本身又可分为两层:TLS 记录和 TLS 握手协议。
TLS 的加密传输在 Pipy 的很多应用场景都会用到,比如 HTTPS/TLS 代理、服务网格网 mTLS、加密隧道等。
我们今天就介绍如何使用编程的方式来完成 TLS 握手,并简单了解下 Pipy 的工作方式。
什么是 TLS 握手
TLS 握手是开启 TLS 的通信会话的过程。在 TLS 握手期间,通信的双方交换消息以相互确认、相互验证、建立它们将使用的加密算法并就会话密钥达成一致。
TLS 何时发生
TLS 握手在通过 TCP 握手打开 TCP 连接后发生。
TLS 的处理流程
TLS(传输层安全协议)的握手流程是一个复杂的过程,其中包含了几个关键步骤来确保数据的安全传输。以下是 TLS 握手流程的详细解释:
-
客户端与服务器的通信开始
- ClientHello:客户端发送一个 “ClientHello” 消息给服务器,包含客户端的 TLS 版本、支持的加密算法、支持的压缩方法等信息。
-
服务器的响应
- ServerHello:服务器回应一个 “ServerHello” 消息,其中包含服务器决定使用的协议版本、加密算法、压缩方法等信息。
- 服务器证书:服务器发送其证书给客户端。这个证书中包含了服务器的公钥和一些身份信息。
- 服务器的密钥交换消息:(可选) 如果需要,服务器还可以发送一个密钥交换消息给客户端。
-
客户端的验证和密钥生成
- 证书验证:客户端验证服务器的证书是否被信任的证书颁发机构签发,以及证书是否有效。
- 客户端密钥交换消息:客户端生成一串随机的数据(预主密钥),并使用服务器的公钥加密这串数据,然后发送给服务器。
-
密钥的计算和确认
- 预主密钥解密:服务器使用自己的私钥来解密收到的预主密钥。
- 会话密钥生成:客户端和服务器都使用预主密钥来生成会话密钥,这将用于接下来的数据加密。
-
握手完成
- 更改密码规格消息:客户端和服务器都发送更改密码规格的消息,表示接下来的通信将使用之前协商好的加密参数和密钥。
- 握手完成消息:客户端和服务器都发送握手完成的消息,此时握手过程完成,之后的通信将通过协商好的加密算法和密钥来加密。
-
加密的数据传输
在完成握手过程后,客户端和服务器就可以开始加密的数据传输了。
这个握手流程确保了客户端和服务器能够安全地交换密钥和加密参数,并建立一个安全的加密通道来保护数据的隐私和完整性。在整个过程中,还能验证服务器的身份,以防止中间人攻击。
使用 Pipy 实现 TLS
我们可以通过一段简单的 PipyJS 脚本实现 TLS 的加密传输。
((
certs = {
"example.com": {
cert: new crypto.Certificate("-----BEGIN CERTIFICATE-----\nMIICsjCCAZoCFEOlbMsCFG+UuQtmQX1HJIVcNc3RMA0GCSqGSIb3DQEBCwUAMBUx\nEzARBgNVBAMMCmZsb21lc2guaW8wHhcNMjMwOTE4MjA0MTIzWhcNMjQwOTE3MjA0\nMTIzWjAWMRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAK9FMoKKep5iwXJAYMAs9TC9MyZJO6eeDGiwuRY0NBiu10yn\nXzP6vqrdkW+OeQlwdn3J6g2mFyhwcgJ30Yd+npRK4L+IYSZsW96Cp78TwkzI+MS9\nJJWfdvUbO61cTjCvHe12VvoKxZXf/b2dR44NLXRiSqC8L+fZbM6G9eIgae9CCn0G\ng9rQdgs886bGqcmptCyiA9OBtvGabLwlyYWE77Z5E9aioFsmAaleplOlbbsNB5yk\npidldLKqPDUxnQe5WkWkrUv3pAtcHILUSgWFE3Uz1ISC3b+2FkDNQ7bALSG8bxf0\n0DTapaAEKxk/0+D2s7yLIEtO6065Kg6m6V9OpLkCAwEAATANBgkqhkiG9w0BAQsF\nAAOCAQEAPWIEDl1JW7/1hZCCYZJWDhaNf2edqM+wNm8Jpm+UqU88ug0h/x3ZINVW\niqEbvImqKsskXIp5JHnmVp/5JbbRx4iILgeHkuIpdbw2iJyD33cG89VPw3g9Itqf\nO01Kg1UAxWCZyweB1Iq8vADf0N7fXEJ3z19Hq5ZBFn14dUjXc7/30UuQ5k5I80gX\nka+cZTsHbzqCbYapZvUJI1UFmWSYjjC461aZWbSKNdpBiyrjK6P3h1FDQmswXWlG\nL8JaV5UIfGx/6sDkWgwadp9K6m1k+E0UJd7yEZW6JNfk8SNOTNlBW/rApyl9AQ4b\nUtiOGw191GWDt5HhTBxmyM9F/NTFEw==\n-----END CERTIFICATE-----\n"),
key: new crypto.PrivateKey("-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCvRTKCinqeYsFy\nQGDALPUwvTMmSTunngxosLkWNDQYrtdMp18z+r6q3ZFvjnkJcHZ9yeoNphcocHIC\nd9GHfp6USuC/iGEmbFvegqe/E8JMyPjEvSSVn3b1GzutXE4wrx3tdlb6CsWV3/29\nnUeODS10YkqgvC/n2WzOhvXiIGnvQgp9BoPa0HYLPPOmxqnJqbQsogPTgbbxmmy8\nJcmFhO+2eRPWoqBbJgGpXqZTpW27DQecpKYnZXSyqjw1MZ0HuVpFpK1L96QLXByC\n1EoFhRN1M9SEgt2/thZAzUO2wC0hvG8X9NA02qWgBCsZP9Pg9rO8iyBLTutOuSoO\npulfTqS5AgMBAAECggEAEJDiG56Kb7/2eB2yR1e4e7krms8fLqeXP/88Ged96RCq\n1C7u+6hpLxiEGjQwJ9PpDy3vdlzKzDgHPp55P8JDANHW+PjN8ztuBNOlQT/VLzwW\nRJolWWhaGQP67wF4zncuxUwDH90MHISwT09h/QDX2VaU+9OYVOSjDnOJIqyyS+Uc\nw4fB+K55gR11wd1TnH8Hq20Rc3uNMapUjfluJ76hnkVBWBDBfeEYeJCpPU9rbyCM\nE+3Eu6PWrtljBC379IxF5YCFOEuZcBqKz6kWhqnjjhDnIMeEDyeHGW+k71cNyttZ\n9wjTXjNiDvNsueUEQJWdcGT+RfLkM8Niu9Qq+RIM1QKBgQDtuzibw8Q6/QYfTzO/\nKochbYiULA6NAqxSnuVRqgFabvkH7bTa/K90WWVnNHAdGVAck1b9kpdYzD21FhMT\nT2QVRQpnYWobL/MH58VJcqqCpfmeaBaibnh/wPD2XJX8vlgrD0RLWytokHhi9BUE\n/S1Pcz8hfkQYPNZ65765w14AuwKBgQC8vTWJZyod8y/kQPbF5uxJ72GkM192srwV\n/xU70HXuCtX0vTacADl7bm1Nzu+FusUQpx044dEq7cqOBwnsZ0Bvgr3JElhz6n78\n5w0mBvRtkn9b6WzDCeXWfZkdaYoQkb34B4b3szRfh0Donam8BPN5/zgTO7LGj806\nZ6+aK7EjGwKBgBbt1NLpOdb6qomAiRwqDOiDeQXZjfm5xUcevj13KTRAudIy8hug\n/Yc6TBZ9L0cPNvPanixM5D4TTOxXWbWmFaAbjZpVAffnUwhzKUshPSLmwUGvBmfa\nQdMIl/UbDvhWn4hfq5WdEH3AWWwp4JEfniwokHE5jXXNAF7QVUvzdPCbAoGACYMj\nupwXdFD4XfGkPk8oI4XMDwGD0zCo6BbmFBCqkOe85svOf8hHHWBwY9aFHeFO40r4\n3xAKgbZgWg4iwuZlSfl15Tdme9kas1ZVxE/fa9JRVumJ0L6j9c54tBHIopMl1uVS\nOROwFQx1CgRANLivKLvjMJz2oqlGt6XYJNYE5HcCgYAVYBQW9MSI2cjxwJ1MM2wp\nDyKW5QC5e8/6rJs4pmkQjYbBsa1CHXkS8RisVidaePahbh3bIV61c9zt/bXMi5hf\naheVtJxwiXjPnEF+XWS1FiXRCP6vh9zY1w1FnYVGBHgc7vEjZCmSQyUYzrtNUNcT\nnDYMtA8zAB7Mx4jigl7M/A==\n-----END PRIVATE KEY-----\n"),
}
}
) =>
pipy()
//tls proxy
.listen(8443)
.acceptTLS({
certificate: sni => certs[sni] || null,
verify: () => true,
}).to($ => $.connect('127.0.0.1:8081'))
//http upstream
.listen(8081)
.serveHTTP(new Message('Hello, Pipy!'))
)()
在这段脚本中使用了自执行函数,通过调用 Pipy 的 API pipy()
创建了一个 JS 对象 Configuration
,这个对象可以理解为 Pipy 要使用的配置。Configuration
提供了丰富的 API 方便我们创建 管道和过滤器。
脚本中,通过使用 listen
API 创建了两个 端口管道 分别监听不同的端口:
8081
:使用过滤器serveHTTP
API 返回 HTTP 响应,内容是 “Hello, Pipy!”8443
:一个 TLS 代理,将请求代理到监听8081
的上游。这里需要完成 TLS 握手的服务端流程:接收客户端的“ClientHello”请求,返回包含服务器证书、加密算法等内容的 “ServerHello”,这里使用过滤器acceptTLS
。完成握手后,代理在将请求转发到上游之前对请求进行解密。
除了端口管道外,还有一个匿名 子管道,就是转发请求到上游的部分,使用的过滤器 connect
。
此外还有代理需要使用的证书,通过定义变量的方式配置在脚本中。
Pipy 的实现
前面提到的管道,在配置中确切地应该被叫做 管道布局,可以理解为管道的定义。它规定了管道里使用了哪些过滤器以及过滤器的顺序。真正的“管道”实际上是运行中处理数据的对象(类似管道布局的实例化)。
在处理多个并发请求时,会以同一个 “管道布局” 为模版,实例化出多个 “管道”,每个管道有各自独立的状态,每个请求都可以在独立的管道中处理。
我们以 JS 编程的方式对过滤器和管道进行编排(配置),并保存在脚本文件中。Pipy 在启动时加载定义的脚本,使用内置的 JS 引擎将其解析为 C++ 的对象,获得一系列的管道布局对象。
在完成加载后,开始运行会通过管道布局来创建管道(包括其中的过滤器)。比如本示例中的端口管道会开始监听端口,等待网络数据。
当接收网络数据后,Pipy 会将其封装为 事件Event
,交给管道进行处理。经过一些列的管道和过滤器处理后,管道输出的 Event 将会被解封装重新写回到网络中。
管道和过滤器,都会接收事件并返回事件,也就是说输入和输出都是事件。
总结
本篇通过不到 20 行的代码实现了一个 TLS 的代理,实现 TLS 数据加密传输,并且可以通过简单的配置增加多域名的支持,这就是代理可编程的魔力。
通过 Pipy 丰富的 API 的支持,可以很灵活的支撑各种通用以及定制化的场景,实现更多的功能支持更多的协议。
- 点赞
- 收藏
- 关注作者
评论(0)