cat-bookmarker/deps/esbuild/lib/esbuild/npm_registry.ex

151 lines
3.9 KiB
Elixir

defmodule Esbuild.NpmRegistry do
@moduledoc false
require Logger
# source: https://registry.npmjs.org/-/npm/v1/keys
@public_key_pem """
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i
6UPp+IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg==
-----END PUBLIC KEY-----
"""
@base_url "https://registry.npmjs.org"
@public_key_id "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"
def fetch_package!(name, version) do
url = "#{@base_url}/#{name}/#{version}"
scheme = URI.parse(url).scheme
Logger.debug("Downloading esbuild from #{url}")
{:ok, _} = Application.ensure_all_started(:inets)
{:ok, _} = Application.ensure_all_started(:ssl)
if proxy = proxy_for_scheme(scheme) do
%{host: host, port: port} = URI.parse(proxy)
Logger.debug("Using #{String.upcase(scheme)}_PROXY: #{proxy}")
set_option = if "https" == scheme, do: :https_proxy, else: :proxy
:httpc.set_options([{set_option, {{String.to_charlist(host), port}, []}}])
end
%{
"_id" => id,
"dist" => %{
"integrity" => integrity,
"signatures" => signatures,
"tarball" => tarball
}
} =
fetch_file!(url)
|> Jason.decode!()
%{"sig" => signature} =
signatures
|> Enum.find(fn %{"keyid" => keyid} -> keyid == @public_key_id end) ||
raise "missing signature"
verify_signature!("#{id}:#{integrity}", signature)
tar = fetch_file!(tarball)
[hash_alg, checksum] =
integrity
|> String.split("-")
verify_integrity!(tar, hash_alg, Base.decode64!(checksum))
tar
end
defp fetch_file!(url) do
case do_fetch(url) do
{:ok, {{_, 200, _}, _headers, body}} ->
body
other ->
raise """
couldn't fetch #{url}: #{inspect(other)}
You may also install the "esbuild" executable manually, \
see the docs: https://hexdocs.pm/esbuild
"""
end
end
defp do_fetch(url) do
scheme = URI.parse(url).scheme
url = String.to_charlist(url)
:httpc.request(
:get,
{url, []},
[
ssl: [
verify: :verify_peer,
# https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/inets
cacertfile: cacertfile() |> String.to_charlist(),
depth: 2,
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
]
]
|> maybe_add_proxy_auth(scheme),
body_format: :binary
)
end
defp proxy_for_scheme("http") do
System.get_env("HTTP_PROXY") || System.get_env("http_proxy")
end
defp proxy_for_scheme("https") do
System.get_env("HTTPS_PROXY") || System.get_env("https_proxy")
end
defp maybe_add_proxy_auth(http_options, scheme) do
case proxy_auth(scheme) do
nil -> http_options
auth -> [{:proxy_auth, auth} | http_options]
end
end
defp proxy_auth(scheme) do
with proxy when is_binary(proxy) <- proxy_for_scheme(scheme),
%{userinfo: userinfo} when is_binary(userinfo) <- URI.parse(proxy),
[username, password] <- String.split(userinfo, ":") do
{String.to_charlist(username), String.to_charlist(password)}
else
_ -> nil
end
end
defp cacertfile() do
Application.get_env(:esbuild, :cacerts_path) || CAStore.file_path()
end
defp verify_signature!(message, signature) do
:public_key.verify(
message,
:sha256,
Base.decode64!(signature),
public_key()
) or raise "invalid signature"
end
defp verify_integrity!(binary, hash_alg, checksum) do
binary_checksum =
hash_alg
|> hash_alg_to_erlang()
|> :crypto.hash(binary)
binary_checksum == checksum or raise "invalid checksum"
end
defp public_key do
[entry] = :public_key.pem_decode(@public_key_pem)
:public_key.pem_entry_decode(entry)
end
defp hash_alg_to_erlang("sha512"), do: :sha512
end