diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6298ca3..fcaafa5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,26 +1,34 @@ name: "test mix.create_keychain" on: ["push", "pull_request"] env: + ASDF_VERSION: v0.18.1 OTP_VERSION: 24.3.4.15 ELIXIR_VERSION: 1.14.5 ELIXIR_VARIANT: -otp-24 jobs: macos: - runs-on: macos-11 + runs-on: macos-15 steps: - name: asdf cache uses: actions/cache@v3 id: asdf-cache with: path: /Users/runner/.asdf - key: macos-otp-${{ env.OTP_VERSION }} + key: macos-15-asdf-${{ env.ASDF_VERSION }}-otp-${{ env.OTP_VERSION }} - name: "Installing Erlang" if: steps.asdf-cache.outputs.cache-hit != 'true' run: | - git clone https://github.com/asdf-vm/asdf.git ~/.asdf - . $HOME/.asdf/asdf.sh + mkdir -p ~/.asdf/bin ~/.asdf/shims + case $(uname -m) in + arm64) ASDF_ARCH=darwin-arm64 ;; + x86_64) ASDF_ARCH=darwin-amd64 ;; + *) echo "Unsupported architecture: $(uname -m)"; exit 1 ;; + esac + curl -sSL "https://github.com/asdf-vm/asdf/releases/download/${{ env.ASDF_VERSION }}/asdf-${{ env.ASDF_VERSION }}-${ASDF_ARCH}.tar.gz" | tar -xz -C ~/.asdf/bin + export ASDF_DATA_DIR="$HOME/.asdf" + export PATH="$ASDF_DATA_DIR/shims:$ASDF_DATA_DIR/bin:$PATH" asdf plugin add erlang asdf plugin add elixir echo "erlang ${{ env.OTP_VERSION }}" >> .tool-versions @@ -34,20 +42,20 @@ jobs: uses: actions/cache/save@v3 with: path: /Users/runner/.asdf - key: macos-otp-${{ env.OTP_VERSION }} + key: macos-15-asdf-${{ env.ASDF_VERSION }}-otp-${{ env.OTP_VERSION }} - uses: actions/checkout@v3 - name: "Create keychain" - env: - MACOS_PEM: ${{ secrets.MACOS_PEM }} run: | echo "erlang ${{ env.OTP_VERSION }}" > .tool-versions echo "elixir ${{ env.ELIXIR_VERSION }}${{ env.ELIXIR_VARIANT }}" >> .tool-versions - . $HOME/.asdf/asdf.sh + export ASDF_DATA_DIR="$HOME/.asdf" + export PATH="$ASDF_DATA_DIR/shims:$ASDF_DATA_DIR/bin:$PATH" mix local.hex --force mix local.rebar --force mix deps.get mix lint - mix desktop.create_keychain + mix desktop.create_keychain ci export MACOS_KEYCHAIN=$HOME/Library/Keychains/macos-build.keychain + export DEVELOPER_ID=- mix test test/codesign_test.exs diff --git a/lib/mix/tasks/create_keychain.ex b/lib/mix/tasks/create_keychain.ex index af39dba..b591ebb 100644 --- a/lib/mix/tasks/create_keychain.ex +++ b/lib/mix/tasks/create_keychain.ex @@ -14,14 +14,35 @@ defmodule Mix.Tasks.Desktop.CreateKeychain do end end + def run(["ci"]) do + name = "macos-build.keychain" + pass = "actions" + base = Mix.Project.deps_paths()[:desktop_deployment] || "" + mac_tools = Path.join(base, "rel/macosx") + full_path = Path.join([System.get_env("HOME"), "Library/Keychains", name]) + + prepare_keychain(name, pass, full_path, mac_tools) + System.put_env("DEVELOPER_ID", "-") + IO.puts(full_path) + end + def run(_args) do name = "macos-build.keychain" pass = "actions" base = Mix.Project.deps_paths()[:desktop_deployment] || "" mac_tools = Path.join(base, "rel/macosx") full_path = Path.join([System.get_env("HOME"), "Library/Keychains", name]) - pem = System.get_env("MACOS_PEM") || raise "No MACOS_PEM env var" + prepare_keychain(name, pass, full_path, mac_tools) + + uid = import_signing_identity(pass) + if is_binary(uid), do: System.put_env("DEVELOPER_ID", uid) + + allow_codesign_access(name, pass) + IO.puts(full_path) + end + + defp prepare_keychain(name, pass, full_path, mac_tools) do if File.exists?(full_path) or File.exists?(full_path <> "-db") do security(["delete-keychain", name]) end @@ -30,19 +51,58 @@ defmodule Mix.Tasks.Desktop.CreateKeychain do System.put_env("MACOS_KEYCHAIN", full_path) security(["list-keychains", "-s", name]) - # security(["default-keychain", "-s", name]) security(["unlock-keychain", "-p", pass, name]) security(["set-keychain-settings", "-t", "3600", "-u", name]) for cert <- Desktop.Deployment.Tooling.wildcard(mac_tools, "*.pem") do security(["import", cert, "-k", name, "-A"]) end + end + + defp import_signing_identity(pass) do + p12 = System.get_env("MACOS_P12") + pem = System.get_env("MACOS_PEM") + + cond do + is_binary(p12) and byte_size(p12) > 0 -> + import_p12_identity(p12, pass) + + is_binary(pem) and byte_size(pem) > 0 -> + import_pem_identity(pem, pass) + + true -> + raise "No MACOS_P12 or MACOS_PEM env var" + end + end + defp import_p12_identity(p12, pass) do + p12_password = System.get_env("MACOS_PEM_PASSWORD") || pass + cert_file = "tmp.cert.pem" + + {_, 0} = + System.cmd("openssl", [ + "pkcs12", + "-in", + p12, + "-passin", + "pass:#{p12_password}", + "-nokeys", + "-out", + cert_file + ]) + + uids = locate_uid(cert_file) || raise "Could not locate UID in PKCS12" + maybe_import_p12(p12, p12_password, uids, pass) + end + + defp import_pem_identity(pem, pass) do file = "tmp.pem" File.write!(file, pem) uids = locate_uid(file) || raise "Could not locate UID in PEM" - maybe_import_pem(file, uids) + maybe_import_pem(file, uids, pass) + end + defp allow_codesign_access(name, pass) do # https://stackoverflow.com/questions/39868578/security-codesign-in-sierra-keychain-ignores-access-control-settings-and-ui-p # https://github.com/lando/code-sign-action/blob/main/action.yml security([ @@ -54,8 +114,6 @@ defmodule Mix.Tasks.Desktop.CreateKeychain do pass, name ]) - - IO.puts(full_path) end defp security(args) do diff --git a/lib/package/linux.ex b/lib/package/linux.ex index a4681dc..ad05ede 100644 --- a/lib/package/linux.ex +++ b/lib/package/linux.ex @@ -80,14 +80,7 @@ defmodule Desktop.Deployment.Package.Linux do files = wildcard(Path.dirname(libwebkit), "#{basename}/*") for file <- files do - if File.dir?(file) do - File.mkdir_p!(Path.join([priv(pkg), "libwebkit2gtk", Path.basename(file)])) - - for subfile <- wildcard(file, "*"), - do: priv_import!(pkg, subfile, extra_path: ["libwebkit2gtk/#{Path.basename(file)}"]) - else - priv_import!(pkg, file, extra_path: ["libwebkit2gtk"]) - end + import_webkit_entry(pkg, file) end redirection = @@ -99,6 +92,17 @@ defmodule Desktop.Deployment.Package.Linux do end end + defp import_webkit_entry(%Package{} = pkg, file) do + if File.dir?(file) do + File.mkdir_p!(Path.join([priv(pkg), "libwebkit2gtk", Path.basename(file)])) + + for subfile <- wildcard(file, "*"), + do: priv_import!(pkg, subfile, extra_path: ["libwebkit2gtk/#{Path.basename(file)}"]) + else + priv_import!(pkg, file, extra_path: ["libwebkit2gtk"]) + end + end + defp import_libgstreamer_modules(%Package{} = pkg, deps) do libgst = Enum.find(deps, fn lib -> String.starts_with?(Path.basename(lib), "libgstreamer") end) diff --git a/lib/package/macos.ex b/lib/package/macos.ex index 6dcd587..e1f56d1 100644 --- a/lib/package/macos.ex +++ b/lib/package/macos.ex @@ -93,11 +93,7 @@ defmodule Desktop.Deployment.Package.MacOS do end for bin <- find_binaries(root) do - rewrite_deps(bin, fn dep -> - if should_rewrite?(bin, dep) do - rewrite_to_approot(pkg, bin, dep, root) - end - end) + rewrite_deps(bin, &maybe_rewrite_dep_to_approot(pkg, bin, &1, root)) end developer_id = find_developer_id() @@ -336,6 +332,10 @@ defmodule Desktop.Deployment.Package.MacOS do )) end + defp maybe_rewrite_dep_to_approot(pkg, bin, dep, root) do + if should_rewrite?(bin, dep), do: rewrite_to_approot(pkg, bin, dep, root) + end + defp rewrite_to_approot(pkg, bin, dep, root) do location = if String.contains?(dep, ".framework/") do @@ -416,9 +416,9 @@ defmodule Desktop.Deployment.Package.MacOS do @friendly_attribute {2, 5, 4, 3} def locate_uid(pem_filename) do cert = File.read!(pem_filename) - # Test for missing public_key application + # 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 - Mix.ensure_application!(:public_key) + Application.ensure_all_started(:public_key) cert_der = List.keyfind!(:public_key.pem_decode(cert), :Certificate, 0) :public_key.der_decode(:Certificate, elem(cert_der, 1)) @@ -460,19 +460,96 @@ defmodule Desktop.Deployment.Package.MacOS do defp do_find_developer_id(uids) do ids = find_identity() - Enum.find(uids, fn uid -> String.contains?(ids, uid) end) + + Enum.find(normalize_uids(uids), fn uid -> String.contains?(ids, uid) end) + end + + defp normalize_uids(uids) do + uids + |> List.wrap() + |> Enum.map(&uid_to_string/1) + |> Enum.uniq() + end + + defp uid_to_string(uid) when is_binary(uid) do + case uid do + <<0x13, len, rest::binary>> when len <= byte_size(rest) -> + binary_part(rest, 0, len) + + _ -> + uid + end + end + + defp uid_to_string(uid) when is_list(uid), do: List.to_string(uid) + defp uid_to_string(uid), do: to_string(uid) + + def maybe_import_p12(file, password, uids, keychain_password \\ nil) do + keychain_password = keychain_password || System.get_env("MACOS_KEYCHAIN_PASSWORD") + normalized_uids = normalize_uids(uids) + + with nil <- do_find_developer_id(normalized_uids) do + cmd("security", [ + "import", + file, + "-k", + keychain(), + "-P", + password, + "-A", + "-T", + "/usr/bin/codesign", + "-f", + "pkcs12" + ]) + + maybe_set_key_partition_list(keychain_password) + + with nil <- do_find_developer_id(normalized_uids) do + ids = find_identity() + + raise """ + Failed to import PKCS12 for uid #{inspect(normalized_uids)}. + Available identities: + #{ids} + """ + end + end end - def maybe_import_pem(file, uids) do + def maybe_import_pem(file, uids, keychain_password \\ nil) do + keychain_password = keychain_password || System.get_env("MACOS_KEYCHAIN_PASSWORD") + with nil <- do_find_developer_id(uids) do - cmd("security", ["import", file, "-k", keychain(), "-A"]) + cmd("security", ["import", file, "-k", keychain(), "-A", "-T", "/usr/bin/codesign"]) + maybe_set_key_partition_list(keychain_password) with nil <- do_find_developer_id(uids) do - raise "Failed to import PEM for uid #{inspect(uids)}" + ids = find_identity() + + raise """ + Failed to import PEM for uid #{inspect(uids)}. + Available code signing identities: + #{ids} + """ end end end + defp maybe_set_key_partition_list(nil), do: :ok + + defp maybe_set_key_partition_list(password) do + cmd("security", [ + "set-key-partition-list", + "-S", + "apple-tool:,apple:,codesign:", + "-s", + "-k", + password, + keychain() + ]) + end + defp find_identity() do cmd("security", ["find-identity", "-v", keychain()]) end @@ -518,7 +595,7 @@ defmodule Desktop.Deployment.Package.MacOS do end defp scan({:AttributeTypeAndValue, @uid_attribute, uid}) do - [String.trim(uid)] + [uid_to_string(uid) |> String.trim()] end defp scan([head | tail]), do: scan(head) ++ scan(tail)