diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fcaafa5..be5a275 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,7 +3,7 @@ on: ["push", "pull_request"] env: ASDF_VERSION: v0.18.1 OTP_VERSION: 24.3.4.15 - ELIXIR_VERSION: 1.14.5 + ELIXIR_VERSION: 1.15.8 ELIXIR_VARIANT: -otp-24 jobs: @@ -15,7 +15,7 @@ jobs: id: asdf-cache with: path: /Users/runner/.asdf - key: macos-15-asdf-${{ env.ASDF_VERSION }}-otp-${{ env.OTP_VERSION }} + key: macos-15-asdf-${{ env.ASDF_VERSION }}-otp-${{ env.OTP_VERSION }}-elixir-${{ env.ELIXIR_VERSION }}${{ env.ELIXIR_VARIANT }} - name: "Installing Erlang" if: steps.asdf-cache.outputs.cache-hit != 'true' @@ -42,7 +42,7 @@ jobs: uses: actions/cache/save@v3 with: path: /Users/runner/.asdf - key: macos-15-asdf-${{ env.ASDF_VERSION }}-otp-${{ env.OTP_VERSION }} + key: macos-15-asdf-${{ env.ASDF_VERSION }}-otp-${{ env.OTP_VERSION }}-elixir-${{ env.ELIXIR_VERSION }}${{ env.ELIXIR_VARIANT }} - uses: actions/checkout@v3 - name: "Create keychain" @@ -58,4 +58,4 @@ jobs: mix desktop.create_keychain ci export MACOS_KEYCHAIN=$HOME/Library/Keychains/macos-build.keychain export DEVELOPER_ID=- - mix test test/codesign_test.exs + mix test test/codesign_test.exs test/locate_uid_test.exs diff --git a/lib/package/macos.ex b/lib/package/macos.ex index e1f56d1..977b8e0 100644 --- a/lib/package/macos.ex +++ b/lib/package/macos.ex @@ -416,15 +416,25 @@ defmodule Desktop.Deployment.Package.MacOS do @friendly_attribute {2, 5, 4, 3} def locate_uid(pem_filename) do cert = File.read!(pem_filename) - # Ensure :public_key is started (see extra_applications in mix.exs) - # ref https://elixirforum.com/t/nerves-key-hub-mix-tasks-fail-because-of-missing-pubkey-pem-module/62821/2 - Application.ensure_all_started(:public_key) + ensure_public_key!() cert_der = List.keyfind!(:public_key.pem_decode(cert), :Certificate, 0) :public_key.der_decode(:Certificate, elem(cert_der, 1)) |> scan() end + # Mix tasks do not automatically put OTP apps on the code path the way a + # started release does. Mix.ensure_application!/1 loads :public_key (and + # crypto/asn1) onto the path; ensure_all_started/1 then starts them. + # Application.ensure_all_started/1 alone is not enough in Mix task context + # and silently returning {:error, _} left pem_decode undefined on CI. + # ref https://elixirforum.com/t/nerves-key-hub-mix-tasks-fail-because-of-missing-pubkey-pem-module/62821/2 + defp ensure_public_key! do + Mix.ensure_application!(:public_key) + {:ok, _} = Application.ensure_all_started(:public_key) + :ok + end + def find_developer_id() do cond do System.get_env("DEVELOPER_ID") != nil -> diff --git a/mix.exs b/mix.exs index 55b59a6..f54288b 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule Desktop.Deployment.MixProject do [ app: :desktop_deployment, version: @version, - elixir: "~> 1.10", + elixir: "~> 1.15", elixirc_paths: elixirc_paths(Mix.env()), # compilers: Mix.compilers(), start_permanent: Mix.env() == :prod, diff --git a/test/locate_uid_test.exs b/test/locate_uid_test.exs new file mode 100644 index 0000000..3812fe2 --- /dev/null +++ b/test/locate_uid_test.exs @@ -0,0 +1,49 @@ +defmodule LocateUidTest do + use ExUnit.Case + + alias Desktop.Deployment.Package.MacOS + + # Regression for: + # UndefinedFunctionError: function :public_key.pem_decode/1 is undefined + # (module :public_key is not available) + # + # That failure happened in Mix task context when only Application.ensure_all_started/1 + # was used (and its error ignored). locate_uid/1 must load :public_key via + # Mix.ensure_application!/1 and then start it before calling pem_decode/1. + test "locate_uid/1 can decode a PEM after ensuring :public_key is available" do + dir = System.tmp_dir!() + key = Path.join(dir, "desktop-deployment-test-key.pem") + cert = Path.join(dir, "desktop-deployment-test-cert.pem") + + try do + {_, 0} = + System.cmd("openssl", [ + "req", + "-x509", + "-newkey", + "rsa:2048", + "-keyout", + key, + "-out", + cert, + "-days", + "1", + "-nodes", + "-subj", + "/CN=Desktop Deployment Test (TESTUID)" + ]) + + # Force ensure_public_key!/0 to re-start :public_key the way a Mix task would + # after the app is not already up. + _ = Application.stop(:public_key) + + # Must not raise UndefinedFunctionError for :public_key.pem_decode/1 + uids = MacOS.locate_uid(cert) + assert is_list(uids) + assert "TESTUID" in uids + after + File.rm(key) + File.rm(cert) + end + end +end