-
Notifications
You must be signed in to change notification settings - Fork 78
Description
We need Hackney to make sure that tzdata we're downloading and unpacking has not been tampered with. However, hackney brings many transient dependencies and for that reason I believe it is not the best choice, it would be great to depend on as least deps as possible.
I think we have two options:
Option 1: httpc + castore. EEF Security WG has a nice guide how to give httpc secure settings. I think this will be pretty simple to implement and already be a big improvement.
Option 2: httpc + manually checking tzdata signature.
This would be similar to how mix local.hex works, it fetches https://repo.hex.pm/installs/hex-1.x.csv and https://repo.hex.pm/installs/hex-1.x.csv.signed and checks if it wasn't tampered with against Hex public key that is included in Elixir.
Turns out tzdata releases are signed with GPG using Paul Eggert's public key. If we include the key in tzdata and verify against that, we wouldn't even need castore which I think would be a small win.
I have a proof of concept:
defmodule Main do
def main do
url = "https://data.iana.org/time-zones/releases/tzdata2021a.tar.gz"
tzdata = curl(url)
tzdata_asc = curl(url <> ".asc")
{public_key_string, 0} = System.cmd("gpg", ~w(--export -a eggert@cs.ucla.edu))
# https://erlang.org/pipermail/erlang-questions/2021-July/101220.html
[public_key] =
:public_key.pem_decode(
IO.iodata_to_binary([
"-----BEGIN RSA PUBLIC KEY-----\n",
public_key_string |> String.split("\n", trim: true) |> Enum.drop(1) |> Enum.drop(-2),
"\n-----END RSA PUBLIC KEY-----"
])
)
IO.inspect(
[
tzdata: tzdata,
tzdata_asc: tzdata_asc,
public_key: public_key_string,
public_key: public_key
],
printable_limit: 100
)
end
defp curl(url) do
{out, 0} = System.cmd("curl", ["--fail", "--silent", url])
out
end
end
Main.main()[
tzdata: <<31, 139, 8, 0, 0, 0, 0, 0, 2, 3, 236, 125, 91, 119, 35, 71, 146,
158, 94, 89, 191, 34, 23, 173, 53, 200, 49, 10, 4, 192, 59, 91, 212, 12, 72,
130, 108, 170, 121, 51, 1, 170, 165, 30, 141, 225, 2, 144, 0, ...>>,
tzdata_asc: "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEfjeSqdis99YzvBWI7ZfpDmKqfjQFAmANxdIACgkQ7ZfpDmKq\nfjTn" <> ...,
public_key: "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBEyAcmQBEADAAyH2xoTu7ppG5D3a8FMZEon74dCvc4+q1XA2J2tBy2pwaT" <> ...,
public_key: {:RSAPublicKey,
<<153, 2, 13, 4, 76, 128, 114, 100, 1, 16, 0, 192, 3, 33, 246, 198, 132, 238,
238, 154, 70, 228, 61, 218, 240, 83, 25, 18, 137, 251, 225, 208, 175, 115,
143, 170, 213, 112, 54, 39, 107, 65, 203, 106, ...>>, :not_encrypted}
]the next step is to use :public_key.verify/4 but not sure how to transform the arguments appropriately so it accepts them.
Maybe @voltone could help with that :)