签名 OK、比对 FAIL — 当证明指向另一个交付物
目标
在没有互联网的情况下,比对进入隔离网络的交付物与随附的三种证明材料(CycloneDX SBOM、in-toto Statement、SLSA Provenance),确认它们是否真的指向该交付物,并留下把已确认和未能确认分开书写的导入审查报告。
为什么重要
在导入审查中,“签名验证通过”所说明的事实,比想象的要窄得多。该命令所确认的,只是证明材料文档自签名之后没有被改动,交付物根本没有出现在这条命令中。 把证明材料和交付物连在一起的是 subject 的 digest。所以,在能够修改文档的一方也能重新附上签名的情况下,重新计算指纹并比对的那一行,就是流程中唯一的防线。 本实验让你亲手做出没有这一行时会发生什么。做出一份签名有效、但 subject 只指向已经批准的旧版本的证明材料,并把签名层和绑定层说出不同结论这件事记录下来。 最后一步的报告不是为了凑格式。如果不把已确认的内容和在这个网络中无法确认的内容分开,在读者看来就都是已经确认的,而这种误解会一直伴随到批准签字。
步骤
- 原样运行生成脚本,在
/root/supply/drop/下创建交付物,在/root/supply/evidence/下创建五份证明材料以及两个签名。 - 重新计算交付物指纹,与 Statement 的 subject 比对,写入
/root/supply/check/subject.txt。 - 把 SBOM 的组件清单与实际目录树做双向比对,写入
/root/supply/check/sbom-diff.json。 - 从 Provenance 中提取事实,并在
/root/supply/check/provenance.json中把/root/supply/QUESTIONS.txt的六个问题区分开。 - 用
openssl dgst -sha256 -verify确认证明材料的签名,并写入/root/supply/check/signature.txt。 - 在
/root/supply/swap/statement-swapped.json中做出签名有效、但 subject 指向旧版本的证明材料,并把结果分两层写入/root/supply/check/swap.txt。 - 查看三份证明材料的谓词,在
/root/supply/check/predicates.json中区分 accept、hold、reject。 - 在
/root/supply/check/attestation-report.json中撰写供导入审查使用的证明材料比对报告。
参考
- 材料中屏幕上没有附带示例的:问题清单 /root/supply/QUESTIONS.txt、报告中要使用的词语清单 /root/supply/REPORT-KEYS.txt、发包方签名密钥 /root/supply/.vendor/vendor-release.key、公钥 /root/supply/evidence/vendor-release.pub。全部由第 1 步的生成脚本创建。
- 重新计算指纹:
sha256sum /root/supply/drop/collector-2.4.1.tar.gz - 确认签名:
openssl dgst -sha256 -verify /root/supply/evidence/vendor-release.pub -signature /root/supply/evidence/statement.json.sig /root/supply/evidence/statement.json - JSON 用
python3 -c或jq读取。这个 Pod 中既没有互联网也没有 pip,所以只使用标准库。 - 常见错误:只看 subject 的 name 就判断正确,只单向比对 SBOM,把
metadata.component算作组件,忽略不认识的谓词并算作通过。 - 规范原文:in-toto Statement https://github.com/in-toto/attestation/blob/main/spec/v1/statement.md · SLSA Provenance https://slsa.dev/spec/v1.0/provenance · CycloneDX https://cyclonedx.org/specification/overview/
在网络内重现交付物与证明材料包
原样运行生成脚本,创建 /root/supply/drop/collector-2.4.1.tar.gz 和 /root/supply/drop/payload/ 七个文件、/root/supply/archive/collector-2.4.0.tar.gz,以及 /root/supply/evidence/ 下的 sbom.cdx.json、statement.json、qa-signoff.json、vendor-release.pub 和两个签名。/root/supply/QUESTIONS.txt 和 /root/supply/REPORT-KEYS.txt 也会一并生成。
脚本把种子和打包时间固定了。这样运行多少次都会得到同样的指纹,后面步骤的比对也不会因人而异。不要修改脚本,直接使用。签名密钥已存在的话,不会重新生成。
重新计算收到文件的指纹,并与 subject 比对
在 /root/supply/check/subject.txt 中写入交付物指纹、Statement 的 subject 名称与指纹、两个指纹是否一致、_type、predicateType。
subject 是数组。每个元素都有 name 和 digest,规范明确规定 digest 必须存在。名称只是用来区分的标签,对着名称比对,什么也确认不了。是否一致用 YES 或 NO 一个词来写。
对 SBOM 与实际目录树做双向比对
在 /root/supply/check/sbom-diff.json 中写入 SBOM 的组件数、目录树的文件数、完全一致的数量、只存在于文档中的清单、只存在于目录树中的清单,以及指纹不一致的清单。
有三种不一致:只存在于文档中的、只存在于目录树中的、名称两边都有但指纹不同的。只比对清单的做法,会把第三种整个漏掉。路径以 payload 为基准写相对路径,组件只统计 type 为 file 的。
区分 Provenance 能回答和不能回答的问题
在 /root/supply/check/provenance.json 中写入 buildType、builder.id、invocationId、开始和结束时间、externalParameters 的键列表、resolvedDependencies 的数量,并把 /root/supply/QUESTIONS.txt 的六个问题区分为 answerable 和 not-answerable。
判断标准只有一个——predicate 里是否真的有写这个事实的位置。规范区分了:externalParameters 要在下游验证,internalParameters 因为信任平台而无需验证。不要把两者混在键列表中。
确认签名覆盖什么、不覆盖什么
用 openssl dgst -sha256 -verify 确认两份文档的签名,并在 /root/supply/check/signature.txt 中写入公钥指纹、两个验证结果、带签名的文档数和不带签名的文档数,以及签名是否覆盖交付物本身。
看验证命令把哪些文件作为输入。交付物并没有出现在这条命令中。公钥指纹是转换成 DER 之后得出的,而不是 PEM。PEM 中混有头部和换行,所以同一把密钥,不同文件的哈希也可能不同。
做出签名有效但 subject 指向另一个文件的证明材料
在 /root/supply/swap/statement-swapped.json 中,保持谓词不变,只把 subject 改成指向 /root/supply/archive/collector-2.4.0.tar.gz,用同一把密钥重新签名,并把结果分两层写入 /root/supply/check/swap.txt。
不要动原来的证明材料,另做一份副本。修改文档之后重新签名,“签名有效却……”这个反例才成立。连谓词也改的话,会与下一步的“不认识的谓词”问题混在一起,使要点变得模糊。指纹不要从 Statement 中抄,而要对该文件重新计算后确认。
把不认识的谓词放在既不通过也不拒绝的位置
在 /root/supply/check/predicates.json 中写入我们能读懂的谓词清单,以及三份证明材料(evidence/statement.json、evidence/qa-signoff.json、swap/statement-swapped.json)的谓词、是否为已知谓词、subject 是否一致、判定。
判定由两个值推出。读不懂的谓词不能成为通过的依据,但它本身也不是拒绝的依据。in-toto 的解析规则建议把策略写成“必须有好的证明材料才通过”,而不是“只要有坏的证明材料就拦截”。判定值只有 accept、hold、reject 三个。
撰写附在导入审查上的证明材料比对报告
在 /root/supply/check/attestation-report.json 中写入交付物及其指纹、/root/supply/evidence/ 下六个文件的指纹、已确认的内容和未能确认的内容、第 3 步统计出的三种不一致、第 7 步中被搁置的证明材料,以及判定。
把 /root/supply/REPORT-KEYS.txt 中的八个词语,不重复、不遗漏地分到 verified 和 not_verified 中。划分的标准只有一个——我是否在这个 Pod 内亲自计算或验证过。别人写在文档中的主张,不算确认。三个判定值的含义也写在同一个文件中。