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

构建是绿灯,可那个库是谁加进来的

造出四种必须验证失败的情形

在 TT Lab 中继续学习

目标

亲手为发布制品签名,并手工制造验证应当失败的情况。 制作一个能拒绝篡改、其他密钥、已撤销的密钥、密钥环中没有的密钥这四种情况的验证器。

为什么重要

“加上了签名”这句话什么也不保证。保证产生于验证失败的地方。 所以引入签名体系时,首先要做的不是签名,而是把应当失败的情况 全部写下来,并逐个真正让它失败。尤其是最后两种经常被漏掉—— 签名本身在数学上完好无损,但我们已经不再信任那把密钥。原始密钥没有 过期时间,所以信任到什么时候,必须在外部另行记录。这就是 sigstore 使用短期有效的 证书和透明日志的原因——为的是让验证方不必依赖签名者的密钥管理 习惯。

步骤

  1. 把制品放在 /root/sign/dist 中,并把指纹写入 /root/sign/01-digest.txt。
  2. 在 /root/sign/release.key 和 /root/sign/release.pub 中生成 P-256 密钥对。
  3. 对制品签名,生成 /root/sign/dist/paygate-1.4.2.js.sig。
  4. 把验证输出留在 /root/sign/04-verify.txt 中。
  5. 制作被篡改的副本,把失败输出留在 /root/sign/05-tamper.txt 中。
  6. 用已撤销的旧密钥签名,把失败输出留在 /root/sign/06-oldkey.txt 中。
  7. 把两把密钥连同期限和指纹登记到 /root/sign/keyring.json 中。
  8. 创建 /root/sign/verify-release.sh,并把四种情况的退出码写入 /root/sign/08-checks.json。

参考

把要签名的东西放在一处,并计算指纹

把 /opt/fixtures/sbom/release/paygate-1.4.2.js 复制到 /root/sign/dist/paygate-1.4.2.js,并把该文件的 SHA-256 以小写十六进制 64 个字符的一行保存到 /root/sign/01-digest.txt。

签名针对的不是整个文件,而是摘要。先把这个值握在手里再开始,之后就能用眼睛确认哪里发生了变化。

生成发布密钥对

使用 prime256v1(P-256)曲线生成密钥对,把私钥保存到 /root/sign/release.key,公钥保存到 /root/sign/release.pub。不要使用设置了口令的私钥。

用 openssl ecparam 指定曲线来生成私钥,再给 openssl ec 加上 -pubout,就能从同一把密钥得到公钥。

对摘要签名

用 /root/sign/release.key 对 /root/sign/dist/paygate-1.4.2.js 签名,并保存到 /root/sign/dist/paygate-1.4.2.js.sig。哈希是 SHA-256。

openssl dgst 加上 -sign 就是签名,加上 -verify 就是验证。输出是二进制的,所以请用 -out 写入文件。

记录验证通过的情况

用 /root/sign/release.pub 验证 /root/sign/dist/paygate-1.4.2.js 和 /root/sign/dist/paygate-1.4.2.js.sig,把 openssl 给出的输出保存到 /root/sign/04-verify.txt。

通过时 openssl 只会输出一行。这一行就是这个步骤的证据。

看看把制品改一个字符会怎样

把 /root/sign/dist/paygate-1.4.2.js 复制到 /root/sign/tampered/paygate-1.4.2.js 后修改其内容,用原来的签名 /root/sign/dist/paygate-1.4.2.js.sig 验证,并把失败的输出保存到 /root/sign/05-tamper.txt。

签名是加在摘要上的。哪怕只有一个字节不同,摘要也会整个改变,所以无法再用同一个签名匹配。

制作一个由去年用过又撤销的密钥签名的东西

再生成一对 P-256 密钥,保存到 /root/sign/old-release.key 和 /root/sign/old-release.pub,并用这把密钥对 /root/sign/dist/paygate-1.4.2.js 签名,生成 /root/sign/dist/paygate-1.4.2.js.oldsig。然后用 /root/sign/release.pub 验证这个签名,把失败的输出保存到 /root/sign/06-oldkey.txt。

这个签名不是伪造的——它在数学上是完好的签名,用那把密钥的公钥可以通过验证。问题在于“现在是否仍然信任那把密钥”,而这是签名算法回答不了的。

写下信任哪把密钥、信任到什么时候

在 /root/sign/keyring.json 中以 keys 数组登记两把密钥。每个条目包含 id、file、fingerprint、not_before、not_after、owner。release.pub 的 id 为 release-2026,期限从 2026-01-01 到 2026-12-31;old-release.pub 的 id 为 release-2025,期限从 2025-01-01 到 2025-12-31,owner 都是 payments-platform。file 只写 /root/sign 中的文件名,fingerprint 是把该公钥导出为 DER 后求得的 SHA-256 小写十六进制。

原始密钥没有过期时间。所以“信任到什么时候”必须在外部另行记录,这也是 sigstore 使用短期有效的证书的原因。

制作会查看密钥环的验证器,并判定四种情况

创建 /root/sign/verify-release.sh。以 bash /root/sign/verify-release.sh <산출물> <서명> <키id>(占位符依次为制品、签名、密钥 id)调用时,它会从 /root/sign/keyring.json 中查找该密钥,查看在基准日 2026-09-11 期限是否仍然有效,如果有效,则用该公钥验证签名。通过则以 0 结束;密钥环中没有、已过期或验证失败则以 1 结束。然后运行这四种情况,把退出码以 good、tampered、old_key、unknown_key 保存到 /root/sign/08-checks.json。good 是正常制品和签名加上 release-2026,tampered 是篡改副本加上相同的签名和 release-2026,old_key 是正常制品加上 .oldsig 和 release-2025,unknown_key 是密钥环中没有的 release-2024。

验证器必须回答两个问题——签名是否与这些字节相符,以及现在是否可以信任那把密钥。如果只看其中一个,用已撤销的密钥签名的东西就会原样通过。