diff --git a/lib/mix/lib/mix/local.ex b/lib/mix/lib/mix/local.ex index a9e760bc7e0..bc611ff7650 100644 --- a/lib/mix/lib/mix/local.ex +++ b/lib/mix/lib/mix/local.ex @@ -76,11 +76,11 @@ defmodule Mix.Local do @doc """ Fetches the given signed CSV files, verify and return the matching - Elixir version with checksum. + Elixir version, artifact version and artifact's checksum. Used to install both Rebar and Hex from S3. """ - def find_matching_elixir_version_from_signed_csv!(name, path) do + def find_matching_versions_from_signed_csv!(name, path) do csv = read_path!(name, path) signature = @@ -96,7 +96,8 @@ defmodule Mix.Local do Mix.raise "Could not install #{name} because Mix could not verify authenticity " <> "of metadata file at #{path}. This may happen because a proxy or some " <> "entity is interfering with the download or because you don't have a " <> - "public key to verify the download" + "public key to verify the download. Add the corresponding public key " <> + "or try again later" end end @@ -125,9 +126,9 @@ defmodule Mix.Local do |> Enum.find_value(entries, &find_version(&1, current_version)) end - defp find_version([_, digest|versions], current_version) do + defp find_version([artifact_version, digest|versions], current_version) do if version = Enum.find(versions, &Version.compare(&1, current_version) != :gt) do - {version, digest} + {version, artifact_version, digest} end end end diff --git a/lib/mix/lib/mix/tasks/local.hex.ex b/lib/mix/lib/mix/tasks/local.hex.ex index c9d82719a3a..ef901ac16af 100644 --- a/lib/mix/lib/mix/tasks/local.hex.ex +++ b/lib/mix/lib/mix/tasks/local.hex.ex @@ -3,7 +3,7 @@ defmodule Mix.Tasks.Local.Hex do @hex_s3 "https://s3.amazonaws.com/s3.hex.pm" @hex_list_url @hex_s3 <> "/installs/hex-1.x.csv" - @hex_archive_url @hex_s3 <> "/installs/[VERSION]/hex.ez" + @hex_archive_url @hex_s3 <> "/installs/[ELIXIR_VERSION]/hex-[HEX_VERSION].ez" @shortdoc "Installs Hex locally" @@ -19,8 +19,14 @@ defmodule Mix.Tasks.Local.Hex do """ @spec run(OptionParser.argv) :: boolean def run(args) do - {version, sha512} = Mix.Local.find_matching_elixir_version_from_signed_csv!("Hex", @hex_list_url) - url = String.replace(@hex_archive_url, "[VERSION]", version) + {elixir_version, hex_version, sha512} = + Mix.Local.find_matching_versions_from_signed_csv!("Hex", @hex_list_url) + + url = + @hex_archive_url + |> String.replace("[ELIXIR_VERSION]", elixir_version) + |> String.replace("[HEX_VERSION]", hex_version) + Mix.Tasks.Archive.Install.run [url, "--sha512", sha512 | args] end end diff --git a/lib/mix/lib/mix/tasks/local.rebar.ex b/lib/mix/lib/mix/tasks/local.rebar.ex index c2b7712e035..7f8495403a0 100644 --- a/lib/mix/lib/mix/tasks/local.rebar.ex +++ b/lib/mix/lib/mix/tasks/local.rebar.ex @@ -3,7 +3,7 @@ defmodule Mix.Tasks.Local.Rebar do @rebar_s3 "https://s3.amazonaws.com/s3.hex.pm" @rebar_list_url @rebar_s3 <> "/installs/rebar-1.x.csv" - @rebar_escript_url @rebar_s3 <> "/installs/[VERSION]/rebar" + @rebar_escript_url @rebar_s3 <> "/installs/[ELIXIR_VERSION]/rebar-[REBAR_VERSION]" @shortdoc "Installs rebar locally" @@ -68,8 +68,14 @@ defmodule Mix.Tasks.Local.Rebar do end defp install_from_s3(opts) do - {version, sha512} = Mix.Local.find_matching_elixir_version_from_signed_csv!("Rebar", @rebar_list_url) - url = String.replace(@rebar_escript_url, "[VERSION]", version) + {elixir_version, rebar_version, sha512} = + Mix.Local.find_matching_versions_from_signed_csv!("Rebar", @rebar_list_url) + + url = + @rebar_escript_url + |> String.replace("[ELIXIR_VERSION]", elixir_version) + |> String.replace("[REBAR_VERSION]", rebar_version) + install_from_path(url, Keyword.put(opts, :sha512, sha512)) end end diff --git a/lib/mix/test/mix/local_test.exs b/lib/mix/test/mix/local_test.exs new file mode 100644 index 00000000000..4d120d0faee --- /dev/null +++ b/lib/mix/test/mix/local_test.exs @@ -0,0 +1,97 @@ +Code.require_file "../test_helper.exs", __DIR__ + +defmodule Mix.LocalTest do + use MixTest.Case, async: true + + # openssl rsa -in elixirest.pem -pubout > elixirest.pub + @public_key """ + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA37moKP1dGGLhsP3d8Fwv + W25SoYZUY2K+Iq7A0OBV36Rnb8yW3BWjfh5YtmPvUCfYUbNCW2HTMMgBntkQ4YmN + B9tHVZazl2uX9lGCfZZPFc/9umvKRojCPkMN81MfTxqnY0oaLHr6DB86RsWHB+ld + 782Xf+nd9q3LFdUl8SGlKX7uzfVWd4EWYNcL7aLeLSupZWeNg8uVmY3zua0EgIlQ + XryalIOZb/R+pwprWZoftCl+20FGYi/mJpo/idFtXsR0sJKF4X0W3NORT9RIRbs9 + WdjiFi+eIP7Nm8KSF4pbaXCqSmVf9cgvUuGTxc9/P5GcIPAlkcsSrE5peLyUCk5f + 2QIDAQAB + -----END PUBLIC KEY----- + """ + + # openssl genrsa -aes256 -out elixirtest.pem -passout stdin 2048 + @private_key """ + -----BEGIN RSA PRIVATE KEY----- + Proc-Type: 4,ENCRYPTED + DEK-Info: AES-256-CBC,48BA5153DA2F120ECE063B33C1204A49 + + 5gp3daNWujH7o9S/dJQEt9TYTRP0pPZtU55PlZrzWt52optr7XHW/ENOm84g5J70 + QCPELp12jfQsNiPwbVWXKy2zD3QlNiAelf65hqLWJTWli7XIXfdP46VXOu67OKf9 + Ziw4HQ+AdBEwFt20wJst77iy17sNlyxp5DhNDonnSizzIowgUAJkoNI5aBUU6D8X + KTSIftZW35Z4SudkazdoHepEfItZTI8mB4rvfn71Q4oOBA1rAuUUmdPWoPBfUHDa + hvIp2T2Q8zZYqm0+SjDxZUYOOreE7fuf5NSLhHHt7+jyWQmtaVxnOWms72G+9xT0 + NGmOEB0WEg1kBsUbYOXXwyCAZhNA6MaKCtgjQczRTK+geS1xNaFc9FDEk3ZjN4Z8 + PxrKQoqo+2aQGVcatZWCom80Dci3bIv7iZNA/y1rjfBn+MeitMOGscP7/CBrJAbI + bh1mvCu0McSnqlN0a+EuCVfJQYFMzjibpRVzKAST0QeaxXd5QxHfcPFPBLOpiVWc + NjHaZsHORyoJbUKGA4rgOiSB63mv7SDRA2mvxWpwV/+6MuwBah6t6CGoEsAr1Hbn + 1ySt5w27bw3QEf2KTiuxDubo8UrF0eYzP5A9MH8vRpSRZHg8T3SBVfPJ/pM16Lnn + 5BaMUdxDFJeet5HUYoke9Zm3udh2BvwGiKhzc9Pbw/EcsCcvChMimRTasqTaRf+S + uIm0Un7o+7kTuvBo2y87j2urCEUzft5QqEynbkR7p3vZnwoLLj+supXh3V8ivW4s + Z6ql+ukRcWd/ode+lbSiYfAJCLc1tCqJ3kTnMnADJBlL0TX7YnwBwWuwwPuZgeAv + F6nnBE1SBQ1WK+bjSVzIqmNFqsZw34wgpnz2heX0q8msF5pzd6EIeA+uz86k8XYh + 4eVZYGXxa4Exodh/MqEpRuN1ytWDXvHULh0gml7xwZC3R50UD8uBNt5RGjXUkjXc + V0atKuvgzVlsB4xbDhVP7EVYHBF02NfNOsvo7kh0Yl1IcT/42UaCGYuU1o9zotPv + 9b3SHz/HOmBVj2uCdR5XZ4EolP5Iv9vqIDt9DsuDpOyO+AFOww0FnJNCQ1Hmfb0T + qBYPv994oSPYLCGR4a8i/xfmmV8KbAIVEgK3AMbz8RxKr3WBWXWnzQdr4+y4EG24 + hSnR52XQ42edv/fkqf9ez+fKNQ9i7PtlPE96Q21NeLMNKHh43X8hJFDh+oPz3Aio + YSNMCZnoyRdrjBRCsVBpnyoLmuhWwG9RlcrEj3G0BxYPh/weaBOAKAHjSr28yuUj + yIa8uddszC6XHSiVUgu7SGO8gQmq++eNdckjX/pEug5MjcWLUqaUg6+YLFWY6NLf + uDPOYuivq7ErtKTvP2xl3TBEDKhdfqxA2+RFxbBDmKjffZnRkcknQsxhlzAdbg22 + Jwa2B1nrfjJpX5F+1Av2jHQGbIKMqZzv8fo1binMKpptFzokbWEOjcPCb3tPuomG + ZRkW3qO2pdyYX2N7VXYG9tGi2HrN/oFrWnHPoYF23v85V8WxNkODOCpTz85e6R5v + PVu+FCNFj5weEOTRhtEQyJo7mU5qIRwYeZvVxiC6W+XeFs95wdBE/Lvpg8yZ8D9d + -----END RSA PRIVATE KEY----- + """ + + @csv """ + 1.2.5,ABC,0.9.0 + 1.2.3,DEF,1.0.0 + 1.2.4,GHI,1.0.0 + """ + + # openssl dgst -sha512 -sign elixirtest.pem hex-1.x.csv | openssl base64 > elixirtest.csv.signed + @csv_signed """ + VRydmXOdEXQcKJu/SK/nKnE00T+s/T4mpXrYROMSXhD/s8ClvdimnGg61ie3YBS6 + LXOjlEhbtMHRM2rTOUvv4z7FcyzwvSxSjunlVi2g3c1pVOZ78MonnYhGb44tZw/q + SOVmV+jJhc9EZFMIAAM3plMoyssyw2pMh7ZB/DxCQTIem3Qf0Ujzc2bYkLVlw7R+ + 1Rn6dcYEgCzyldVkAUMaYBwieyweWALA+YVDCMudJJK2J7p1OnuoPSVV+N3OkB/Z + T6Jj5ljD+54XnuxAMcgCoF9lpOwXscnw/Ma+8JqIoWo0jNFE3ji+8dGCUzQUdSe8 + llLXgJJE2tGpDhEXBA3idg== + """ + + setup_all do + File.mkdir_p!(Mix.PublicKey.public_keys_path) + + Path.join(Mix.PublicKey.public_keys_path, "test_key.pub") + |> File.write!(@public_key) + end + + test "select correct versions from csv" do + in_tmp "select correct versions from csv", fn -> + File.write!("csv", @csv) + File.write!("csv.signed", @csv_signed) + + assert {"1.0.0", "1.2.4", "GHI"} = + Mix.Local.find_matching_versions_from_signed_csv!("name", "csv") + end + end + + test "raise on bad signature" do + in_tmp "raise on bad signature", fn -> + csv_signed = String.replace(@csv_signed, "VRy", "BAD") + File.write!("csv", @csv) + File.write!("csv.signed", csv_signed) + + assert_raise Mix.Error, fn -> + Mix.Local.find_matching_versions_from_signed_csv!("name", "csv") + end + end + end +end