#!/usr/bin/env bash
#
# Sign macOS binaries with a *stable* code identity.
#
# Why this exists
# ---------------
# macOS binds a keychain item's ACL to the code identity of the process that
# created it. For unsigned or ad-hoc-signed code that identity is the binary's
# CDHash, which changes on every single compile. The practical consequences for
# an OpenLatch user on a Mac:
#
#   * Every keychain read raises "openlatch wants to use your confidential
#     information stored in 'openlatch' in your keychain."
#   * Clicking "Always Allow" appears to work but is void the moment a new
#     build lands — a rebuild locally, or an auto-update in production, is a
#     brand-new identity as far as the ACL is concerned.
#
# Signing with a stable identity fixes this at the root: the ACL then records
# the *designated requirement* (identifier + signing certificate), which is
# invariant across rebuilds. "Always Allow" sticks for good.
#
# The `--identifier` values below are pinned for the same reason: codesign
# otherwise derives the identifier from the filename, so renaming a packaged
# artifact would silently mint a new identity.
#
# Usage
# -----
#   ci/codesign-macos.sh <binary> [<binary> ...]
#
# Environment
# -----------
#   OPENLATCH_CODESIGN_IDENTITY  Signing identity (certificate common name or
#                                SHA-1 hash) as shown by:
#                                  security find-identity -v -p codesigning
#                                When empty the script skips signing and exits
#                                0, so forks and PR builds without signing
#                                secrets stay green.
#   OPENLATCH_CODESIGN_REQUIRED  Set to 1 to turn a missing identity into a
#                                hard failure. Release builds set this.
#   OPENLATCH_CODESIGN_TIMESTAMP `yes` / `none`. Defaults to `yes` for a real
#                                Developer ID certificate (notarization
#                                requires a trusted timestamp) and `none`
#                                otherwise, so local dev signing works offline.
#
# Run `ci/setup-dev-codesign-macos.sh` first to create a local dev identity.

set -euo pipefail

if [ "$#" -eq 0 ]; then
  echo "usage: $0 <binary> [<binary> ...]" >&2
  exit 2
fi

if [ "$(uname -s)" != "Darwin" ]; then
  echo "codesign-macos: not macOS, nothing to do."
  exit 0
fi

IDENTITY="${OPENLATCH_CODESIGN_IDENTITY:-}"

if [ -z "$IDENTITY" ]; then
  if [ "${OPENLATCH_CODESIGN_REQUIRED:-0}" = "1" ]; then
    echo "ERROR: OPENLATCH_CODESIGN_IDENTITY is empty but signing is required." >&2
    echo "       Release builds must ship a stable code identity — see the" >&2
    echo "       header of this script for why an ad-hoc signature is not enough." >&2
    exit 1
  fi
  echo "codesign-macos: OPENLATCH_CODESIGN_IDENTITY not set — skipping."
  echo "  Binaries keep their ad-hoc signature. Expect repeated keychain"
  echo "  authorization dialogs; run ci/setup-dev-codesign-macos.sh to fix."
  exit 0
fi

# Notarization needs a trusted timestamp; a self-signed dev cert cannot get one
# (and would fail offline), so only opt in for a real Developer ID.
TIMESTAMP="${OPENLATCH_CODESIGN_TIMESTAMP:-}"
if [ -z "$TIMESTAMP" ]; then
  case "$IDENTITY" in
    *"Developer ID"*) TIMESTAMP="yes" ;;
    *) TIMESTAMP="none" ;;
  esac
fi

if [ "$TIMESTAMP" = "yes" ]; then
  TIMESTAMP_FLAG="--timestamp"
else
  TIMESTAMP_FLAG="--timestamp=none"
fi

# Stable, filename-independent identifiers. Adding a binary here is deliberate:
# an unlisted one would fall back to its filename.
identifier_for() {
  case "$(basename "$1")" in
    openlatch)      echo "ai.openlatch.client" ;;
    openlatch-hook) echo "ai.openlatch.hook" ;;
    *)
      echo "ERROR: no pinned code identifier for '$(basename "$1")'." >&2
      echo "       Add one to identifier_for() in $0 rather than letting" >&2
      echo "       codesign derive it from the filename." >&2
      return 1
      ;;
  esac
}

echo "codesign-macos: identity=${IDENTITY} timestamp=${TIMESTAMP}"

for BIN in "$@"; do
  if [ ! -f "$BIN" ]; then
    echo "ERROR: $BIN does not exist." >&2
    exit 1
  fi

  IDENT="$(identifier_for "$BIN")"

  # --force replaces the ad-hoc signature cargo's linker already applied.
  # --options runtime enables the hardened runtime (required to notarize).
  codesign \
    --force \
    --sign "$IDENTITY" \
    --identifier "$IDENT" \
    --options runtime \
    $TIMESTAMP_FLAG \
    "$BIN"

  # --strict catches a signature that exists but would be rejected at load.
  codesign --verify --strict --verbose=2 "$BIN"

  echo "  signed $BIN as $IDENT"
done

echo "codesign-macos: done."
