Skip to content

Dropping hackney dependency #123

@wojtekmach

Description

@wojtekmach

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 :)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions