mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00
api: use signed endpoint
This commit is contained in:
parent
bfff981f1a
commit
b7d6d00c34
@ -56,7 +56,7 @@ module Homebrew
|
||||
(Homebrew::EnvConfig.no_auto_update? ||
|
||||
((Time.now - Homebrew::EnvConfig.api_auto_update_secs.to_i) < target.mtime))
|
||||
|
||||
begin
|
||||
json_data = begin
|
||||
begin
|
||||
args = curl_args.dup
|
||||
args.prepend("--time-cond", target.to_s) if target.exist? && !target.empty?
|
||||
@ -83,7 +83,7 @@ module Homebrew
|
||||
end
|
||||
|
||||
FileUtils.touch(target) unless skip_download
|
||||
[JSON.parse(target.read), !skip_download]
|
||||
JSON.parse(target.read)
|
||||
rescue JSON::ParserError
|
||||
target.unlink
|
||||
retry_count += 1
|
||||
@ -92,6 +92,21 @@ module Homebrew
|
||||
|
||||
retry
|
||||
end
|
||||
|
||||
if endpoint.end_with?(".jws.json")
|
||||
success, data = verify_and_parse_jws(json_data)
|
||||
unless success
|
||||
target.unlink
|
||||
odie <<~EOS
|
||||
Failed to verify integrity (#{data}) of:
|
||||
#{url}
|
||||
Potential MITM attempt detected. Please run `brew update` and try again.
|
||||
EOS
|
||||
end
|
||||
[data, !skip_download]
|
||||
else
|
||||
[json_data, !skip_download]
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(name: String, git_head: T.nilable(String)).returns(String) }
|
||||
@ -140,5 +155,31 @@ module Homebrew
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
sig { params(json_data: Hash).returns([T::Boolean, T.any(String, Array, Hash)]) }
|
||||
private_class_method def self.verify_and_parse_jws(json_data)
|
||||
signatures = json_data["signatures"]
|
||||
homebrew_signature = signatures&.find { |sig| sig.dig("header", "kid") == "homebrew-1" }
|
||||
return false, "key not found" if homebrew_signature.nil?
|
||||
|
||||
header = JSON.parse(Base64.urlsafe_decode64(homebrew_signature["protected"]))
|
||||
if header["alg"] != "PS512" || header["b64"] != false # NOTE: nil has a meaning of true
|
||||
return false, "invalid algorithm"
|
||||
end
|
||||
|
||||
require "openssl"
|
||||
|
||||
pubkey = OpenSSL::PKey::RSA.new((HOMEBREW_LIBRARY_PATH/"api/homebrew-1.pem").read)
|
||||
signing_input = "#{homebrew_signature["protected"]}.#{json_data["payload"]}"
|
||||
unless pubkey.verify_pss("SHA512",
|
||||
Base64.urlsafe_decode64(homebrew_signature["signature"]),
|
||||
signing_input,
|
||||
salt_length: :digest,
|
||||
mgf1_hash: "SHA512")
|
||||
return false, "signature mismatch"
|
||||
end
|
||||
|
||||
[true, JSON.parse(json_data["payload"])]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -27,8 +27,8 @@ module Homebrew
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def download_and_cache_data!
|
||||
json_casks, updated = Homebrew::API.fetch_json_api_file "cask.json",
|
||||
target: HOMEBREW_CACHE_API/"cask.json"
|
||||
json_casks, updated = Homebrew::API.fetch_json_api_file "cask.jws.json",
|
||||
target: HOMEBREW_CACHE_API/"cask.jws.json"
|
||||
|
||||
cache["casks"] = json_casks.to_h do |json_cask|
|
||||
[json_cask["token"], json_cask.except("token")]
|
||||
|
@ -22,8 +22,8 @@ module Homebrew
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def download_and_cache_data!
|
||||
json_formulae, updated = Homebrew::API.fetch_json_api_file "formula.json",
|
||||
target: HOMEBREW_CACHE_API/"formula.json"
|
||||
json_formulae, updated = Homebrew::API.fetch_json_api_file "formula.jws.json",
|
||||
target: HOMEBREW_CACHE_API/"formula.jws.json"
|
||||
|
||||
cache["aliases"] = {}
|
||||
cache["formulae"] = json_formulae.to_h do |json_formula|
|
||||
|
14
Library/Homebrew/api/homebrew-1.pem
Normal file
14
Library/Homebrew/api/homebrew-1.pem
Normal file
@ -0,0 +1,14 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyKoOYzp1rhwXISRi61BY
|
||||
XBEr2PalSK8lEVOL2USy7mpy0OubOlFyujawyQcBcCn+uPOJ/WaK+POhNWcLLoiK
|
||||
L2m8GViaQm7SMwdLKUXFgKSPHcG/1m6Vu+TNBKTfQqT60PjEYIrn5NW9ZrM0cUhK
|
||||
REmsbeAMBevdSaW9UwY9iIhprrgovvT8SzKhF8ZOIZKXfJX4VNk0y/7VJYNuGGqH
|
||||
3npxV7OKd4yTGRGqFcC9kJ84me3thiu0yqlOjASmfWIwIwcfp4j6BEM2LuqKd7yX
|
||||
h51/O+MTthkuxV36moDKfdgdOFsvlCFkziaYLScCX9lOlmZHtOfJTAOXxTmM7qGr
|
||||
wTGK0vhvTi8k9dBmH/dccredQBtPOfM/FEdeyakGLoTcDguiBS/4El3I2KtF6B2h
|
||||
OGoBumR915/cI4drr5yPMduZ7gjs7ZEZnVkeVzic24TfUHpnOYzrhucNJtHMBDj9
|
||||
6d1Gk82AhtuF9KlusLmCb6qXCWQSp/A4RZpN37E/p9q8rLp/7B/zp8X2TVvecPNy
|
||||
BdMagdktdEqK7WPlYMcUp56JaOph8vqYoU+oGyCpWoLvcXFb75o4eefuu6Rs5SyM
|
||||
c9JCCJ0DDFPjCRFnGPkvsKxFCzMFqH1jpWH0RQIrgmNVM5PO84iRH9YJsSPQzpMj
|
||||
KvK/ZH4YgR9wNkBNagFo7lsCAwEAAQ==
|
||||
-----END PUBLIC KEY-----
|
@ -766,28 +766,28 @@ EOS
|
||||
|
||||
for formula_or_cask in formula cask
|
||||
do
|
||||
if [[ -f "${HOMEBREW_CACHE}/api/${formula_or_cask}.json" ]]
|
||||
if [[ -f "${HOMEBREW_CACHE}/api/${formula_or_cask}.jws.json" ]]
|
||||
then
|
||||
INITIAL_JSON_BYTESIZE="$(wc -c "${HOMEBREW_CACHE}"/api/"${formula_or_cask}".json)"
|
||||
INITIAL_JSON_BYTESIZE="$(wc -c "${HOMEBREW_CACHE}"/api/"${formula_or_cask}".jws.json)"
|
||||
fi
|
||||
JSON_URLS=()
|
||||
if [[ -n "${HOMEBREW_API_DOMAIN}" && "${HOMEBREW_API_DOMAIN}" != "${HOMEBREW_API_DEFAULT_DOMAIN}" ]]
|
||||
then
|
||||
JSON_URLS=("${HOMEBREW_API_DOMAIN}/${formula_or_cask}.json")
|
||||
JSON_URLS=("${HOMEBREW_API_DOMAIN}/${formula_or_cask}.jws.json")
|
||||
fi
|
||||
JSON_URLS+=("${HOMEBREW_API_DEFAULT_DOMAIN}/${formula_or_cask}.json")
|
||||
JSON_URLS+=("${HOMEBREW_API_DEFAULT_DOMAIN}/${formula_or_cask}.jws.json")
|
||||
for json_url in "${JSON_URLS[@]}"
|
||||
do
|
||||
time_cond=()
|
||||
if [[ -s "${HOMEBREW_CACHE}/api/${formula_or_cask}.json" ]]
|
||||
if [[ -s "${HOMEBREW_CACHE}/api/${formula_or_cask}.jws.json" ]]
|
||||
then
|
||||
time_cond=("--time-cond" "${HOMEBREW_CACHE}/api/${formula_or_cask}.json")
|
||||
time_cond=("--time-cond" "${HOMEBREW_CACHE}/api/${formula_or_cask}.jws.json")
|
||||
fi
|
||||
curl \
|
||||
"${CURL_DISABLE_CURLRC_ARGS[@]}" \
|
||||
--fail --compressed --silent \
|
||||
--speed-limit "${HOMEBREW_CURL_SPEED_LIMIT}" --speed-time "${HOMEBREW_CURL_SPEED_TIME}" \
|
||||
--location --remote-time --output "${HOMEBREW_CACHE}/api/${formula_or_cask}.json" \
|
||||
--location --remote-time --output "${HOMEBREW_CACHE}/api/${formula_or_cask}.jws.json" \
|
||||
"${time_cond[@]}" \
|
||||
--user-agent "${HOMEBREW_USER_AGENT_CURL}" \
|
||||
"${json_url}"
|
||||
@ -796,8 +796,8 @@ EOS
|
||||
done
|
||||
if [[ ${curl_exit_code} -eq 0 ]]
|
||||
then
|
||||
touch "${HOMEBREW_CACHE}/api/${formula_or_cask}.json"
|
||||
CURRENT_JSON_BYTESIZE="$(wc -c "${HOMEBREW_CACHE}"/api/"${formula_or_cask}".json)"
|
||||
touch "${HOMEBREW_CACHE}/api/${formula_or_cask}.jws.json"
|
||||
CURRENT_JSON_BYTESIZE="$(wc -c "${HOMEBREW_CACHE}"/api/"${formula_or_cask}".jws.json)"
|
||||
if [[ "${INITIAL_JSON_BYTESIZE}" != "${CURRENT_JSON_BYTESIZE}" ]]
|
||||
then
|
||||
rm -f "${HOMEBREW_CACHE}/api/${formula_or_cask}_names.txt"
|
||||
|
@ -9,12 +9,16 @@ describe Homebrew::API::Cask do
|
||||
before do
|
||||
stub_const("Homebrew::API::HOMEBREW_CACHE_API", cache_dir)
|
||||
Homebrew::API.clear_cache
|
||||
described_class.clear_cache
|
||||
end
|
||||
|
||||
def mock_curl_download(stdout:)
|
||||
allow(Utils::Curl).to receive(:curl_download) do |*_args, **kwargs|
|
||||
kwargs[:to].write stdout
|
||||
end
|
||||
allow(Homebrew::API).to receive(:verify_and_parse_jws) do |json_data|
|
||||
[true, json_data]
|
||||
end
|
||||
end
|
||||
|
||||
describe "::all_casks" do
|
||||
|
@ -8,12 +8,16 @@ describe Homebrew::API::Formula do
|
||||
|
||||
before do
|
||||
stub_const("Homebrew::API::HOMEBREW_CACHE_API", cache_dir)
|
||||
described_class.clear_cache
|
||||
end
|
||||
|
||||
def mock_curl_download(stdout:)
|
||||
allow(Utils::Curl).to receive(:curl_download) do |*_args, **kwargs|
|
||||
kwargs[:to].write stdout
|
||||
end
|
||||
allow(Homebrew::API).to receive(:verify_and_parse_jws) do |json_data|
|
||||
[true, json_data]
|
||||
end
|
||||
end
|
||||
|
||||
describe "::all_formulae" do
|
||||
|
Loading…
x
Reference in New Issue
Block a user