TT Lab
开始
学习 学习路径 课程

隔离网络镜像与私有 CA

哈希证明是否变化,签名证明是谁

在 TT Lab 中继续学习

一句话总结

导入不是“搬运文件”,而是一套申请单 → 下载 → 清单 → 签名 → 介质 → 验证 → 安装 → 记录的流程。哈希保证传递过程中是否被改动,签名保证该清单是谁制作的。两者缺一,都只是半套。

为什么需要它

在隔离网络的导入评审中,常见的往来是一个 USB 和一封邮件。USB 里如果一并放着哈希清单文件,人们就会安心,但这份清单,改动文件的人也可以顺手修改它。它能发现传输过程中的损坏,却发现不了被调包。反过来,如果只有签名而没有哈希清单,就不清楚签名签的是什么。而且,如果评审一方收到的申请单写的是“版本用最新的”,那么从外部下载的日子到评审日之间如果发布了新版本,清单与实物就会对不上。

工作原理

申请单要写准确的版本。 要写 requests==2.32.3,而不是 requests;要写 v1.5.2,而不是 rsc.io/quote。如果写范围或“最新”,每次下载的结果都会不同,也就没有人能证明评审记录与导入物是一致的。依赖不是由人写在申请单里,而是把下载工具解析出的结果(pip 的 wheel 清单、Go 的 go.sum)附在清单上。

清单用相对路径。 sha256sum 会以当前目录为基准,打开清单中所写的路径。在包目录内用 find . -type f 生成,无论把包解压到哪里,都能在其中执行 sha256sum -c。清单文件本身和签名文件要从清单中去掉。

对清单签名。 不必对数百个文件逐一签名,只要对一张哈希清单签名,清单就把所有文件联系在一起。用 OpenSSL 3 的 genpkey -algorithm ed25519 生成密钥,用 pkeyutl -sign -rawin 签名,用 pkeyutl -verify -pubin -rawin -sigfile 验证(Ed25519 不单独选择哈希,直接接收原文)。也有很多组织使用 GPG 或 minisign,原理相同。

私钥和公钥走不同的路。 私钥不能离开下载端负责人的 PC。公钥必须通过介质之外的途径(事先登记、单独的文档),提前到达隔离网络一侧。如果把公钥也放进介质,调包的人只要放进自己的公钥就行了。

验证顺序是先签名。 隔离网络一侧要做的是:① 用事先登记的公钥确认清单的签名;② 用该清单确认文件哈希。如果把顺序颠倒,先看哈希,就会把被调包的清单与自己的文件完美吻合,误读为“通过”。而且验证脚本在失败时必须以非 0 值结束,下一步(安装)才会停下来。只打印消息就以 0 结束的验证,在自动化中算不上验证。

在现场相遇的样子

在这个实验镜像中,把 requests 2.32.3(5 个 wheel)和 rsc.io/quote v1.5.2(3 个模块 zip,大部分是 x/text 的 4.8MB)打成一个包,共有 48 个文件,5MB 出头。因为除了 5 个 wheel 和 3 个模块 zip 之外,Go 的 cache/download 中堆放的辅助文件(.mod、.info、.ziphash 以及校验和数据库查询记录)也一并放了进去。这就是清单必须由 find 而不是人来生成的原因。再加上一张 SHA256SUMS 和一张 64 字节的 Ed25519 签名。评分器会把你的验证脚本对两份包副本运行。一份是改动了某个 wheel 的一个字节(应该在哈希处被拦下),另一份是连 SHA256SUMS 也按改动后的文件改过(应该在签名处被拦下)。让第二份通过的脚本,就拦不住调包。

记录也是流程的一部分。申请了什么、包中有多少个文件、清单本身的哈希是什么、用哪把密钥确认了签名、安装了什么,都必须留下,以后才能回答“当时进来的是什么”。

下一项实验要做什么

编写申请单,把 Python wheel 和 Go 模块下载成一个包,并附上 SHA256SUMS 和 Ed25519 签名。制作去掉私钥的介质(tar),在隔离网络一侧解压,并编写先签名、后哈希顺序的验证脚本。评分器会把该脚本对被调包的副本运行。最后在封闭外部的状态下,仅凭包完成安装,并用 JSON 留下导入记录。

参考文档:OpenSSL genpkey、OpenSSL pkeyutl、GNU coreutils sha256sum、pip download、Go Modules Reference