Wire Apple KMS release signing
This commit is contained in:
parent
ed73d7d16a
commit
126af6b5cf
17 changed files with 1913 additions and 16 deletions
53
Scripts/apple/profile-entitlements.py
Executable file
53
Scripts/apple/profile-entitlements.py
Executable file
|
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Extract signing entitlements from an Apple provisioning profile."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import plistlib
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def decode_profile(path: Path) -> dict:
|
||||
commands = [
|
||||
["security", "cms", "-D", "-i", str(path)],
|
||||
["openssl", "smime", "-inform", "DER", "-verify", "-noverify", "-in", str(path)],
|
||||
["openssl", "cms", "-inform", "DER", "-verify", "-noverify", "-in", str(path)],
|
||||
]
|
||||
errors: list[str] = []
|
||||
for command in commands:
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
command,
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
if proc.returncode == 0 and proc.stdout:
|
||||
return plistlib.loads(proc.stdout)
|
||||
errors.append(f"{command[0]} exited {proc.returncode}: {proc.stderr.decode(errors='replace').strip()}")
|
||||
raise RuntimeError(f"unable to decode provisioning profile {path}: {'; '.join(errors)}")
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("profile", type=Path)
|
||||
parser.add_argument("--output", "-o", type=Path, required=True)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
profile = decode_profile(args.profile)
|
||||
entitlements = profile.get("Entitlements")
|
||||
if not isinstance(entitlements, dict):
|
||||
raise RuntimeError(f"profile has no Entitlements dictionary: {args.profile}")
|
||||
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_bytes(plistlib.dumps(entitlements, fmt=plistlib.FMT_XML))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
258
Scripts/apple/sign-release-artifacts-kms.sh
Executable file
258
Scripts/apple/sign-release-artifacts-kms.sh
Executable file
|
|
@ -0,0 +1,258 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$repo_root"
|
||||
|
||||
platform="${1:-all}"
|
||||
build_number="${BUILD_NUMBER:-${RELEASE_BUILD_NUMBER:-${RELEASE_REF:-manual}}}"
|
||||
out_root="${BURROW_RELEASE_OUT:-${repo_root}/dist/builds/${build_number}}"
|
||||
apple_root="${out_root}/apple"
|
||||
bundle_id="${BURROW_APPLE_BUNDLE_ID:-net.burrow.app}"
|
||||
network_extension_bundle_id="${BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID:-${bundle_id}.network}"
|
||||
notarize_macos="${BURROW_NOTARIZE_MACOS:-false}"
|
||||
sparkle_sign_with_kms="${BURROW_SPARKLE_SIGN_WITH_KMS:-true}"
|
||||
|
||||
truthy() {
|
||||
case "${1:-}" in
|
||||
1|true|TRUE|True|yes|YES|Yes|y|Y|on|ON|On) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
sha256_file() {
|
||||
local file="$1"
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$file" > "${file}.sha256"
|
||||
else
|
||||
sha256sum "$file" > "${file}.sha256"
|
||||
fi
|
||||
}
|
||||
|
||||
safe_profile_slug() {
|
||||
python3 - "$1" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
|
||||
print(re.sub(r"[^A-Za-z0-9]", "", sys.argv[1]).lower())
|
||||
PY
|
||||
}
|
||||
|
||||
profile_for() {
|
||||
local identifier="$1"
|
||||
local suffix="$2"
|
||||
local slug path
|
||||
slug="$(safe_profile_slug "$identifier")"
|
||||
for path in \
|
||||
".forgejo/actions/export/${slug}${suffix}.provisionprofile" \
|
||||
".forgejo/actions/export/${slug}${suffix}.mobileprovision" \
|
||||
"Apple/Profiles/${slug}${suffix}.provisionprofile" \
|
||||
"Apple/Profiles/${slug}${suffix}.mobileprovision"; do
|
||||
if [[ -s "$path" ]]; then
|
||||
printf '%s\n' "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
require_tool() {
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
echo "error: missing required tool: $1" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
extract_profile_entitlements() {
|
||||
local profile="$1"
|
||||
local output="$2"
|
||||
Scripts/apple/profile-entitlements.py "$profile" --output "$output"
|
||||
}
|
||||
|
||||
sign_bundle() {
|
||||
local family="$1"
|
||||
local bundle_path="$2"
|
||||
local entitlements="$3"
|
||||
local runtime_flag=()
|
||||
if [[ "$family" == "developer-id" ]]; then
|
||||
runtime_flag=(--runtime)
|
||||
fi
|
||||
Scripts/apple/sign-with-google-kms.sh \
|
||||
--family "$family" \
|
||||
--path "$bundle_path" \
|
||||
--entitlements "$entitlements" \
|
||||
"${runtime_flag[@]}"
|
||||
}
|
||||
|
||||
write_notary_api_key_json() {
|
||||
local out="$1"
|
||||
if [[ -z "${ASC_API_KEY_ID:-}" || -z "${ASC_API_ISSUER_ID:-}" || -z "${ASC_API_KEY_PATH:-}" ]]; then
|
||||
echo "macOS notarization requires ASC_API_KEY_ID, ASC_API_ISSUER_ID, and ASC_API_KEY_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
rcodesign encode-app-store-connect-api-key \
|
||||
"$ASC_API_ISSUER_ID" \
|
||||
"$ASC_API_KEY_ID" \
|
||||
"$ASC_API_KEY_PATH" \
|
||||
"$out"
|
||||
}
|
||||
|
||||
sign_ios() {
|
||||
local unsigned_ipa app_profile ext_profile work_dir app ext app_entitlements ext_entitlements output_ipa
|
||||
unsigned_ipa="${apple_root}/Burrow-iOS-${build_number}.unsigned.ipa"
|
||||
if [[ ! -s "$unsigned_ipa" ]]; then
|
||||
echo "error: missing staged unsigned iOS IPA: $unsigned_ipa" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
app_profile="$(profile_for "$bundle_id" "appstore")" || {
|
||||
echo "error: missing iOS App Store provisioning profile for ${bundle_id}" >&2
|
||||
exit 1
|
||||
}
|
||||
ext_profile="$(profile_for "$network_extension_bundle_id" "appstore")" || {
|
||||
echo "error: missing iOS App Store provisioning profile for ${network_extension_bundle_id}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
work_dir="$(mktemp -d "${TMPDIR:-/tmp}/burrow-ios-kms-sign.XXXXXX")"
|
||||
trap 'rm -rf "${work_dir:-}"' RETURN
|
||||
unzip -q "$unsigned_ipa" -d "$work_dir"
|
||||
app="${work_dir}/Payload/Burrow.app"
|
||||
ext="${app}/PlugIns/BurrowNetworkExtension.appex"
|
||||
if [[ ! -d "$app" || ! -d "$ext" ]]; then
|
||||
echo "error: unsigned IPA is missing Burrow.app or BurrowNetworkExtension.appex" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "$app_profile" "${app}/embedded.mobileprovision"
|
||||
cp "$ext_profile" "${ext}/embedded.mobileprovision"
|
||||
app_entitlements="${work_dir}/app-entitlements.plist"
|
||||
ext_entitlements="${work_dir}/extension-entitlements.plist"
|
||||
extract_profile_entitlements "$app_profile" "$app_entitlements"
|
||||
extract_profile_entitlements "$ext_profile" "$ext_entitlements"
|
||||
|
||||
rm -rf "${app}/_CodeSignature" "${ext}/_CodeSignature"
|
||||
sign_bundle ios "$ext" "$ext_entitlements"
|
||||
sign_bundle ios "$app" "$app_entitlements"
|
||||
|
||||
output_ipa="${apple_root}/Burrow-iOS-${build_number}.ipa"
|
||||
rm -f "$output_ipa"
|
||||
(cd "$work_dir" && zip -qry "$output_ipa" Payload)
|
||||
sha256_file "$output_ipa"
|
||||
}
|
||||
|
||||
write_sparkle_appcast() {
|
||||
local artifact="$1"
|
||||
local channel sparkle_dir length pubdate url
|
||||
channel="${SPARKLE_CHANNEL:-build-${build_number}}"
|
||||
sparkle_dir="${out_root}/sparkle/${channel}"
|
||||
mkdir -p "$sparkle_dir"
|
||||
cp "$artifact" "$sparkle_dir/"
|
||||
length="$(wc -c < "$artifact" | tr -d ' ')"
|
||||
pubdate="$(LC_ALL=C date -u '+%a, %d %b %Y %H:%M:%S +0000')"
|
||||
url="${SPARKLE_DOWNLOAD_PREFIX:-https://releases.burrow.net/sparkle}/${channel}/$(basename "$artifact")"
|
||||
cat > "${sparkle_dir}/appcast.xml" <<EOF
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
|
||||
<channel>
|
||||
<title>Burrow ${channel}</title>
|
||||
<item>
|
||||
<title>Burrow ${build_number}</title>
|
||||
<pubDate>${pubdate}</pubDate>
|
||||
<sparkle:version>${build_number}</sparkle:version>
|
||||
<sparkle:shortVersionString>0.1</sparkle:shortVersionString>
|
||||
<enclosure url="${url}" sparkle:version="${build_number}" length="${length}" type="application/octet-stream" />
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
EOF
|
||||
if truthy "$sparkle_sign_with_kms"; then
|
||||
Scripts/sparkle/sign-appcast-kms.py \
|
||||
--appcast "${sparkle_dir}/appcast.xml" \
|
||||
--artifact-dir "$sparkle_dir"
|
||||
fi
|
||||
}
|
||||
|
||||
sign_macos() {
|
||||
local unsigned_zip app_profile ext_profile work_dir app ext app_entitlements ext_entitlements output_zip notary_zip key_json
|
||||
unsigned_zip="${apple_root}/Burrow-macOS-${build_number}.unsigned.zip"
|
||||
if [[ ! -s "$unsigned_zip" ]]; then
|
||||
echo "error: missing staged unsigned macOS ZIP: $unsigned_zip" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
app_profile="$(profile_for "$bundle_id" "developerid")" || {
|
||||
echo "error: missing macOS Developer ID provisioning profile for ${bundle_id}" >&2
|
||||
exit 1
|
||||
}
|
||||
ext_profile="$(profile_for "$network_extension_bundle_id" "developerid")" || {
|
||||
echo "error: missing macOS Developer ID provisioning profile for ${network_extension_bundle_id}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
work_dir="$(mktemp -d "${TMPDIR:-/tmp}/burrow-macos-kms-sign.XXXXXX")"
|
||||
trap 'rm -rf "${work_dir:-}"' RETURN
|
||||
unzip -q "$unsigned_zip" -d "$work_dir"
|
||||
app="$(find "$work_dir" -maxdepth 2 -type d -name 'Burrow.app' -print | head -n 1)"
|
||||
if [[ -z "$app" || ! -d "$app" ]]; then
|
||||
echo "error: unsigned macOS ZIP does not contain Burrow.app" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ext="${app}/Contents/PlugIns/BurrowNetworkExtension.appex"
|
||||
cp "$app_profile" "${app}/Contents/embedded.provisionprofile"
|
||||
app_entitlements="${work_dir}/app-entitlements.plist"
|
||||
extract_profile_entitlements "$app_profile" "$app_entitlements"
|
||||
|
||||
if [[ -d "$ext" ]]; then
|
||||
cp "$ext_profile" "${ext}/Contents/embedded.provisionprofile"
|
||||
ext_entitlements="${work_dir}/extension-entitlements.plist"
|
||||
extract_profile_entitlements "$ext_profile" "$ext_entitlements"
|
||||
rm -rf "${ext}/Contents/_CodeSignature"
|
||||
sign_bundle developer-id "$ext" "$ext_entitlements"
|
||||
fi
|
||||
|
||||
rm -rf "${app}/Contents/_CodeSignature"
|
||||
sign_bundle developer-id "$app" "$app_entitlements"
|
||||
|
||||
if truthy "$notarize_macos"; then
|
||||
key_json="${work_dir}/notary-api-key.json"
|
||||
notary_zip="${work_dir}/Burrow.notary.zip"
|
||||
write_notary_api_key_json "$key_json"
|
||||
(cd "$(dirname "$app")" && zip -qry "$notary_zip" "$(basename "$app")")
|
||||
rcodesign notary-submit --api-key-file "$key_json" --wait "$notary_zip"
|
||||
rcodesign staple "$app"
|
||||
fi
|
||||
|
||||
output_zip="${apple_root}/Burrow-macOS-${build_number}.zip"
|
||||
rm -f "$output_zip"
|
||||
(cd "$(dirname "$app")" && zip -qry "$output_zip" "$(basename "$app")")
|
||||
sha256_file "$output_zip"
|
||||
write_sparkle_appcast "$output_zip"
|
||||
}
|
||||
|
||||
require_tool python3
|
||||
require_tool unzip
|
||||
require_tool zip
|
||||
require_tool rcodesign
|
||||
require_tool gcloud
|
||||
mkdir -p "$apple_root"
|
||||
|
||||
case "$platform" in
|
||||
all)
|
||||
sign_ios
|
||||
sign_macos
|
||||
;;
|
||||
ios)
|
||||
sign_ios
|
||||
;;
|
||||
macos)
|
||||
sign_macos
|
||||
;;
|
||||
*)
|
||||
echo "unknown Apple KMS signing platform: $platform" >&2
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
|
||||
find "$out_root" -type f -print | sort
|
||||
263
Scripts/apple/sign-with-google-kms.sh
Executable file
263
Scripts/apple/sign-with-google-kms.sh
Executable file
|
|
@ -0,0 +1,263 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$repo_root"
|
||||
|
||||
family=""
|
||||
path=""
|
||||
entitlements=""
|
||||
certificate=""
|
||||
kms_key=""
|
||||
kms_version=""
|
||||
runtime=false
|
||||
timestamp_url=""
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: Scripts/apple/sign-with-google-kms.sh --family ios|developer-id --path <bundle-or-dmg> [options]
|
||||
|
||||
Options:
|
||||
--entitlements <plist> XML entitlements for bundle signing
|
||||
--certificate <cer> Apple public certificate matching the KMS key
|
||||
--kms-key <name> Google KMS crypto key name/resource
|
||||
--kms-version <version> Google KMS crypto key version
|
||||
--runtime Set hardened-runtime code-signature flags
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--family)
|
||||
family="${2:?missing value for --family}"
|
||||
shift 2
|
||||
;;
|
||||
--path)
|
||||
path="${2:?missing value for --path}"
|
||||
shift 2
|
||||
;;
|
||||
--entitlements)
|
||||
entitlements="${2:?missing value for --entitlements}"
|
||||
shift 2
|
||||
;;
|
||||
--certificate)
|
||||
certificate="${2:?missing value for --certificate}"
|
||||
shift 2
|
||||
;;
|
||||
--kms-key)
|
||||
kms_key="${2:?missing value for --kms-key}"
|
||||
shift 2
|
||||
;;
|
||||
--kms-version)
|
||||
kms_version="${2:?missing value for --kms-version}"
|
||||
shift 2
|
||||
;;
|
||||
--runtime)
|
||||
runtime=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "error: unknown argument $1" >&2
|
||||
usage >&2
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "$family" in
|
||||
ios)
|
||||
certificate="${certificate:-Apple/Certificates/ios-distribution-3G42677598.cer}"
|
||||
kms_key="${kms_key:-${BURROW_IOS_DISTRIBUTION_KMS_KEY:-apple-ios-distribution}}"
|
||||
kms_version="${kms_version:-${BURROW_IOS_DISTRIBUTION_KMS_VERSION:-1}}"
|
||||
timestamp_url="${BURROW_IOS_CODESIGN_TIMESTAMP_URL:-none}"
|
||||
;;
|
||||
developer-id)
|
||||
certificate="${certificate:-Apple/Certificates/developer-id-application-9JKN6HXBHC.cer}"
|
||||
kms_key="${kms_key:-${BURROW_DEVELOPER_ID_KMS_KEY:-apple-developer-id-application}}"
|
||||
kms_version="${kms_version:-${BURROW_DEVELOPER_ID_KMS_VERSION:-1}}"
|
||||
timestamp_url="${BURROW_DEVELOPER_ID_TIMESTAMP_URL:-${BURROW_APPLE_TIMESTAMP_URL:-}}"
|
||||
;;
|
||||
*)
|
||||
echo "error: --family must be ios or developer-id" >&2
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -z "$path" ]]; then
|
||||
echo "error: --path is required" >&2
|
||||
exit 64
|
||||
fi
|
||||
if [[ "$path" != /* ]]; then
|
||||
path="$repo_root/$path"
|
||||
fi
|
||||
if [[ "$certificate" != /* ]]; then
|
||||
certificate="$repo_root/$certificate"
|
||||
fi
|
||||
if [[ -n "$entitlements" && "$entitlements" != /* ]]; then
|
||||
entitlements="$repo_root/$entitlements"
|
||||
fi
|
||||
if [[ ! -e "$path" ]]; then
|
||||
echo "error: signing target does not exist: $path" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -s "$certificate" ]]; then
|
||||
echo "error: Apple certificate is missing or empty: $certificate" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "$entitlements" && ! -s "$entitlements" ]]; then
|
||||
echo "error: entitlements file is missing or empty: $entitlements" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
require_tool() {
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
echo "error: missing required tool: $1" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
resolve_module() {
|
||||
local candidate
|
||||
for candidate in \
|
||||
"${BURROW_APPLE_PKCS11_MODULE:-}" \
|
||||
"${BURROW_GCP_KMS_PKCS11_MODULE:-}" \
|
||||
"${BURROW_PKCS11_MODULE:-}" \
|
||||
"$(command -v google-kms-pkcs11-module >/dev/null 2>&1 && google-kms-pkcs11-module || true)" \
|
||||
/nix/store/*-google-kms-pkcs11-*/lib/libkmsp11.so \
|
||||
/nix/store/*-google-kms-pkcs11-*/lib/libkmsp11.dylib; do
|
||||
if [[ -n "$candidate" && -f "$candidate" ]]; then
|
||||
printf '%s\n' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
cert_public_key() {
|
||||
local cert_file="$1"
|
||||
local out="$2"
|
||||
if ! openssl x509 -inform DER -in "$cert_file" -pubkey -noout >"$out" 2>/dev/null; then
|
||||
openssl x509 -in "$cert_file" -pubkey -noout >"$out"
|
||||
fi
|
||||
}
|
||||
|
||||
public_fingerprint() {
|
||||
openssl pkey -pubin -in "$1" -outform DER \
|
||||
| openssl dgst -sha256 -binary \
|
||||
| od -An -tx1 \
|
||||
| tr -d ' \n'
|
||||
}
|
||||
|
||||
resolve_kms_parts() {
|
||||
local resource="$1"
|
||||
local key_ring="${BURROW_GCP_KMS_KEY_RING:-${GOOGLE_KMS_KEY_RING:-projects/${GOOGLE_CLOUD_PROJECT:-project-88c23ce9-918a-470a-b33}/locations/${BURROW_KMS_LOCATION:-global}/keyRings/${BURROW_KMS_KEYRING:-burrow-identity}}}"
|
||||
|
||||
if [[ "$resource" =~ ^projects/([^/]+)/locations/([^/]+)/keyRings/([^/]+)/cryptoKeys/([^/]+)/cryptoKeyVersions/([^/]+)$ ]]; then
|
||||
printf '%s\n%s\n%s\n%s\n%s\n' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" "${BASH_REMATCH[4]}" "${BASH_REMATCH[5]}"
|
||||
return 0
|
||||
fi
|
||||
if [[ "$resource" =~ ^projects/([^/]+)/locations/([^/]+)/keyRings/([^/]+)/cryptoKeys/([^/]+)$ ]]; then
|
||||
printf '%s\n%s\n%s\n%s\n%s\n' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" "${BASH_REMATCH[4]}" "$kms_version"
|
||||
return 0
|
||||
fi
|
||||
if [[ ! "$key_ring" =~ ^projects/([^/]+)/locations/([^/]+)/keyRings/([^/]+)$ ]]; then
|
||||
echo "error: invalid KMS key ring resource: $key_ring" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n%s\n%s\n%s\n%s\n' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" "$resource" "$kms_version"
|
||||
}
|
||||
|
||||
verify_certificate_matches_kms() {
|
||||
local work_dir="$1"
|
||||
local cert_public="$work_dir/apple-public.pem"
|
||||
local kms_public="$work_dir/kms-public.pem"
|
||||
local project location keyring key version expected actual
|
||||
kms_info="$(resolve_kms_parts "$kms_key")"
|
||||
project="$(printf '%s\n' "$kms_info" | sed -n '1p')"
|
||||
location="$(printf '%s\n' "$kms_info" | sed -n '2p')"
|
||||
keyring="$(printf '%s\n' "$kms_info" | sed -n '3p')"
|
||||
key="$(printf '%s\n' "$kms_info" | sed -n '4p')"
|
||||
version="$(printf '%s\n' "$kms_info" | sed -n '5p')"
|
||||
if [[ -z "$version" ]]; then
|
||||
echo "error: KMS key version is required for Apple release signing" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cert_public_key "$certificate" "$cert_public"
|
||||
expected="$(public_fingerprint "$cert_public")"
|
||||
gcloud kms keys versions get-public-key "$version" \
|
||||
--project="$project" \
|
||||
--location="$location" \
|
||||
--keyring="$keyring" \
|
||||
--key="$key" \
|
||||
--output-file="$kms_public" >/dev/null
|
||||
actual="$(public_fingerprint "$kms_public")"
|
||||
if [[ "$actual" != "$expected" ]]; then
|
||||
echo "error: Apple certificate public key does not match Google KMS key" >&2
|
||||
echo "family=$family" >&2
|
||||
echo "kms_key=$key" >&2
|
||||
echo "kms_version=$version" >&2
|
||||
echo "certificate_spki_sha256=$expected" >&2
|
||||
echo "kms_spki_sha256=$actual" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_tool rcodesign
|
||||
require_tool openssl
|
||||
require_tool od
|
||||
require_tool gcloud
|
||||
|
||||
if ! rcodesign sign --help 2>&1 | grep -Fq -- "--pkcs11-library"; then
|
||||
echo "error: rcodesign does not include PKCS#11 signing support" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
module="$(resolve_module || true)"
|
||||
if [[ -z "$module" ]]; then
|
||||
echo "error: unable to locate Google KMS PKCS#11 module" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
work_dir="$(mktemp -d "${TMPDIR:-/tmp}/burrow-apple-kms-sign.XXXXXX")"
|
||||
trap 'rm -rf "$work_dir"' EXIT
|
||||
|
||||
eval "$(Scripts/release/google-kms-pkcs11-materialize.sh --emit-env)"
|
||||
verify_certificate_matches_kms "$work_dir"
|
||||
|
||||
real_openssl="$(command -v openssl)"
|
||||
export BURROW_REAL_OPENSSL="${BURROW_REAL_OPENSSL:-$real_openssl}"
|
||||
export BURROW_OPENSSL="$repo_root/Scripts/release/google-kms-openssl-dgst-shim.sh"
|
||||
export BURROW_APPLE_PKCS11_BACKEND=gcp-kms
|
||||
export BURROW_APPLE_PKCS11_SIGN_WITH_OPENSSL_PROVIDER=true
|
||||
export BURROW_APPLE_PKCS11_SIGN_WITH_GCLOUD=true
|
||||
export BURROW_GCP_KMS_ACTIVE_CRYPTO_KEY="$kms_key"
|
||||
export BURROW_GCP_KMS_ACTIVE_CRYPTO_KEY_VERSION="$kms_version"
|
||||
|
||||
pin="${BURROW_APPLE_PKCS11_PIN:-${BURROW_GCP_KMS_PKCS11_PIN:-${BURROW_PKCS11_PIN:-ignored}}}"
|
||||
token_label="${BURROW_APPLE_PKCS11_TOKEN_LABEL:-${BURROW_GCP_KMS_PKCS11_TOKEN_LABEL:-${BURROW_PKCS11_TOKEN_LABEL:-burrow-release}}}"
|
||||
|
||||
args=(
|
||||
sign
|
||||
--pkcs11-library "$module"
|
||||
--pkcs11-certificate-file "$certificate"
|
||||
--pkcs11-token-label "$token_label"
|
||||
--pkcs11-key-label "$kms_key"
|
||||
--pkcs11-pin "$pin"
|
||||
)
|
||||
if [[ -n "$timestamp_url" ]]; then
|
||||
args+=(--timestamp-url "$timestamp_url")
|
||||
fi
|
||||
if [[ -n "$entitlements" ]]; then
|
||||
args+=(--entitlements-xml-file "$entitlements")
|
||||
fi
|
||||
if [[ "$runtime" == "true" ]]; then
|
||||
args+=(--code-signature-flags runtime)
|
||||
fi
|
||||
args+=("$path")
|
||||
|
||||
rcodesign "${args[@]}"
|
||||
|
|
@ -261,7 +261,7 @@ async function main() {
|
|||
const issuerId = args["issuer-id"];
|
||||
const keyFile = args["key-file"];
|
||||
const outDir = args["out-dir"] ?? ".forgejo/actions/export";
|
||||
const appId = args["app-id"] ?? "com.hackclub.burrow";
|
||||
const appId = args["app-id"] ?? "net.burrow.app";
|
||||
const networkId = args["network-id"] ?? `${appId}.network`;
|
||||
const iosCertificateId = args["ios-certificate-id"] ?? process.env.BURROW_IOS_DISTRIBUTION_CERTIFICATE_ID;
|
||||
const macosCertificateId = args["macos-certificate-id"] ?? process.env.BURROW_DEVELOPER_ID_CERTIFICATE_ID;
|
||||
|
|
|
|||
|
|
@ -13,13 +13,15 @@ derived_data="${BURROW_DERIVED_DATA_PATH:-${repo_root}/.derived-data/apple}"
|
|||
source_packages="${BURROW_SWIFTPM_CACHE_PATH:-${XDG_CACHE_HOME:-${HOME}/.cache}/burrow/swiftpm}"
|
||||
project="Apple/Burrow.xcodeproj"
|
||||
scheme="${BURROW_APPLE_SCHEME:-App}"
|
||||
bundle_id="${BURROW_APPLE_BUNDLE_ID:-com.hackclub.burrow}"
|
||||
bundle_id="${BURROW_APPLE_BUNDLE_ID:-net.burrow.app}"
|
||||
network_extension_bundle_id="${BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID:-${bundle_id}.network}"
|
||||
signing_ready="${BURROW_APPLE_SIGNING_READY:-0}"
|
||||
signing_mode="${BURROW_APPLE_SIGNING_MODE:-keychain}"
|
||||
require_signing="${BURROW_APPLE_REQUIRE_SIGNING:-false}"
|
||||
sparkle_feed_url="${BURROW_SPARKLE_FEED_URL:-https://releases.burrow.net/sparkle/appcast.xml}"
|
||||
sparkle_public_ed_key="${BURROW_SPARKLE_PUBLIC_ED_KEY:-Myv9ZNZT6YGKMtMezh52ra4WqaeEKc4VlvVU0evhJeI=}"
|
||||
sparkle_sign_with_kms="${BURROW_SPARKLE_SIGN_WITH_KMS:-false}"
|
||||
notarize_macos="${BURROW_NOTARIZE_MACOS:-false}"
|
||||
|
||||
mkdir -p "$apple_root" "$archives_root" "$derived_data" "$source_packages"
|
||||
|
||||
|
|
@ -39,6 +41,74 @@ sha256_file() {
|
|||
fi
|
||||
}
|
||||
|
||||
safe_profile_slug() {
|
||||
python3 - "$1" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
|
||||
print(re.sub(r"[^A-Za-z0-9]", "", sys.argv[1]).lower())
|
||||
PY
|
||||
}
|
||||
|
||||
profile_for() {
|
||||
local identifier="$1"
|
||||
local suffix="$2"
|
||||
local slug path
|
||||
slug="$(safe_profile_slug "$identifier")"
|
||||
for path in \
|
||||
".forgejo/actions/export/${slug}${suffix}.provisionprofile" \
|
||||
".forgejo/actions/export/${slug}${suffix}.mobileprovision" \
|
||||
"Apple/Profiles/${slug}${suffix}.provisionprofile" \
|
||||
"Apple/Profiles/${slug}${suffix}.mobileprovision"; do
|
||||
if [[ -s "$path" ]]; then
|
||||
printf '%s\n' "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
generate_entitlements_from_profile() {
|
||||
local profile="$1"
|
||||
local output="$2"
|
||||
Scripts/apple/profile-entitlements.py "$profile" --output "$output"
|
||||
}
|
||||
|
||||
apple_signing_available() {
|
||||
if [[ "$signing_mode" == "google-kms" || "$signing_mode" == "google-kms-staged" || "$signing_mode" == "kms" ]]; then
|
||||
return 0
|
||||
fi
|
||||
[[ "$signing_ready" == "1" ]]
|
||||
}
|
||||
|
||||
kms_sign_bundle() {
|
||||
local family="$1"
|
||||
local bundle_path="$2"
|
||||
local entitlements="$3"
|
||||
local runtime_flag=()
|
||||
if [[ "$family" == "developer-id" ]]; then
|
||||
runtime_flag=(--runtime)
|
||||
fi
|
||||
Scripts/apple/sign-with-google-kms.sh \
|
||||
--family "$family" \
|
||||
--path "$bundle_path" \
|
||||
--entitlements "$entitlements" \
|
||||
"${runtime_flag[@]}"
|
||||
}
|
||||
|
||||
kms_sign_file() {
|
||||
local family="$1"
|
||||
local file_path="$2"
|
||||
local runtime_flag=()
|
||||
if [[ "$family" == "developer-id" ]]; then
|
||||
runtime_flag=(--runtime)
|
||||
fi
|
||||
Scripts/apple/sign-with-google-kms.sh \
|
||||
--family "$family" \
|
||||
--path "$file_path" \
|
||||
"${runtime_flag[@]}"
|
||||
}
|
||||
|
||||
write_version() {
|
||||
mkdir -p Apple/Configuration
|
||||
printf 'CURRENT_PROJECT_VERSION = %s\n' "$build_number" > Apple/Configuration/Version.xcconfig
|
||||
|
|
@ -93,7 +163,7 @@ unsigned_note() {
|
|||
ensure_signing_or_note() {
|
||||
local platform_name="$1"
|
||||
local artifact_name="$2"
|
||||
if [[ "$signing_ready" == "1" ]]; then
|
||||
if apple_signing_available; then
|
||||
return 0
|
||||
fi
|
||||
if truthy "$require_signing"; then
|
||||
|
|
@ -151,6 +221,14 @@ build_ios_release() {
|
|||
build_ios_unsigned
|
||||
return 0
|
||||
fi
|
||||
if [[ "$signing_mode" == "google-kms-staged" ]]; then
|
||||
build_ios_kms_staged
|
||||
return 0
|
||||
fi
|
||||
if [[ "$signing_mode" == "google-kms" || "$signing_mode" == "kms" ]]; then
|
||||
build_ios_kms_release
|
||||
return 0
|
||||
fi
|
||||
|
||||
archive_path="${archives_root}/Burrow-iOS-${build_number}.xcarchive"
|
||||
export_dir="${apple_root}/ios-export"
|
||||
|
|
@ -181,6 +259,91 @@ build_ios_release() {
|
|||
sha256_file "${apple_root}/Burrow-iOS-${build_number}.ipa"
|
||||
}
|
||||
|
||||
build_ios_kms_staged() {
|
||||
local product_dir source_app stage_dir ipa
|
||||
xcodebuild build \
|
||||
"${xcode_common_args[@]}" \
|
||||
-destination "generic/platform=iOS" \
|
||||
ARCHS=arm64 \
|
||||
ONLY_ACTIVE_ARCH=YES \
|
||||
SKIP_INSTALL=NO \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGN_IDENTITY=
|
||||
|
||||
product_dir="${derived_data}/Build/Products/Release-iphoneos"
|
||||
source_app="${product_dir}/Burrow.app"
|
||||
if [[ ! -d "$source_app" ]]; then
|
||||
echo "iOS build completed but did not produce Burrow.app" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
stage_dir="${apple_root}/ios-unsigned/Payload"
|
||||
rm -rf "$stage_dir"
|
||||
mkdir -p "$stage_dir"
|
||||
ditto "$source_app" "${stage_dir}/Burrow.app"
|
||||
ipa="${apple_root}/Burrow-iOS-${build_number}.unsigned.ipa"
|
||||
rm -f "$ipa"
|
||||
(cd "${apple_root}/ios-unsigned" && zip -qry "$ipa" Payload)
|
||||
sha256_file "$ipa"
|
||||
}
|
||||
|
||||
build_ios_kms_release() {
|
||||
local product_dir source_app stage_dir app ext app_profile ext_profile app_entitlements ext_entitlements ipa
|
||||
xcodebuild build \
|
||||
"${xcode_common_args[@]}" \
|
||||
-destination "generic/platform=iOS" \
|
||||
ARCHS=arm64 \
|
||||
ONLY_ACTIVE_ARCH=YES \
|
||||
SKIP_INSTALL=NO \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGN_IDENTITY=
|
||||
|
||||
product_dir="${derived_data}/Build/Products/Release-iphoneos"
|
||||
source_app="${product_dir}/Burrow.app"
|
||||
if [[ ! -d "$source_app" ]]; then
|
||||
echo "iOS build completed but did not produce Burrow.app" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
stage_dir="${apple_root}/ios-kms/Payload"
|
||||
rm -rf "$stage_dir"
|
||||
mkdir -p "$stage_dir"
|
||||
ditto "$source_app" "${stage_dir}/Burrow.app"
|
||||
app="${stage_dir}/Burrow.app"
|
||||
ext="${app}/PlugIns/BurrowNetworkExtension.appex"
|
||||
if [[ ! -d "$ext" ]]; then
|
||||
echo "iOS app is missing BurrowNetworkExtension.appex" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
app_profile="$(profile_for "$bundle_id" "appstore")" || {
|
||||
echo "missing iOS App Store provisioning profile for ${bundle_id}" >&2
|
||||
exit 1
|
||||
}
|
||||
ext_profile="$(profile_for "$network_extension_bundle_id" "appstore")" || {
|
||||
echo "missing iOS App Store provisioning profile for ${network_extension_bundle_id}" >&2
|
||||
exit 1
|
||||
}
|
||||
cp "$app_profile" "${app}/embedded.mobileprovision"
|
||||
cp "$ext_profile" "${ext}/embedded.mobileprovision"
|
||||
|
||||
app_entitlements="${apple_root}/ios-kms/app-entitlements.plist"
|
||||
ext_entitlements="${apple_root}/ios-kms/extension-entitlements.plist"
|
||||
generate_entitlements_from_profile "$app_profile" "$app_entitlements"
|
||||
generate_entitlements_from_profile "$ext_profile" "$ext_entitlements"
|
||||
|
||||
rm -rf "${app}/_CodeSignature" "${ext}/_CodeSignature"
|
||||
kms_sign_bundle ios "$ext" "$ext_entitlements"
|
||||
kms_sign_bundle ios "$app" "$app_entitlements"
|
||||
|
||||
ipa="${apple_root}/Burrow-iOS-${build_number}.ipa"
|
||||
rm -f "$ipa"
|
||||
(cd "${apple_root}/ios-kms" && zip -qry "$ipa" Payload)
|
||||
sha256_file "$ipa"
|
||||
}
|
||||
|
||||
build_macos_unsigned() {
|
||||
local product_dir zip_path
|
||||
xcodebuild build \
|
||||
|
|
@ -203,6 +366,14 @@ build_macos_release() {
|
|||
build_macos_unsigned
|
||||
return 0
|
||||
fi
|
||||
if [[ "$signing_mode" == "google-kms-staged" ]]; then
|
||||
build_macos_kms_staged
|
||||
return 0
|
||||
fi
|
||||
if [[ "$signing_mode" == "google-kms" || "$signing_mode" == "kms" ]]; then
|
||||
build_macos_kms_release
|
||||
return 0
|
||||
fi
|
||||
|
||||
archive_path="${archives_root}/Burrow-macOS-${build_number}.xcarchive"
|
||||
export_dir="${apple_root}/macos-export"
|
||||
|
|
@ -264,6 +435,135 @@ EOF
|
|||
fi
|
||||
}
|
||||
|
||||
build_macos_kms_staged() {
|
||||
local product_dir source_app zip_path
|
||||
xcodebuild build \
|
||||
"${xcode_common_args[@]}" \
|
||||
-destination "platform=macOS" \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGN_IDENTITY=
|
||||
|
||||
product_dir="${derived_data}/Build/Products/Release"
|
||||
source_app="${product_dir}/Burrow.app"
|
||||
if [[ ! -d "$source_app" ]]; then
|
||||
echo "macOS build completed but did not produce Burrow.app" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
zip_path="${apple_root}/Burrow-macOS-${build_number}.unsigned.zip"
|
||||
rm -f "$zip_path"
|
||||
ditto -c -k --keepParent "$source_app" "$zip_path"
|
||||
sha256_file "$zip_path"
|
||||
}
|
||||
|
||||
write_sparkle_appcast() {
|
||||
local dmg="$1"
|
||||
local channel sparkle_dir length pubdate url
|
||||
channel="${SPARKLE_CHANNEL:-build-${build_number}}"
|
||||
sparkle_dir="${out_root}/sparkle/${channel}"
|
||||
mkdir -p "$sparkle_dir"
|
||||
cp "$dmg" "$sparkle_dir/"
|
||||
length="$(wc -c < "$dmg" | tr -d ' ')"
|
||||
pubdate="$(LC_ALL=C date -u '+%a, %d %b %Y %H:%M:%S +0000')"
|
||||
url="${SPARKLE_DOWNLOAD_PREFIX:-https://releases.burrow.net/sparkle}/${channel}/$(basename "$dmg")"
|
||||
cat > "${sparkle_dir}/appcast.xml" <<EOF
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
|
||||
<channel>
|
||||
<title>Burrow ${channel}</title>
|
||||
<item>
|
||||
<title>Burrow ${build_number}</title>
|
||||
<pubDate>${pubdate}</pubDate>
|
||||
<sparkle:version>${build_number}</sparkle:version>
|
||||
<sparkle:shortVersionString>0.1</sparkle:shortVersionString>
|
||||
<enclosure url="${url}" sparkle:version="${build_number}" length="${length}" type="application/octet-stream" />
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
EOF
|
||||
|
||||
if truthy "$sparkle_sign_with_kms"; then
|
||||
Scripts/sparkle/sign-appcast-kms.py \
|
||||
--appcast "${sparkle_dir}/appcast.xml" \
|
||||
--artifact-dir "$sparkle_dir"
|
||||
fi
|
||||
}
|
||||
|
||||
notarize_dmg_if_requested() {
|
||||
local dmg="$1"
|
||||
local key_json
|
||||
if ! truthy "$notarize_macos"; then
|
||||
return 0
|
||||
fi
|
||||
if [[ -z "${ASC_API_KEY_ID:-}" || -z "${ASC_API_ISSUER_ID:-}" || -z "${ASC_API_KEY_PATH:-}" ]]; then
|
||||
echo "macOS notarization requires ASC_API_KEY_ID, ASC_API_ISSUER_ID, and ASC_API_KEY_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
key_json="${apple_root}/notary-api-key.json"
|
||||
rcodesign encode-app-store-connect-api-key \
|
||||
"$ASC_API_ISSUER_ID" \
|
||||
"$ASC_API_KEY_ID" \
|
||||
"$ASC_API_KEY_PATH" \
|
||||
"$key_json"
|
||||
rcodesign notary-submit --api-key-file "$key_json" --wait --staple "$dmg"
|
||||
rm -f "$key_json"
|
||||
}
|
||||
|
||||
build_macos_kms_release() {
|
||||
local product_dir source_app app ext app_profile ext_profile app_entitlements ext_entitlements dmg
|
||||
xcodebuild build \
|
||||
"${xcode_common_args[@]}" \
|
||||
-destination "platform=macOS" \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGN_IDENTITY=
|
||||
|
||||
product_dir="${derived_data}/Build/Products/Release"
|
||||
source_app="${product_dir}/Burrow.app"
|
||||
if [[ ! -d "$source_app" ]]; then
|
||||
echo "macOS build completed but did not produce Burrow.app" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
app="${apple_root}/macos-kms/Burrow.app"
|
||||
rm -rf "$app"
|
||||
mkdir -p "$(dirname "$app")"
|
||||
ditto "$source_app" "$app"
|
||||
ext="${app}/Contents/PlugIns/BurrowNetworkExtension.appex"
|
||||
|
||||
app_profile="$(profile_for "$bundle_id" "developerid")" || {
|
||||
echo "missing macOS Developer ID provisioning profile for ${bundle_id}" >&2
|
||||
exit 1
|
||||
}
|
||||
cp "$app_profile" "${app}/Contents/embedded.provisionprofile"
|
||||
app_entitlements="${apple_root}/macos-kms/app-entitlements.plist"
|
||||
generate_entitlements_from_profile "$app_profile" "$app_entitlements"
|
||||
|
||||
if [[ -d "$ext" ]]; then
|
||||
ext_profile="$(profile_for "$network_extension_bundle_id" "developerid")" || {
|
||||
echo "missing macOS Developer ID provisioning profile for ${network_extension_bundle_id}" >&2
|
||||
exit 1
|
||||
}
|
||||
cp "$ext_profile" "${ext}/Contents/embedded.provisionprofile"
|
||||
ext_entitlements="${apple_root}/macos-kms/extension-entitlements.plist"
|
||||
generate_entitlements_from_profile "$ext_profile" "$ext_entitlements"
|
||||
rm -rf "${ext}/Contents/_CodeSignature"
|
||||
kms_sign_bundle developer-id "$ext" "$ext_entitlements"
|
||||
fi
|
||||
|
||||
rm -rf "${app}/Contents/_CodeSignature"
|
||||
kms_sign_bundle developer-id "$app" "$app_entitlements"
|
||||
|
||||
dmg="${apple_root}/Burrow-macOS-${build_number}.dmg"
|
||||
rm -f "$dmg"
|
||||
hdiutil create -volname "Burrow ${build_number}" -srcfolder "$app" -ov -format UDZO "$dmg"
|
||||
kms_sign_file developer-id "$dmg"
|
||||
notarize_dmg_if_requested "$dmg"
|
||||
sha256_file "$dmg"
|
||||
write_sparkle_appcast "$dmg"
|
||||
}
|
||||
|
||||
require_xcodebuild
|
||||
write_version
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ cd "$repo_root"
|
|||
|
||||
platform="${1:-all}"
|
||||
out_dir="${BURROW_APPLE_PROFILES_OUT_DIR:-.forgejo/actions/export}"
|
||||
app_id="${BURROW_APPLE_BUNDLE_ID:-com.hackclub.burrow}"
|
||||
app_id="${BURROW_APPLE_BUNDLE_ID:-net.burrow.app}"
|
||||
network_id="${BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID:-${app_id}.network}"
|
||||
replace_certificate_mismatch="${BURROW_APPLE_REPLACE_CERTIFICATE_MISMATCH:-false}"
|
||||
if [[ -n "${BURROW_IOS_DISTRIBUTION_CERTIFICATE_ID:-}" || -n "${BURROW_DEVELOPER_ID_CERTIFICATE_ID:-}" ]]; then
|
||||
|
|
|
|||
197
Scripts/release/google-kms-openssl-dgst-shim.sh
Executable file
197
Scripts/release/google-kms-openssl-dgst-shim.sh
Executable file
|
|
@ -0,0 +1,197 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
real_openssl="${BURROW_REAL_OPENSSL:-openssl}"
|
||||
gcloud_bin="${BURROW_GCLOUD_BIN:-gcloud}"
|
||||
original_args=("$@")
|
||||
|
||||
exec_real_openssl() {
|
||||
exec "$real_openssl" "$@"
|
||||
}
|
||||
|
||||
truthy() {
|
||||
case "${1:-}" in
|
||||
1|true|TRUE|True|yes|YES|Yes|y|Y|on|ON|On) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
parse_pkcs11_key_name() {
|
||||
local uri="$1"
|
||||
local value
|
||||
value="${uri#*;object=}"
|
||||
if [[ "$value" != "$uri" ]]; then
|
||||
printf '%s\n' "${value%%;*}"
|
||||
return 0
|
||||
fi
|
||||
value="${uri#*;id=}"
|
||||
if [[ "$value" != "$uri" ]]; then
|
||||
printf '%s\n' "${value%%;*}"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
select_single_enabled_version() {
|
||||
local project_id="$1"
|
||||
local kms_location="$2"
|
||||
local kms_keyring_name="$3"
|
||||
local crypto_key_name="$4"
|
||||
local versions=()
|
||||
local version
|
||||
|
||||
while IFS= read -r version; do
|
||||
[[ -n "$version" ]] && versions+=("$version")
|
||||
done < <(
|
||||
"$gcloud_bin" kms keys versions list \
|
||||
--project="$project_id" \
|
||||
--location="$kms_location" \
|
||||
--keyring="$kms_keyring_name" \
|
||||
--key="$crypto_key_name" \
|
||||
--filter=state=ENABLED \
|
||||
--format='value(name)'
|
||||
)
|
||||
if [[ "${#versions[@]}" -ne 1 || -z "${versions[0]:-}" ]]; then
|
||||
echo "error: Google KMS crypto key ${crypto_key_name} has ${#versions[@]} ENABLED versions; set BURROW_GCP_KMS_ACTIVE_CRYPTO_KEY_VERSION" >&2
|
||||
return 1
|
||||
fi
|
||||
printf '%s\n' "${versions[0]##*/}"
|
||||
}
|
||||
|
||||
if [[ "${1:-}" != "dgst" ]]; then
|
||||
exec_real_openssl "${original_args[@]}"
|
||||
fi
|
||||
|
||||
if ! truthy "${BURROW_APPLE_PKCS11_SIGN_WITH_GCLOUD:-true}"; then
|
||||
exec_real_openssl "${original_args[@]}"
|
||||
fi
|
||||
|
||||
shift
|
||||
digest_algorithm=""
|
||||
signature_file=""
|
||||
input_file=""
|
||||
sign_uri=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-sha256)
|
||||
digest_algorithm="sha256"
|
||||
shift
|
||||
;;
|
||||
-sign)
|
||||
[[ $# -ge 2 ]] || {
|
||||
echo "error: openssl dgst shim received -sign without a key URI" >&2
|
||||
exit 2
|
||||
}
|
||||
sign_uri="$2"
|
||||
shift 2
|
||||
;;
|
||||
-out)
|
||||
[[ $# -ge 2 ]] || {
|
||||
echo "error: openssl dgst shim received -out without a path" >&2
|
||||
exit 2
|
||||
}
|
||||
signature_file="$2"
|
||||
shift 2
|
||||
;;
|
||||
-provider|-passin)
|
||||
[[ $# -ge 2 ]] || {
|
||||
echo "error: openssl dgst shim received $1 without a value" >&2
|
||||
exit 2
|
||||
}
|
||||
shift 2
|
||||
;;
|
||||
-*)
|
||||
exec_real_openssl "${original_args[@]}"
|
||||
;;
|
||||
*)
|
||||
input_file="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$digest_algorithm" != "sha256" || "$sign_uri" != pkcs11:* ]]; then
|
||||
exec_real_openssl "${original_args[@]}"
|
||||
fi
|
||||
if [[ -z "$signature_file" || -z "$input_file" ]]; then
|
||||
echo "error: openssl dgst shim requires -out and an input file" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ ! -f "$input_file" ]]; then
|
||||
echo "error: openssl dgst shim input file does not exist: $input_file" >&2
|
||||
exit 1
|
||||
fi
|
||||
command -v "$gcloud_bin" >/dev/null 2>&1 || {
|
||||
echo "error: gcloud is required for Google KMS direct signing; set BURROW_GCLOUD_BIN" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v python3 >/dev/null 2>&1 || {
|
||||
echo "error: python3 is required to decode Google KMS signatures" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
crypto_key="${BURROW_GCP_KMS_ACTIVE_CRYPTO_KEY:-}"
|
||||
if [[ -z "$crypto_key" ]]; then
|
||||
crypto_key="$(parse_pkcs11_key_name "$sign_uri" || true)"
|
||||
fi
|
||||
if [[ -z "$crypto_key" ]]; then
|
||||
echo "error: BURROW_GCP_KMS_ACTIVE_CRYPTO_KEY or a pkcs11 object selector is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
key_version="${BURROW_GCP_KMS_ACTIVE_CRYPTO_KEY_VERSION:-}"
|
||||
key_ring="${BURROW_GCP_KMS_KEY_RING:-${GOOGLE_KMS_KEY_RING:-projects/${GOOGLE_CLOUD_PROJECT:-project-88c23ce9-918a-470a-b33}/locations/${BURROW_KMS_LOCATION:-global}/keyRings/${BURROW_KMS_KEYRING:-burrow-identity}}}"
|
||||
project_id="${GOOGLE_CLOUD_PROJECT:-project-88c23ce9-918a-470a-b33}"
|
||||
kms_location=""
|
||||
kms_keyring_name=""
|
||||
crypto_key_name=""
|
||||
|
||||
if [[ "$crypto_key" =~ ^projects/([^/]+)/locations/([^/]+)/keyRings/([^/]+)/cryptoKeys/([^/]+)/cryptoKeyVersions/([^/]+)$ ]]; then
|
||||
project_id="${BASH_REMATCH[1]}"
|
||||
kms_location="${BASH_REMATCH[2]}"
|
||||
kms_keyring_name="${BASH_REMATCH[3]}"
|
||||
crypto_key_name="${BASH_REMATCH[4]}"
|
||||
key_version="${BASH_REMATCH[5]}"
|
||||
elif [[ "$crypto_key" =~ ^projects/([^/]+)/locations/([^/]+)/keyRings/([^/]+)/cryptoKeys/([^/]+)$ ]]; then
|
||||
project_id="${BASH_REMATCH[1]}"
|
||||
kms_location="${BASH_REMATCH[2]}"
|
||||
kms_keyring_name="${BASH_REMATCH[3]}"
|
||||
crypto_key_name="${BASH_REMATCH[4]}"
|
||||
else
|
||||
if [[ ! "$key_ring" =~ ^projects/([^/]+)/locations/([^/]+)/keyRings/([^/]+)$ ]]; then
|
||||
echo "error: BURROW_GCP_KMS_KEY_RING must be projects/<project>/locations/<location>/keyRings/<name>, got: ${key_ring}" >&2
|
||||
exit 1
|
||||
fi
|
||||
project_id="${BASH_REMATCH[1]}"
|
||||
kms_location="${BASH_REMATCH[2]}"
|
||||
kms_keyring_name="${BASH_REMATCH[3]}"
|
||||
crypto_key_name="$crypto_key"
|
||||
fi
|
||||
|
||||
if [[ -z "$key_version" ]]; then
|
||||
key_version="$(select_single_enabled_version "$project_id" "$kms_location" "$kms_keyring_name" "$crypto_key_name")"
|
||||
fi
|
||||
|
||||
encoded_signature_file="$(mktemp "${TMPDIR:-/tmp}/burrow-google-kms-signature.XXXXXX")"
|
||||
trap 'rm -f "$encoded_signature_file"' EXIT
|
||||
"$gcloud_bin" kms asymmetric-sign \
|
||||
--project="$project_id" \
|
||||
--location="$kms_location" \
|
||||
--keyring="$kms_keyring_name" \
|
||||
--key="$crypto_key_name" \
|
||||
--version="$key_version" \
|
||||
--digest-algorithm="$digest_algorithm" \
|
||||
--input-file="$input_file" \
|
||||
--signature-file="$encoded_signature_file" \
|
||||
--quiet >/dev/null
|
||||
|
||||
python3 - "$encoded_signature_file" "$signature_file" <<'PY'
|
||||
import base64
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
encoded_path = pathlib.Path(sys.argv[1])
|
||||
signature_path = pathlib.Path(sys.argv[2])
|
||||
signature_path.write_bytes(base64.b64decode(encoded_path.read_text(encoding="utf-8").strip()))
|
||||
PY
|
||||
93
Scripts/release/google-kms-pkcs11-materialize.sh
Executable file
93
Scripts/release/google-kms-pkcs11-materialize.sh
Executable file
|
|
@ -0,0 +1,93 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
emit_env=false
|
||||
ROOT="$(
|
||||
git rev-parse --show-toplevel 2>/dev/null || {
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/../.."
|
||||
pwd
|
||||
}
|
||||
)"
|
||||
runtime_dir="${BURROW_GCP_KMS_PKCS11_RUNTIME_DIR:-${RUNNER_TEMP:-${TMPDIR:-/tmp}}/burrow-google-kms-pkcs11-runtime}"
|
||||
config_file="${BURROW_GCP_KMS_PKCS11_CONFIG:-${KMS_PKCS11_CONFIG:-}}"
|
||||
config_yaml="${BURROW_GCP_KMS_PKCS11_CONFIG_YAML:-}"
|
||||
key_ring="${BURROW_GCP_KMS_KEY_RING:-${GOOGLE_KMS_KEY_RING:-projects/${GOOGLE_CLOUD_PROJECT:-project-88c23ce9-918a-470a-b33}/locations/${BURROW_KMS_LOCATION:-global}/keyRings/${BURROW_KMS_KEYRING:-burrow-identity}}}"
|
||||
token_label="${BURROW_GCP_KMS_PKCS11_TOKEN_LABEL:-${BURROW_PKCS11_TOKEN_LABEL:-burrow-release}}"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: Scripts/release/google-kms-pkcs11-materialize.sh [--emit-env]
|
||||
|
||||
Materializes optional Google KMS PKCS#11 config YAML into a runner-local file
|
||||
and emits environment variables consumed by the Apple release signer.
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--emit-env)
|
||||
emit_env=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "error: unknown argument $1" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
write_config_file() {
|
||||
local path="$1"
|
||||
local value="$2"
|
||||
mkdir -p "$(dirname "$path")"
|
||||
printf '%s\n' "$value" >"$path"
|
||||
chmod 600 "$path"
|
||||
}
|
||||
|
||||
resolve_config_path() {
|
||||
local path="$1"
|
||||
if [[ -z "$path" ]]; then
|
||||
return 1
|
||||
fi
|
||||
if [[ "$path" != /* ]]; then
|
||||
path="$ROOT/$path"
|
||||
fi
|
||||
printf '%s\n' "$path"
|
||||
}
|
||||
|
||||
env_lines=()
|
||||
if [[ -z "$config_file" && -n "$config_yaml" ]]; then
|
||||
config_file="$runtime_dir/pkcs11-config.yaml"
|
||||
write_config_file "$config_file" "$config_yaml"
|
||||
fi
|
||||
|
||||
if [[ -z "$config_file" && -n "$key_ring" ]]; then
|
||||
config_file="$runtime_dir/pkcs11-config.yaml"
|
||||
write_config_file "$config_file" "---
|
||||
tokens:
|
||||
- key_ring: \"${key_ring}\"
|
||||
label: \"${token_label}\""
|
||||
fi
|
||||
|
||||
if [[ -n "$config_file" ]]; then
|
||||
config_file="$(resolve_config_path "$config_file")"
|
||||
if [[ ! -s "$config_file" ]]; then
|
||||
echo "error: Google KMS PKCS#11 config is missing or empty: $config_file" >&2
|
||||
exit 1
|
||||
fi
|
||||
env_lines+=("BURROW_GCP_KMS_PKCS11_CONFIG=$config_file")
|
||||
env_lines+=("KMS_PKCS11_CONFIG=$config_file")
|
||||
fi
|
||||
|
||||
if [[ "${#env_lines[@]}" -gt 0 && -n "${GITHUB_ENV:-}" ]]; then
|
||||
printf '%s\n' "${env_lines[@]}" >>"$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
if [[ "$emit_env" == "true" && "${#env_lines[@]}" -gt 0 ]]; then
|
||||
printf 'export %q\n' "${env_lines[@]}"
|
||||
fi
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
bundle_id="${BURROW_UI_TEST_APP_BUNDLE_ID:-com.hackclub.burrow}"
|
||||
bundle_id="${BURROW_UI_TEST_APP_BUNDLE_ID:-net.burrow.app}"
|
||||
simulator_name="${BURROW_UI_TEST_SIMULATOR_NAME:-iPhone 17 Pro}"
|
||||
simulator_os="${BURROW_UI_TEST_SIMULATOR_OS:-26.4}"
|
||||
simulator_id="${BURROW_UI_TEST_SIMULATOR_ID:-}"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
bundle_id="${BURROW_UI_TEST_APP_BUNDLE_ID:-com.hackclub.burrow}"
|
||||
bundle_id="${BURROW_UI_TEST_APP_BUNDLE_ID:-net.burrow.app}"
|
||||
smoke_root="${BURROW_TAILNET_SMOKE_ROOT:-/tmp/burrow-tailnet-connectivity}"
|
||||
socket_path="${smoke_root}/burrow.sock"
|
||||
db_path="${smoke_root}/burrow.db"
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ def kms_sign(args: argparse.Namespace, payload: Path) -> bytes:
|
|||
if args.gcloud_project:
|
||||
command.extend(["--project", args.gcloud_project])
|
||||
run(command)
|
||||
return signature_path.read_bytes()
|
||||
return base64.b64decode(signature_path.read_text(encoding="utf-8").strip())
|
||||
|
||||
|
||||
def enclosure_path(enclosure: ET.Element, artifact_dir: Path) -> Path:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue