一覧SD-14-3 · 依存・ビルド・配布の防御

配布物の署名と来歴の検証

署名の正しさと、信頼する発行元・対象・工程の一致を別の条件として検証できます。

説明

署名の検証に成功しても、その配布物が期待するリポジトリやビルド工程から作られたかは別に確認します。署名対象に成果物のダイジェストと来歴を含め、受入側の条件へ照合します。

公開のEd25519鍵で固定アーカイブの来歴文を署名します。成果物、署名文、署名者が正しく署名した別工程の文を順に変え、拒否される条件を比較します。

署名の正しさと、信頼する発行元・対象・工程の一致を別の条件として検証できます。

1成果物と署名する来歴を用意する

subjectはアーカイブのSHA-256です。builder、repository、refも署名対象へ入れます。

from sd14 import * data = artifact("1.0.0") s,signature = statement(data)成果物と来歴を結び付けて署名する print(s) print(len(signature))

署名は64バイトです。この教材のJSON文は固定試料で、SLSAの全スキーマやDSSE形式そのものではありません。

2信頼した鍵と受入条件で照合する

鍵は試料の受入側へ事前に設定したものを使います。

print(verify(data,s,signature)署名・成果物・来歴を照合する)

verifiedです。配布物に同梱された未知の鍵へ自動的に信頼を移さず、受入側の信頼設定を使います。

3成果物だけを差し替える

署名文を保ったまま版1.0.1を渡します。

print(verify(artifact("1.0.1")異なるバイト列の成果物へ置き換える,s,signature))

artifact_mismatchです。来歴に書かれたダイジェストと実際の配布物が一致しません。

4来歴文だけを書き換える

署名を作り直さずbuilderを書き換えます。

changed = dict(s,builder=署名済み文の一項目を変える"other-builder") print(verify(data,changed,signature))

signature_invalidです。署名対象を変えたため、署名検証の段階で拒否します。

5正しい署名でも別工程の成果物を拒否する

同じ鍵で正しく署名した別builderと別refを比較します。

for change in ({"builder":"other-builder"},{"ref":"refs/heads/unreviewed"}): other,sig = statement(data,**change)異なる来歴へ正しい署名を付ける print(verify(data,other,sig))

どちらもprovenance_rejectedです。署名できる主体が作ったものすべてを受け入れず、期待する工程の条件も確認します。

6更新版へ対応する署名を作る

同じ受入方針の下で、更新版のバイト列に対応する来歴を署名します。

updated = artifact("1.0.1") new_statement,new_signature = statement(updated)更新版のバイト列に署名を結び付ける print(verify(updated,new_statement,new_signature)) print(s["subject"]==new_statement["subject"])

更新版はverified、subjectの一致はFalseです。ファイル名やタグ名が同じでも、対象ダイジェストを更新して照合します。

7検証済み成果物の内容を回帰確認する

署名が正しくても機能の正しさは別なので、固定アーカイブの版と実装を読みます。

with tarfile.open(fileobj=io.BytesIO(updated)) as tar: print(tar.extractfile("package/version.txt").read()) source = tar.extractfile("package/parser.py").read() namespace = {} exec(source,namespace) print(namespace["parse"]検証済み試料の固定機能を試す(" public "))

版1.0.1で、固定のparseは両端空白を除いてpublicを返します。検証済みの公開試料だけを実行し、真正性と機能検査を両方確認しました。

まとめ

署名へ成果物ダイジェストと来歴を結び付けます。

信頼する鍵に加え、期待する工程の条件を照合します。

真正性の検証後も機能の回帰確認を行います。

操作履歴と解説を振り返れます。

Python 3.15 / 固定の公開試料

1
from sd14 import * data = artifact("1.0.0") s,signature = statement(data) print(s) print(len(signature))
{'subject': 'e849b95b8a7bd0d18300a3443aafb63fb743b0676ee1f68d314753d60c12f550', 'builder': 'builder-7', 'repository': 'https://code.example.test/app', 'ref': 'refs/tags/v1', 'source_digest': 'public-source-7'}
64
2
print(verify(data,s,signature))
verified
3
print(verify(artifact("1.0.1"),s,signature))
artifact_mismatch
4
changed = dict(s,builder="other-builder") print(verify(data,changed,signature))
signature_invalid
5
for change in ({"builder":"other-builder"},{"ref":"refs/heads/unreviewed"}): other,sig = statement(data,**change) print(verify(data,other,sig))
provenance_rejected
provenance_rejected
6
updated = artifact("1.0.1") new_statement,new_signature = statement(updated) print(verify(updated,new_statement,new_signature)) print(s["subject"]==new_statement["subject"])
verified
False
7
with tarfile.open(fileobj=io.BytesIO(updated)) as tar: print(tar.extractfile("package/version.txt").read()) source = tar.extractfile("package/parser.py").read() namespace = {} exec(source,namespace) print(namespace["parse"](" public "))
b'1.0.1'
public

演習完了