Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions lib/sigstore/verifier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ def verify(input:, policy:, offline:)
rescue JSON::ParserError
raise Error::InvalidBundle, "invalid JSON for in-toto statement in DSSE payload"
end
verify_in_toto(input, in_toto)
if (result = verify_in_toto(input, in_toto))
return result
end
else
raise Sigstore::Error::Unimplemented,
"unsupported DSSE payload type: #{bundle.dsse_envelope.payloadType.inspect}"
Expand Down Expand Up @@ -216,25 +218,27 @@ def verify_dsse(dsse_envelope, public_key)
end

def verify_in_toto(input, in_toto_payload)
type = in_toto_payload.fetch("_type")
type = in_toto_payload["_type"]
raise Error::InvalidBundle, "Expected in-toto statement, got #{type.inspect}" unless type == "https://in-toto.io/Statement/v1"

subject = in_toto_payload.fetch("subject")
raise Error::InvalidBundle, "Expected in-toto statement with subject" unless subject && subject.size == 1

subject = subject.first
digest = subject.fetch("digest")
raise Error::InvalidBundle, "Expected in-toto statement with digest" if !digest || digest.empty?
subjects = in_toto_payload["subject"]
raise Error::InvalidBundle, "Expected in-toto statement with subject" if !subjects || subjects.empty?

expected_algorithm = Internal::Util.hash_algorithm_name(input.hashed_input.algorithm)
expected_hexdigest = Internal::Util.hex_encode(input.hashed_input.digest)
digest.each do |name, value|
next if expected_hexdigest == value

return VerificationFailure.new(
"in-toto subject does not match for #{input.hashed_input.algorithm} of #{subject.fetch("name")}: " \
"expected #{name} to be #{value}, got #{expected_hexdigest}"
matched = subjects.map do |subject|
digest = subject["digest"]
raise Error::InvalidBundle, "Expected in-toto statement with digest" if !digest || digest.empty?

digest[expected_algorithm] == expected_hexdigest
end.any?

return if matched

VerificationFailure.new(
"None of in-toto subjects matches artifact for #{expected_algorithm}: #{expected_hexdigest}"
)
end
end

public
Expand Down
119 changes: 119 additions & 0 deletions test/sigstore/verifier_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,125 @@
require "sigstore/verifier"

class Sigstore::VerifierTest < Test::Unit::TestCase
HEXDIGEST256 = "01234567" * 8
OTHER_HEXDIGEST256 = "0" * 64

HashedInput = Struct.new(:hashed_input)

def make_input(hexdigest)
digest = [hexdigest].pack("H*")
hashed_input = Sigstore::Common::V1::HashOutput.new
hashed_input.algorithm = Sigstore::Common::V1::HashAlgorithm::SHA2_256
hashed_input.digest = digest
HashedInput.new(hashed_input)
end

def make_payload(subjects)
{
"_type" => "https://in-toto.io/Statement/v1",
"subject" => subjects,
"predicateType" => "https://slsa.dev/provenance/v1",
"predicate" => {}
}
end

def test_verify_in_toto_single_subject_matches
verifier = Sigstore::Verifier.allocate
input = make_input(HEXDIGEST256)
payload = make_payload([
{ "name" => "artifact.txt", "digest" => { "sha256" => HEXDIGEST256 } }
])
assert_nil verifier.send(:verify_in_toto, input, payload)
end

def test_verify_in_toto_multiple_subjects_first_matches
verifier = Sigstore::Verifier.allocate
input = make_input(HEXDIGEST256)
payload = make_payload([
{ "name" => "artifact.txt", "digest" => { "sha256" => HEXDIGEST256 } },
{ "name" => "other.txt", "digest" => { "sha256" => OTHER_HEXDIGEST256 } }
])
assert_nil verifier.send(:verify_in_toto, input, payload)
end

def test_verify_in_toto_multiple_subjects_second_matches
verifier = Sigstore::Verifier.allocate
input = make_input(HEXDIGEST256)
payload = make_payload([
{ "name" => "other.txt", "digest" => { "sha256" => OTHER_HEXDIGEST256 } },
{ "name" => "artifact.txt", "digest" => { "sha256" => HEXDIGEST256 } }
])
assert_nil verifier.send(:verify_in_toto, input, payload)
end

def test_verify_in_toto_no_subject_matches
verifier = Sigstore::Verifier.allocate
input = make_input(HEXDIGEST256)
payload = make_payload([
{ "name" => "other.txt", "digest" => { "sha256" => OTHER_HEXDIGEST256 } }
])
result = verifier.send(:verify_in_toto, input, payload)
assert_kind_of Sigstore::VerificationFailure, result
end

def test_verify_in_toto_wrong_algorithm_does_not_match
verifier = Sigstore::Verifier.allocate
input = make_input(HEXDIGEST256)
payload = make_payload([
{ "name" => "artifact.txt", "digest" => { "sha512_256" => HEXDIGEST256 } }
])
result = verifier.send(:verify_in_toto, input, payload)
assert_kind_of Sigstore::VerificationFailure, result
end

def test_verify_in_toto_no_subjects_raises
verifier = Sigstore::Verifier.allocate
input = make_input(HEXDIGEST256)
payload = make_payload(nil)
assert_raise Sigstore::Error::InvalidBundle do
verifier.send(:verify_in_toto, input, payload)
end
end

def test_verify_in_toto_empty_subjects_raises
verifier = Sigstore::Verifier.allocate
input = make_input(HEXDIGEST256)
payload = make_payload([])
assert_raise Sigstore::Error::InvalidBundle do
verifier.send(:verify_in_toto, input, payload)
end
end

def test_verify_in_toto_no_digest_raises
verifier = Sigstore::Verifier.allocate
input = make_input(HEXDIGEST256)
payload = make_payload([{ "name" => "artifact.txt" }])
assert_raise Sigstore::Error::InvalidBundle do
verifier.send(:verify_in_toto, input, payload)
end
end

def test_verify_in_toto_empty_digest_raises
verifier = Sigstore::Verifier.allocate
input = make_input(HEXDIGEST256)
payload = make_payload([{ "name" => "artifact.txt", "digest" => {} }])
assert_raise Sigstore::Error::InvalidBundle do
verifier.send(:verify_in_toto, input, payload)
end
end

def test_verify_in_toto_wrong_type_raises
verifier = Sigstore::Verifier.allocate
input = make_input(HEXDIGEST256)
payload = make_payload([
{ "name" => "artifact.txt", "digest" => { "sha256" => HEXDIGEST256 } }
])
payload["_type"] = "https://in-toto.io/Statement/v0.1"
assert_raise Sigstore::Error::InvalidBundle do
verifier.send(:verify_in_toto, input, payload)
end
end

def test_pack_digitally_signed_precertificate
verifier = Sigstore::Verifier.allocate
[3, 255, 1024, 16_777_215].each do |precert_bytes_len|
Expand Down
Loading