Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
68 changes: 63 additions & 5 deletions lib/mix/tasks/create_keychain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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([
Expand All @@ -54,8 +114,6 @@ defmodule Mix.Tasks.Desktop.CreateKeychain do
pass,
name
])

IO.puts(full_path)
end

defp security(args) do
Expand Down
20 changes: 12 additions & 8 deletions lib/package/linux.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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)
Expand Down
101 changes: 89 additions & 12 deletions lib/package/macos.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Loading