网络层
主机 (Host) 是一个 WebSocket 服务器,监听来自终端的传入连接。
终端 (Terminal) 是连接到主机的 WebSocket 客户端。
本文介绍了主机和终端是如何建立连接的,以及终端和终端之间是如何交换信息的。
主机通常可以通过互联网访问,而终端通常位于本地网络中,无法从互联网访问。因此,终端可以连接到主机并通过主机相互传输消息。
终端是对等的,可以被视为一个 P2P 网络。
主机是一个傻组件,仅在终端之间转发消息。而终端是智能组件,可以处理消息。
创建主机
您可以使用我们提供的主机集群服务 (wss://hosts.ntnl.io
),或自行部署一个主机集群 (@yuants/app-hosts
)。
您需要先生成一个 ED25519 密钥对,然后使用私钥签名一个空字符串,得到签名。将公钥和签名拼接到主机连接 URL 中,即可创建一个主机 URL。
主机 URL 形如:
wss://hosts.ntnl.io/?public_key=84KD3d2vGLnYo5ars7eNG7KxZwWai2snuJBMFU8kbeg5&signature=3NP1aeyn88Lj7xJRoaDvzpfcLP8mMEp7CgMyXoQXS6MH5cpnpy62GkdeCkZhSwUdv4EJh8RX761e633cc5QtqeYw
其中,public_key
是主机的公钥;signature
是主机的签名,是用主机私钥对 空字符串 加签名得到的结果。
您需要用这个 URL 来连接到主机。主机集群收到此公钥和签名后,可以验证签名是否与公钥匹配,从而允许您连接到主机。
加入主机的终端并不一定需要知道主机的私钥,终端从主机的创建者处获得主机的连接 URL 即可。
如果私钥意外泄漏,主机的创建者可以随时创建一个新的主机密钥对,将所有服务迁移到新的主机上。
切勿将主机 URL 随意泄露给他人。知道主机令牌的任何人都可以连接到主机并向终端发送消息。
这可能会导致严重的安全问题并损失您的资金和秘密。
连接主机
如果您已经有了主机 URL,您可以使用 Web GUI、@yuants/protocol
的 Terminal
类或者任意其他 WebSocket 客户端连接到主机。
终端在连接主机时,需要在主机 URL 后面带上终端 ID,形如:
wss://hosts.ntnl.io/?public_key=84KD3d2vGLnYo5ars7eNG7KxZwWai2snuJBMFU8kbeg5&signature=3NP1aeyn88Lj7xJRoaDvzpfcLP8mMEp7CgMyXoQXS6MH5cpnpy62GkdeCkZhSwUdv4EJh8RX761e633cc5QtqeYw&terminal_id=haha
其中,terminal_id
是终端的 ID。它在主机中应该是唯一的。这类似于一个 IP 地址。
安全性
讨论安全性之前,首先要明确:我们是谁?我们要防谁?
Yuan 中,所有有意义的数据都是由终端生成的。我们要站在终端的角度思考安全性。
- 网络中间人攻击。例如,网络提供商,黑客等会不会窃听我的消息?
- 软件供应链攻击。例如,开发者会不会在代码中植入后门?
- 来自主机集群的攻击。例如
hosts.ntnl.io
会不会监听我的消息? - 来自主机创建者的攻击。例如,主机的创建者会不会对我不利?
- 来自主机中的其他终端的攻击。例如,主机中的其他终端会不会伪造自身的身份,诱导我将隐私数据发送给他们?
如何防止网络中间人攻击
使用 TLS 证书,将主机 URL 改为 wss://
协议。不要使用 ws://
协议。
更多资料可以参考 维基百科 - 传输层安全性协议。
如何防止软件供应链攻击
我们的代码是开源的,受到开源社区的监督,您可以参与审查源代码。我们不会在代码中做任何损害用户利益的事情。
另外,您可以检查代码包的哈希值,确保您下载的代码包没有被篡改。现代 npm 包管理器已经支持了这一功能,会在安装时自动检查哈希值。
如何防止来自主机集群的攻击
来自主机集群的攻击本质上类似于网络中间人攻击。主机集群由于是网络提供商,它有能力窃听、篡改通过它的消息。
但是,由于主机内部的终端通常都是互相认识的一伙人,他们可以采取预共享密钥的方式,保证主机集群不会窃听消息。
如果终端知道主机私钥,这意味着终端是由主机创建者部署的,或者受到了创建者完整的授权的,那么终端可以直接通过这个私钥派生出一个 AES-GCM 对称密钥,然后将消息加密后发送,由于主机集群不知道私钥,也就无法知道其派生的对称密钥,所以无法解密消息。
如果终端不知道主机的私钥,它可以在部署时,要求主机创建者提供额外的预共享对称密钥。这样仍然可以保证不泄露私钥的情况下,终端可以防御主机集群的攻击。
顺便一提,如果主机集群没有尽责地隔离主机之间的通信,使得另一个主机得以接收您的消息、或者向您发送消息,那也等同于来自主机集群的攻击的情形。我们可以少讨论一种情形,即来自其他主机的攻击。
如何防止来自主机创建者的攻击
好问题,这是一个难题。但你为什么要冒险加入这个主机呢?这是您自己的选择。
为什么不自己成为一个主机创建者呢?您可以自己创建一个主机。这样您就不用担心主机创建者的攻击了。
如何防止来自主机中的其他终端的攻击
更严格的信任问题是,假设您来到了一个主机,您信任这个主机的创建者,但这个主机中有一些其他终端是您不信任的,您担心他们会伪造自己的身份,诱导您将隐私数据发送给他们。
现在的问题是,如何与主机创建者的终端安全地通讯,而让其他的参与者无法获取隐私数据?
经典地,我们假设存在 Alice, Bob, Eve 三个终端。
您是 Alice;Bob 拥有主机私钥;Eve 是恶意终端。
- Eve 有能力 100% 窃听、并篡改主机内的消息,甚至主机集群也和 Eve 是一伙的,目的是诱导 Alice 将隐私数据发送给自己。
- Bob 拥有主机私钥,遵守协议,不存在泄漏 Alice 的隐私数据的动机,但不事先认识 Alice,无法提前将任何信息单独告知 Alice。
- Alice 想要将隐私数据发送给 Bob,且不想将隐私数据发送给 Eve。
怎么做?其实用 X25519 密钥交换算法就可以解决这个问题。
- Alice 生成一个一次性的随机 X25519 密钥对 (Pub_A, Sec_A),将其中的 Pub_A 发送给对方终端。要求对方终端也生成一个一次性随机的 X25519 密钥对 (Pub_B, Sec_B),然后使用 主机私钥 对 "Pub_A+Pub_B" 进行签名。然后将签名连同 Pub_B 一同返回。
- Alice 可以验证签名,看 "Pub_A+Pub_B" 和主机的公钥是否匹配,如果不匹配,说明对方终端不是 Bob,重新生成密钥对,重试。
- Alice 使用得到的密钥 Pub_B 和自己的 Sec_A 计算得到共享密钥 Shared_A_B,用这个密钥加密隐私数据,然后发送给对方终端。
- 如果对方终端是 Bob,他可以使用自己的 Sec_B 和 Alice 的 Pub_A 计算得到相同的 Shared_A_B,用这个密钥解密消息 。
如果对方终端其实是 Eve 呢?
- 如果 Eve 获得了 "Pub_A",由于没有主机私钥,无法直接对任何消息加签名,无法通过 Alice 的验证。
- Eve 只能考虑向 Bob 骗取一个正确消息的签名。假设 Bob 生成的 X25519 密钥对是 (Pub_B, Sec_B)。能被 Alice 接受的签名的消息只有 "Pub_A+?" 的格式。但是 Bob 返回的签名格式一定是 "?+Pub_B" 的格式。两者的交集仅有一个元素: "Pub_A+Pub_B",所以 Eve 无法篡改消息,Alice 一定会获得 Pub_B,Bob 一定会获得 Pub_A。当然,Eve 会获得 Pub_A, Pub_B,但是这并没有什么卵用。
- 根据 X25519 密钥交换算法的数学原理,Alice 和 Bob 一定会得到相同的 Shared_A_B,而可怜的 Eve 无法获得这个密钥,自然没有办法解读用 Shared_A_B 加密后的数据。
- Eve 最多只能无能狂怒地破坏网络,阻挠 Alice 和 Bob 的正常通讯,但是无法窃取隐私数据。
这适合于一些访客场景,例如,主机内的一部分终端是官方的,而另一部分终端是访客的。
访客终端可以通过一些方式验证其他终端的身份是否为官方终端。
当然,访客终端可以访问主机内的所有服务,因此还是需要谨慎对待。