cask: add audit for incorrect signing

This commit is contained in:
Sean Molenaar 2022-08-01 14:30:04 +02:00
parent b556db72fb
commit e90371f8ab
No known key found for this signature in database
GPG Key ID: 6BF5D8DF0D34FAAE
4 changed files with 96 additions and 7 deletions

View File

@ -20,28 +20,31 @@ module Cask
attr_reader :cask, :download attr_reader :cask, :download
attr_predicate :appcast?, :new_cask?, :strict?, :online?, :token_conflicts? attr_predicate :appcast?, :new_cask?, :strict?, :signing?, :online?, :token_conflicts?
def initialize(cask, appcast: nil, download: nil, quarantine: nil, def initialize(cask, appcast: nil, download: nil, quarantine: nil,
token_conflicts: nil, online: nil, strict: nil, token_conflicts: nil, online: nil, strict: nil, signing: nil,
new_cask: nil) new_cask: nil)
# `new_cask` implies `online` and `strict` # `new_cask` implies `online`, `token_conflicts`, `strict` and signing
online = new_cask if online.nil? online = new_cask if online.nil?
strict = new_cask if strict.nil? strict = new_cask if strict.nil?
signing = new_cask if signing.nil?
token_conflicts = new_cask if token_conflicts.nil?
# `online` implies `appcast` and `download` # `online` implies `appcast` and `download`
appcast = online if appcast.nil? appcast = online if appcast.nil?
download = online if download.nil? download = online if download.nil?
# `new_cask` implies `token_conflicts` # `signing` implies `download`
token_conflicts = new_cask if token_conflicts.nil? download = signing if download.nil?
@cask = cask @cask = cask
@appcast = appcast @appcast = appcast
@download = Download.new(cask, quarantine: quarantine) if download @download = Download.new(cask, quarantine: quarantine) if download
@online = online @online = online
@strict = strict @strict = strict
@signing = signing
@new_cask = new_cask @new_cask = new_cask
@token_conflicts = token_conflicts @token_conflicts = token_conflicts
end end
@ -81,6 +84,7 @@ module Cask
check_github_repository_archived check_github_repository_archived
check_github_prerelease_version check_github_prerelease_version
check_bitbucket_repository check_bitbucket_repository
check_signing
self self
rescue => e rescue => e
odebug e, e.backtrace odebug e, e.backtrace
@ -550,6 +554,33 @@ module Cask
add_error "download not possible: #{e}" add_error "download not possible: #{e}"
end end
def check_signing
return if !signing? || download.blank? || cask.url.blank?
odebug "Auditing signing"
odebug cask.artifacts
artifacts = cask.artifacts.select { |k| k.is_a?(Artifact::Pkg) || k.is_a?(Artifact::App) }
return if artifacts.empty?
downloaded_path = download.fetch
primary_container = UnpackStrategy.detect(downloaded_path, type: @cask.container&.type, merge_xattrs: true)
return if primary_container.nil?
Dir.mktmpdir do |tmpdir|
tmpdir = Pathname(tmpdir)
primary_container.extract_nestedly(to: tmpdir, basename: downloaded_path.basename, verbose: false)
cask.artifacts.each do |artifact|
result = system_command("codesign", args: [
"--verify",
tmpdir/artifact.source.basename,
], print_stderr: false)
add_warning result.merged_output unless result.success?
end
end
end
def check_livecheck_version def check_livecheck_version
return unless appcast? return unless appcast?

View File

@ -15,6 +15,7 @@ module Cask
audit_online: nil, audit_online: nil,
audit_new_cask: nil, audit_new_cask: nil,
audit_strict: nil, audit_strict: nil,
audit_signing: nil,
audit_token_conflicts: nil, audit_token_conflicts: nil,
quarantine: nil, quarantine: nil,
any_named_args: nil, any_named_args: nil,
@ -29,6 +30,7 @@ module Cask
audit_online: audit_online, audit_online: audit_online,
audit_new_cask: audit_new_cask, audit_new_cask: audit_new_cask,
audit_strict: audit_strict, audit_strict: audit_strict,
audit_signing: audit_signing,
audit_token_conflicts: audit_token_conflicts, audit_token_conflicts: audit_token_conflicts,
quarantine: quarantine, quarantine: quarantine,
any_named_args: any_named_args, any_named_args: any_named_args,
@ -46,6 +48,7 @@ module Cask
audit_appcast: nil, audit_appcast: nil,
audit_online: nil, audit_online: nil,
audit_strict: nil, audit_strict: nil,
audit_signing: nil,
audit_token_conflicts: nil, audit_token_conflicts: nil,
audit_new_cask: nil, audit_new_cask: nil,
quarantine: nil, quarantine: nil,
@ -60,6 +63,7 @@ module Cask
@audit_online = audit_online @audit_online = audit_online
@audit_new_cask = audit_new_cask @audit_new_cask = audit_new_cask
@audit_strict = audit_strict @audit_strict = audit_strict
@audit_signing = audit_signing
@quarantine = quarantine @quarantine = quarantine
@audit_token_conflicts = audit_token_conflicts @audit_token_conflicts = audit_token_conflicts
@any_named_args = any_named_args @any_named_args = any_named_args
@ -133,6 +137,7 @@ module Cask
appcast: @audit_appcast, appcast: @audit_appcast,
online: @audit_online, online: @audit_online,
strict: @audit_strict, strict: @audit_strict,
signing: @audit_signing,
new_cask: @audit_new_cask, new_cask: @audit_new_cask,
token_conflicts: @audit_token_conflicts, token_conflicts: @audit_token_conflicts,
download: @audit_download, download: @audit_download,

View File

@ -19,6 +19,8 @@ module Cask
description: "Audit the appcast" description: "Audit the appcast"
switch "--[no-]token-conflicts", switch "--[no-]token-conflicts",
description: "Audit for token conflicts" description: "Audit for token conflicts"
switch "--[no-]signing",
description: "Audit for signed apps, which is required on ARM"
switch "--[no-]strict", switch "--[no-]strict",
description: "Run additional, stricter style checks" description: "Run additional, stricter style checks"
switch "--[no-]online", switch "--[no-]online",
@ -50,6 +52,7 @@ module Cask
appcast: args.appcast?, appcast: args.appcast?,
online: args.online?, online: args.online?,
strict: args.strict?, strict: args.strict?,
signing: args.signing?,
new_cask: args.new_cask?, new_cask: args.new_cask?,
token_conflicts: args.token_conflicts?, token_conflicts: args.token_conflicts?,
quarantine: args.quarantine?, quarantine: args.quarantine?,
@ -71,6 +74,7 @@ module Cask
appcast: nil, appcast: nil,
online: nil, online: nil,
strict: nil, strict: nil,
signing: nil,
new_cask: nil, new_cask: nil,
token_conflicts: nil, token_conflicts: nil,
quarantine: nil, quarantine: nil,
@ -84,6 +88,7 @@ module Cask
audit_appcast: appcast, audit_appcast: appcast,
audit_online: online, audit_online: online,
audit_strict: strict, audit_strict: strict,
audit_signing: signing,
audit_new_cask: new_cask, audit_new_cask: new_cask,
audit_token_conflicts: token_conflicts, audit_token_conflicts: token_conflicts,
quarantine: quarantine, quarantine: quarantine,

View File

@ -411,6 +411,53 @@ describe Cask::Audit, :cask do
end end
end end
describe "signing checks" do
let(:download_double) { instance_double(Cask::Download) }
let(:unpack_double) { instance_double(UnpackStrategy::Zip) }
before do
allow(audit).to receive(:download).and_return(download_double)
allow(audit).to receive(:signing?).and_return(true)
allow(audit).to receive(:check_https_availability)
end
context "when cask is not using a signed artifact" do
let(:cask) do
tmp_cask "signing-cask-test", <<~RUBY
cask 'signing-cask-test' do
version '1.0'
url "https://brew.sh/index.html"
binary 'Audit.app'
end
RUBY
end
it "does not fail" do
expect(download_double).not_to receive(:fetch)
expect(UnpackStrategy).not_to receive(:detect)
expect(run).not_to warn_with(/Audit\.app/)
end
end
context "when cask is using a signed artifact" do
let(:cask) do
tmp_cask "signing-cask-test", <<~RUBY
cask 'signing-cask-test' do
version '1.0'
url "https://brew.sh/"
pkg 'Audit.app'
end
RUBY
end
it "does not fail since no extract" do
allow(download_double).to receive(:fetch).and_return(Pathname.new("/tmp/test.zip"))
allow(UnpackStrategy).to receive(:detect).and_return(nil)
expect(run).not_to warn_with(/Audit\.app/)
end
end
end
describe "livecheck should be skipped" do describe "livecheck should be skipped" do
let(:online) { true } let(:online) { true }
let(:message) { /Version '[^']*' differs from '[^']*' retrieved by livecheck\./ } let(:message) { /Version '[^']*' differs from '[^']*' retrieved by livecheck\./ }
@ -888,7 +935,7 @@ describe Cask::Audit, :cask do
end end
describe "audit of downloads" do describe "audit of downloads" do
let(:cask_token) { "with-binary" } let(:cask_token) { "basic-cask" }
let(:cask) { Cask::CaskLoader.load(cask_token) } let(:cask) { Cask::CaskLoader.load(cask_token) }
let(:download_double) { instance_double(Cask::Download) } let(:download_double) { instance_double(Cask::Download) }
let(:message) { "Download Failed" } let(:message) { "Download Failed" }
@ -896,10 +943,11 @@ describe Cask::Audit, :cask do
before do before do
allow(audit).to receive(:download).and_return(download_double) allow(audit).to receive(:download).and_return(download_double)
allow(audit).to receive(:check_https_availability) allow(audit).to receive(:check_https_availability)
allow(UnpackStrategy).to receive(:detect).and_return(nil)
end end
it "when download and verification succeed it does not fail" do it "when download and verification succeed it does not fail" do
expect(download_double).to receive(:fetch) expect(download_double).to receive(:fetch).and_return(Pathname.new("/tmp/test.zip"))
expect(run).to pass expect(run).to pass
end end