Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix keychain add-certificates on macOS 15 #428

Merged
merged 3 commits into from
Sep 20, 2024
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Version 0.53.8
-------------

**Bugfixes**
- Fix action `keychain add-certificates` on macOS 15.0. [PR #428](https://github.com/codemagic-ci-cd/cli-tools/pull/428)

Version 0.53.7
-------------

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "codemagic-cli-tools"
version = "0.53.7"
version = "0.53.8"
description = "CLI tools used in Codemagic builds"
readme = "README.md"
authors = [
Expand Down
2 changes: 1 addition & 1 deletion src/codemagic/__version__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__title__ = "codemagic-cli-tools"
__description__ = "CLI tools used in Codemagic builds"
__version__ = "0.53.7.dev"
__version__ = "0.53.8.dev"
__url__ = "https://github.com/codemagic-ci-cd/cli-tools"
__licence__ = "GNU General Public License v3.0"
66 changes: 52 additions & 14 deletions src/codemagic/tools/keychain.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import shutil
from datetime import datetime
from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING
from typing import Iterable
from typing import List
from typing import Optional
Expand All @@ -18,6 +19,9 @@
from codemagic.mixins import PathFinderMixin
from codemagic.models import Certificate

if TYPE_CHECKING:
from typing_extensions import Literal


class Seconds(int):
pass
Expand All @@ -33,6 +37,10 @@ class KeychainError(cli.CliAppException):
pass


class _CertificateDataDecodeError(IOError):
pass


class KeychainArgument(cli.Argument):
PATH = cli.ArgumentProperties(
flags=("-p", "--path"),
Expand Down Expand Up @@ -375,6 +383,34 @@ def _add_certificate(
allowed_applications: Sequence[str] = tuple(),
):
self.logger.info(f"Add certificate {certificate_path} to keychain {self.path}")

try:
self._run_add_certificate_process(
certificate_path=certificate_path,
certificate_password=certificate_password,
allow_for_all_apps=allow_for_all_apps,
allowed_applications=allowed_applications,
import_format="pkcs12",
)
except _CertificateDataDecodeError:
# Attempt import again, but now using different format specifier.
self._run_add_certificate_process(
certificate_path=certificate_path,
certificate_password=certificate_password,
allow_for_all_apps=allow_for_all_apps,
allowed_applications=allowed_applications,
import_format="openssl",
)

def _run_add_certificate_process(
self,
*,
certificate_path: pathlib.Path,
certificate_password: Optional[str] = None,
allow_for_all_apps: bool = False,
allowed_applications: Sequence[str] = tuple(),
import_format: Literal["pkcs12", "openssl"] = "pkcs12",
):
# If case of no password, we need to explicitly set -P '' flag. Otherwise,
# security tries to open an interactive dialog to prompt the user for a password,
# which fails in non-interactive CI environment.
Expand All @@ -385,15 +421,10 @@ def _add_certificate(
obfuscate_patterns = []

import_cmd = [
"security",
"import",
certificate_path,
"-f",
"pkcs12",
"-k",
self.path,
"-P",
certificate_password,
*("security", "import", certificate_path),
*("-f", import_format),
*("-k", self.path),
*("-P", certificate_password),
]
if allow_for_all_apps:
import_cmd.append("-A")
Expand All @@ -402,11 +433,18 @@ def _add_certificate(

process = self.execute(import_cmd, obfuscate_patterns=obfuscate_patterns)

if process.returncode != 0:
if "The specified item already exists in the keychain" in process.stderr:
pass # It is fine that the certificate is already in keychain
else:
raise KeychainError(f"Unable to add certificate {certificate_path} to keychain {self.path}", process)
if process.returncode == 0:
return
elif "The specified item already exists in the keychain" in process.stderr:
# It is fine that the certificate is already in keychain
pass
elif import_format == "pkcs12" and "Unable to decode the provided data" in process.stderr:
# MacOS has not been very compliant with unencrypted PEM-formatted PKCS#12
# containers generated by OpenSSL. But starting from macOS 15.0 security
# just rejects them with error message "Unable to decode the provided data".
raise _CertificateDataDecodeError()
else:
raise KeychainError(f"Unable to add certificate {certificate_path} to keychain {self.path}", process)

def _find_certificates(self):
process = self.execute(("security", "find-certificate", "-a", "-p", self.path), show_output=False)
Expand Down
Loading