handle backfilled attestation subjects correctly
Signed-off-by: William Woodruff <william@yossarian.net>
This commit is contained in:
parent
e2b5d93198
commit
faa00c8c79
@ -63,9 +63,9 @@ module Homebrew
|
|||||||
# @api private
|
# @api private
|
||||||
sig {
|
sig {
|
||||||
params(bottle: Bottle, signing_repo: String,
|
params(bottle: Bottle, signing_repo: String,
|
||||||
signing_workflow: T.nilable(String)).returns(T::Hash[T.untyped, T.untyped])
|
signing_workflow: T.nilable(String), subject: T.nilable(String)).returns(T::Hash[T.untyped, T.untyped])
|
||||||
}
|
}
|
||||||
def self.check_attestation(bottle, signing_repo, signing_workflow = nil)
|
def self.check_attestation(bottle, signing_repo, signing_workflow = nil, subject = nil)
|
||||||
cmd = [gh_executable, "attestation", "verify", bottle.cached_download, "--repo", signing_repo, "--format",
|
cmd = [gh_executable, "attestation", "verify", bottle.cached_download, "--repo", signing_repo, "--format",
|
||||||
"json"]
|
"json"]
|
||||||
|
|
||||||
@ -86,9 +86,9 @@ module Homebrew
|
|||||||
# `gh attestation verify` returns a JSON array of one or more results,
|
# `gh attestation verify` returns a JSON array of one or more results,
|
||||||
# for all attestations that match the input's digest. We want to additionally
|
# for all attestations that match the input's digest. We want to additionally
|
||||||
# filter these down to just the attestation whose subject matches the bottle's name.
|
# filter these down to just the attestation whose subject matches the bottle's name.
|
||||||
bottle_name = bottle.filename.to_s
|
subject = bottle.filename.to_s if subject.blank?
|
||||||
attestation = attestations.find do |a|
|
attestation = attestations.find do |a|
|
||||||
a.dig("verificationResult", "statement", "subject", 0, "name") == bottle_name
|
a.dig("verificationResult", "statement", "subject", 0, "name") == subject
|
||||||
end
|
end
|
||||||
|
|
||||||
raise InvalidAttestationError, "no attestation matches subject" if attestation.blank?
|
raise InvalidAttestationError, "no attestation matches subject" if attestation.blank?
|
||||||
@ -112,7 +112,16 @@ module Homebrew
|
|||||||
return attestation
|
return attestation
|
||||||
rescue InvalidAttestationError
|
rescue InvalidAttestationError
|
||||||
odebug "falling back on backfilled attestation for #{bottle}"
|
odebug "falling back on backfilled attestation for #{bottle}"
|
||||||
backfill_attestation = check_attestation bottle, BACKFILL_REPO, BACKFILL_REPO_CI_URI
|
|
||||||
|
# Our backfilled attestation is a little unique: the subject is not just the bottle
|
||||||
|
# filename, but also has the bottle's hosted URL hash prepended to it.
|
||||||
|
# This was originally unintentional, but has a virtuous side effect of further
|
||||||
|
# limiting domain separation on the backfilled signatures (by committing them to
|
||||||
|
# their original bottle URLs).
|
||||||
|
url_sha256 = Digest::SHA256.hexdigest(bottle.url)
|
||||||
|
subject = "#{url_sha256}--#{bottle.filename}"
|
||||||
|
|
||||||
|
backfill_attestation = check_attestation bottle, BACKFILL_REPO, BACKFILL_REPO_CI_URI, subject
|
||||||
timestamp = backfill_attestation.dig("verificationResult", "verifiedTimestamps",
|
timestamp = backfill_attestation.dig("verificationResult", "verifiedTimestamps",
|
||||||
0, "timestamp")
|
0, "timestamp")
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,10 @@ RSpec.describe Homebrew::Attestation do
|
|||||||
let(:fake_gh) { Pathname.new("/extremely/fake/gh") }
|
let(:fake_gh) { Pathname.new("/extremely/fake/gh") }
|
||||||
let(:cached_download) { "/fake/cached/download" }
|
let(:cached_download) { "/fake/cached/download" }
|
||||||
let(:fake_bottle_filename) { instance_double(Bottle::Filename, to_s: "fakebottle--1.0.faketag.bottle.tar.gz") }
|
let(:fake_bottle_filename) { instance_double(Bottle::Filename, to_s: "fakebottle--1.0.faketag.bottle.tar.gz") }
|
||||||
let(:fake_bottle) { instance_double(Bottle, cached_download:, filename: fake_bottle_filename) }
|
let(:fake_bottle_url) { "https://example.com/#{fake_bottle_filename}" }
|
||||||
|
let(:fake_bottle) do
|
||||||
|
instance_double(Bottle, cached_download:, filename: fake_bottle_filename, url: fake_bottle_url)
|
||||||
|
end
|
||||||
let(:fake_json_resp) do
|
let(:fake_json_resp) do
|
||||||
JSON.dump([
|
JSON.dump([
|
||||||
{ verificationResult: {
|
{ verificationResult: {
|
||||||
@ -15,6 +18,16 @@ RSpec.describe Homebrew::Attestation do
|
|||||||
} },
|
} },
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
let(:fake_json_resp_backfill) do
|
||||||
|
JSON.dump([
|
||||||
|
{ verificationResult: {
|
||||||
|
verifiedTimestamps: [{ timestamp: "2024-03-13T00:00:00Z" }],
|
||||||
|
statement: {
|
||||||
|
subject: [{ name: "#{Digest::SHA256.hexdigest(fake_bottle_url)}--#{fake_bottle_filename}" }],
|
||||||
|
},
|
||||||
|
} },
|
||||||
|
])
|
||||||
|
end
|
||||||
let(:fake_json_resp_too_new) do
|
let(:fake_json_resp_too_new) do
|
||||||
JSON.dump([
|
JSON.dump([
|
||||||
{ verificationResult: {
|
{ verificationResult: {
|
||||||
@ -113,7 +126,7 @@ RSpec.describe Homebrew::Attestation do
|
|||||||
.with(fake_gh, "attestation", "verify", cached_download, "--repo",
|
.with(fake_gh, "attestation", "verify", cached_download, "--repo",
|
||||||
described_class::BACKFILL_REPO, "--format", "json", "--cert-identity",
|
described_class::BACKFILL_REPO, "--format", "json", "--cert-identity",
|
||||||
described_class::BACKFILL_REPO_CI_URI)
|
described_class::BACKFILL_REPO_CI_URI)
|
||||||
.and_return(fake_json_resp)
|
.and_return(fake_json_resp_backfill)
|
||||||
|
|
||||||
described_class.check_core_attestation fake_bottle
|
described_class.check_core_attestation fake_bottle
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user