嘿。我是 Mrinal Wadhwa, Ockam的首席技术官。在 Ockam 的早期,我们正在开发一个 C 库。这就是为什么几个月后我们决定放弃数万行 C 代码并用Rust重写的故事。
在我们开始之前,本周我与 InfluxData 的 CTO Paul Dix 一起参加了一场录制的网络研讨会,我们在会上讨论了 InfluxDB 和 Ockam 在 Rust 中的重写。为什么这两个开源项目选择重写,为什么我们选择 Rust 作为我们的新语言,我们一路上学到的经验教训等等。请检查一下录音。这是一次富有洞察力的讨论。
Ockam 使开发人员能够构建可以信任动态数据的应用程序。我们为您提供简单的工具,可以将端到端加密和相互验证的通信添加到任何环境中运行的任何应用程序。您的应用程序可以跨私有网络、多个云之间、通过Kafka中的消息流,通过任何多跳、多协议拓扑,获得数据完整性、真实性和机密性的端到端保证。所有通信都经过端到端身份验证且私密。
我们还使困难部分变得超级容易扩展 - 引导信任关系、安全管理密钥、轮换/撤销短期凭证、强制执行基于属性的授权策略等。最终结果是 - 您可以构建对以下内容具有精细控制的应用程序每一个信任和访问决策 - 应用程序都是私密且安全的设计。
2019 年,我们开始用 C 语言构建所有这些。我们希望 Ockam 能够在任何地方运行 - 从受限的边缘设备到强大的云服务器。我们还希望 Ockam 可用于任何类型的应用程序 - 无论应用程序是使用哪种语言构建的。
这使得 C 成为一个明显的候选者。它可以为 99% 的计算机进行编译,并且几乎可以在任何地方运行(一旦您弄清楚如何处理所有特定于目标的工具链)。所有其他流行语言都可以通过某种形式的本机函数接口调用 C 库 - 因此我们稍后可以为其他所有语言提供语言惯用包装器:Typescript、Python、Elixir、Java 等。
我们的想法是,我们将保持以通信为中心的协议的核心与任何特定于硬件的行为分离,并为我们想要支持的硬件提供可插拔的适配器。例如,将有用于在各种 HSM 中存储密钥的适配器、用于各种传输协议的适配器等。
我们的计划是将我们的核心实现为 C 库。然后,我们将用其他语言的包装器包装这个 C 库,并在可插拔硬件适配器的帮助下在任何地方运行它。
但是,我们也非常关心简单性——这就是我们的名字。我们希望 Ockam 易于使用、易于构建和易于维护。
Ockam 的核心是加密和基于消息的协议的分层堆栈,例如 Ockam安全通道和 Ockam路由。这些是异步、多步骤、有状态的通信协议,我们希望从应用程序开发人员那里抽象出这些协议的所有细节。我们将用户体验想象为单个单行函数调用,以创建端到端的经过身份验证和加密的安全通道。
与密码学相关的代码也往往有很多枪口,稍有失误,你的系统就会变得不安全。因此,简单性对我们来说不仅仅是一种审美理想,我们认为这是确保我们能够帮助每个人构建安全系统的关键要求。没有必要了解所涉及的每个协议的实质内容。我们希望隐藏这些“脚枪”,并提供易于正确使用的开发人员界面,并且不可能/难以使用,从而导致您的应用程序搬起石头砸自己的脚。
这就是C严重缺乏的地方。
我们尝试用 C 语言公开安全且简单的接口,但没有成功。在每次迭代中,我们发现应用程序开发人员需要了解有关协议状态和状态转换的太多细节。
大约在那个时候,我编写了一个在 Elixir 中通过 Ockam路由创建 Ockam安全通道的原型。
Elixir 程序在 BEAM(Erlang 虚拟机)上运行。 BEAM 提供 Erlang 进程。 Erlang 进程是轻量级的有状态并发参与者。由于参与者可以在维护内部状态的同时并发运行,因此运行有状态协议 Ockam Transports + Ockam Routing + Ockam Secure Channels 的并发堆栈变得很容易。
我能够隐藏所有有状态层并创建一个简单的单行函数,有人可以调用该函数来通过各种多跳、多协议路由创建端到端加密的安全通道。
{:ok, channel} = Ockam.SecureChannel.create(route, vault, keypair)
应用程序开发人员将调用这个简单的函数,并且多个并发参与者将运行有状态协议的底层。当通道建立或出现错误时,该函数将返回。这正是我们想要的界面。
但 Elixir 与 C 不同。它在小型/受限计算机上运行得不太好,而且它也不是封装在特定于语言的惯用包装器中的好选择。
此时,我们知道我们想要实现轻量级 Actor,但我们也知道 C 不会让这变得容易。从那时起,我开始深入研究 Rust,并很快遇到了一些让 Rust 非常有吸引力的事情:
Rust 库可以导出与 C 调用约定兼容的接口。这意味着任何可以静态或动态链接和调用 C 库中函数的语言或运行时也可以以完全相同的方式链接和调用 Rust 库中的函数。由于大多数语言都支持 C 中的本机函数,因此它们也已经支持 Rust 中的本机函数。从我们要求核心库具有特定于语言的包装器的角度来看,这使得 Rust 等于 C。
Rust 使用 LLVM 进行编译,这意味着它可以针对大量计算机。这个集合可能不像 C 可以使用 GCC 和各种专有 GCC 分支来定位的所有东西那么大,但仍然是一个非常大的子集,并且正在进行让 Rust 使用 GCC 进行编译的工作。随着新的 LLVM 目标的支持不断增长以及 Rust 中潜在的 GCC 支持,从我们能够在任何地方运行的要求的角度来看,这似乎是一个不错的选择。
Rust 的类型系统允许我们将不变量转化为编译时错误。这使得在开发时更容易发现错误,从而减少了可能发生的错误。我们的团队和 Rust 库的用户将行为错误或安全漏洞带入生产的可能性越来越小。
Rust 的内存安全功能消除了释放后使用、双重释放、溢出、越界访问、数据竞争和许多其他常见错误的可能性,这些错误已知会导致大型 C 中 60-70% 的高严重性漏洞或 C++ 代码库。 Rust 在编译时提供了这种安全性,而不会产生使用垃圾收集器在运行时安全管理内存的性能成本。这为 Rust 提供了巨大的优势,可以编写需要高性能、在受限环境中运行且高度安全的代码。
最后让我相信 Rust 非常适合 Ockam 的是async/await
。我们已经确定我们需要轻量级参与者来创建一个简单且安全的接口奥卡姆协议栈。 async/await
意味着创建 actor 的大量艰苦工作已经在 tokio 和 async-std 等项目中完成。我们可以在此基础上构建 Ockam 的 Actor 实现。
另一个突出的重要方面是 Rust 中的async/await
与 Javascript 等其他语言中的async/await
有一个显着的区别。在 Javascript 中,浏览器引擎或 Nodejs 选择运行异步函数的方式。但在 Rust 中,你可以插入你自己选择的机制。这些称为异步运行时 - tokio 是此类运行时的一个流行示例,它针对高度可扩展的系统进行了优化。但我们并不总是必须使用 tokio,我们可以选择针对微型嵌入式设备或微控制器优化的异步运行时。
这意味着 Ockam 的 actor 实现(我们后来称为 Ockam Workers ) ,如果我们基于 Rust 的async/await
可以向我们的用户呈现完全相同的界面,无论它在哪里运行 - 大型计算机或小型计算机。我们所有位于 Ockam Workers 之上的协议接口也可以呈现完全相同的简单接口 - 无论它们在哪里运行。
此时我们确信我们应该用 Rust 重写 Ockam。在我之前提到的网络研讨会对话中,Paul Dix 和我讨论了 Ockam 和 InfluxDB 团队在每个项目决定切换到 Rust 后的过渡情况。我们讨论了 InfluxDB 如何从 Go 迁移到 Rust,以及 Ockam 如何从 C 迁移到 Rust。如果您对我们旅程的那部分感兴趣,可以听听录音。
经过多次迭代后,任何人现在都可以使用 Rust 中的 Ockam 箱,通过简单的函数调用创建端到端加密且相互验证的安全通道。
这是我们一开始就想象的一行:
let channel = node.create_secure_channel(&identity, route, options).await?;
它通过任意多跳、多协议路由创建一个经过身份验证和加密的通道,可以跨越专用网络和云。我们能够将所有潜在的复杂性和枪械隐藏在这个简单而安全的函数调用背后。无论您如何在可扩展服务器或微型微控制器上使用它,代码都保持不变。
要了解更多信息,请查看GitHub 上的 kout Ockam或尝试Ockam Rust 库和Ockam Command的分步演练。
也发布在这里。
如果您参与过用 Rust 重写的项目,请在我们的Discord 服务器上与我们分享您团队的故事。我们还在招聘 Rust 和 Elixir 职位,欢迎加入我们的构建者团队- 我们致力于通过设计使软件更加安全和私密。