#!/usr/bin/env bash

set -euo pipefail

readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
readonly REPOSITORY_ROOT="$(cd -- "$SCRIPT_DIR/.." && pwd -P)"
readonly POLICY_PATH="$REPOSITORY_ROOT/release/gateway-image-signing-policy.json"
readonly EXPECTED_SCHEMA="nexuscrm.oci-owner-signing-policy/v4"
readonly EXPECTED_REPOSITORY="pingcap-inc/nexuscrm-api-host"
readonly EXPECTED_REPOSITORY_ID="1279604404"
readonly EXPECTED_REF="refs/heads/main"
readonly EXPECTED_WORKFLOW="publish-gateway-image"
readonly EXPECTED_WORKFLOW_REF="pingcap-inc/nexuscrm-api-host/.github/workflows/publish-gateway-image.yml@refs/heads/main"
readonly EXPECTED_OIDC_SUBJECT="repo:pingcap-inc/nexuscrm-api-host:ref:refs/heads/main"
readonly EXPECTED_OIDC_ISSUER="https://token.actions.githubusercontent.com"
readonly EXPECTED_OIDC_JWKS_URI="https://token.actions.githubusercontent.com/.well-known/jwks"
readonly EXPECTED_OIDC_AUDIENCE_PREFIX="urn:nexuscrm:gateway-image:"
readonly EXPECTED_OIDC_TOKEN_ANNOTATION="io.nexuscrm.github-oidc-token"
readonly EXPECTED_IMAGE="ghcr.io/pingcap-inc/nexuscrm-api-host-gateway"
readonly EXPECTED_PUBLIC_KEY_PATH="release/gateway-image-signing-public-key.pem"
readonly EXPECTED_PROVENANCE_BUILD_TYPE="https://github.com/pingcap-inc/nexuscrm-api-host/blob/main/docs/gateway-image-build-type-v1.md"
readonly EXPECTED_PROVENANCE_PREDICATE_TYPE="https://slsa.dev/provenance/v1"

fail() {
  printf 'gateway image signing policy: %s\n' "$*" >&2
  exit 1
}

load_owner_policy() {
  [ -f "$POLICY_PATH" ] || fail "owner signing policy is missing"
  jq -e '
    type == "object" and
    (keys == [
      "aws_region",
      "aws_role_arn",
      "image_repository",
      "key_spec",
      "key_usage",
      "kms_key_arn",
      "oidc_audience_prefix",
      "oidc_issuer",
      "oidc_jwks_uri",
      "oidc_max_token_lifetime_seconds",
      "oidc_subject",
      "oidc_token_annotation",
      "owner_repository",
      "provenance_build_type",
      "provenance_builder_id",
      "provenance_predicate_type",
      "public_key_path",
      "public_key_sha256",
      "publisher_state",
      "repository_id",
      "repository_visibility",
      "runner_environment",
      "schema_version",
      "signing_algorithm",
      "source_ref",
      "workflow_name",
      "workflow_ref"
    ])
  ' "$POLICY_PATH" >/dev/null || fail "owner signing policy has an unknown or missing fact"

  POLICY_SCHEMA="$(jq -er '.schema_version' "$POLICY_PATH")"
  OWNER_REPOSITORY="$(jq -er '.owner_repository' "$POLICY_PATH")"
  PROVENANCE_BUILDER_ID="$(jq -er '.provenance_builder_id' "$POLICY_PATH")"
  PROVENANCE_BUILD_TYPE="$(jq -er '.provenance_build_type' "$POLICY_PATH")"
  PROVENANCE_PREDICATE_TYPE="$(jq -er '.provenance_predicate_type' "$POLICY_PATH")"
  REPOSITORY_ID="$(jq -er '.repository_id' "$POLICY_PATH")"
  REPOSITORY_VISIBILITY="$(jq -er '.repository_visibility' "$POLICY_PATH")"
  SOURCE_REF="$(jq -er '.source_ref' "$POLICY_PATH")"
  WORKFLOW_NAME="$(jq -er '.workflow_name' "$POLICY_PATH")"
  WORKFLOW_REF="$(jq -er '.workflow_ref' "$POLICY_PATH")"
  RUNNER_ENVIRONMENT="$(jq -er '.runner_environment' "$POLICY_PATH")"
  OIDC_SUBJECT="$(jq -er '.oidc_subject' "$POLICY_PATH")"
  OIDC_ISSUER="$(jq -er '.oidc_issuer' "$POLICY_PATH")"
  OIDC_JWKS_URI="$(jq -er '.oidc_jwks_uri' "$POLICY_PATH")"
  OIDC_AUDIENCE_PREFIX="$(jq -er '.oidc_audience_prefix' "$POLICY_PATH")"
  OIDC_TOKEN_ANNOTATION="$(jq -er '.oidc_token_annotation' "$POLICY_PATH")"
  OIDC_MAX_TOKEN_LIFETIME_SECONDS="$(jq -er '.oidc_max_token_lifetime_seconds' "$POLICY_PATH")"
  IMAGE_REPOSITORY="$(jq -er '.image_repository' "$POLICY_PATH")"
  AWS_ROLE_ARN="$(jq -er '.aws_role_arn' "$POLICY_PATH")"
  AWS_REGION="$(jq -er '.aws_region' "$POLICY_PATH")"
  KMS_KEY_ARN="$(jq -er '.kms_key_arn' "$POLICY_PATH")"
  KEY_SPEC="$(jq -er '.key_spec' "$POLICY_PATH")"
  KEY_USAGE="$(jq -er '.key_usage' "$POLICY_PATH")"
  SIGNING_ALGORITHM="$(jq -er '.signing_algorithm' "$POLICY_PATH")"
  PUBLIC_KEY_RELATIVE_PATH="$(jq -er '.public_key_path' "$POLICY_PATH")"
  PUBLIC_KEY_SHA256="$(jq -er '.public_key_sha256' "$POLICY_PATH")"
  PUBLISHER_STATE="$(jq -er '.publisher_state' "$POLICY_PATH")"
  PUBLIC_KEY_PATH="$REPOSITORY_ROOT/$PUBLIC_KEY_RELATIVE_PATH"
  POLICY_SHA256="sha256:$(sha256sum "$POLICY_PATH" | awk '{print $1}')"

  [ "$POLICY_SCHEMA" = "$EXPECTED_SCHEMA" ] || fail "unexpected owner signing policy schema"
  [ "$OWNER_REPOSITORY" = "$EXPECTED_REPOSITORY" ] || fail "unexpected owner repository"
  [ "$PROVENANCE_BUILD_TYPE" = "$EXPECTED_PROVENANCE_BUILD_TYPE" ] || fail "unexpected provenance build type"
  [ "$PROVENANCE_PREDICATE_TYPE" = "$EXPECTED_PROVENANCE_PREDICATE_TYPE" ] || fail "unexpected provenance predicate type"
  [ "$REPOSITORY_ID" = "$EXPECTED_REPOSITORY_ID" ] || fail "unexpected immutable repository ID"
  [ "$REPOSITORY_VISIBILITY" = "private" ] || fail "unexpected repository visibility"
  [ "$SOURCE_REF" = "$EXPECTED_REF" ] || fail "unexpected source ref"
  [ "$WORKFLOW_NAME" = "$EXPECTED_WORKFLOW" ] || fail "unexpected workflow name"
  [ "$WORKFLOW_REF" = "$EXPECTED_WORKFLOW_REF" ] || fail "unexpected workflow ref"
  [ "$RUNNER_ENVIRONMENT" = "github-hosted" ] || fail "unexpected runner environment"
  [ "$OIDC_SUBJECT" = "$EXPECTED_OIDC_SUBJECT" ] || fail "unexpected OIDC subject"
  [ "$OIDC_ISSUER" = "$EXPECTED_OIDC_ISSUER" ] || fail "unexpected OIDC issuer"
  [ "$OIDC_JWKS_URI" = "$EXPECTED_OIDC_JWKS_URI" ] || fail "unexpected OIDC JWKS URI"
  [ "$OIDC_AUDIENCE_PREFIX" = "$EXPECTED_OIDC_AUDIENCE_PREFIX" ] || fail "unexpected OIDC audience policy"
  [ "$OIDC_TOKEN_ANNOTATION" = "$EXPECTED_OIDC_TOKEN_ANNOTATION" ] || fail "unexpected OIDC token annotation"
  [ "$OIDC_MAX_TOKEN_LIFETIME_SECONDS" = "600" ] || fail "unexpected OIDC token lifetime"
  [ "$IMAGE_REPOSITORY" = "$EXPECTED_IMAGE" ] || fail "unexpected image repository"
  [ "$PUBLIC_KEY_RELATIVE_PATH" = "$EXPECTED_PUBLIC_KEY_PATH" ] || fail "unexpected public key path"
  case "$PUBLISHER_STATE" in
    bootstrap_inactive)
      [ -z "$PROVENANCE_BUILDER_ID" ] || fail "inactive publisher must not claim a builder"
      ;;
    active)
      [[ "$PROVENANCE_BUILDER_ID" =~ ^https://github\.com/pingcap-inc/nexuscrm-api-host/\.github/workflows/trusted-publish-gateway-image\.yml@[0-9a-f]{40}$ ]] || \
        fail "active publisher must pin the exact trusted workflow SHA"
      ;;
    *) fail "unexpected publisher state" ;;
  esac
  [ "$KEY_SPEC" = "ECC_NIST_P256" ] || fail "unexpected KMS key specification"
  [ "$KEY_USAGE" = "SIGN_VERIFY" ] || fail "unexpected KMS key usage"
  [ "$SIGNING_ALGORITHM" = "ECDSA_SHA_256" ] || fail "unexpected KMS signing algorithm"
  [[ "$AWS_REGION" =~ ^[a-z]{2}(-gov)?-[a-z]+-[0-9]+$ ]] || fail "invalid AWS region"

  local key_pattern='^arn:(aws|aws-us-gov|aws-cn):kms:([a-z0-9-]+):([0-9]{12}):key/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$'
  [[ "$KMS_KEY_ARN" =~ $key_pattern ]] || fail "KMS key must be an immutable key ARN, not an alias"
  local partition="${BASH_REMATCH[1]}"
  local key_region="${BASH_REMATCH[2]}"
  local account="${BASH_REMATCH[3]}"
  [ "$key_region" = "$AWS_REGION" ] || fail "KMS key region does not match owner policy"
  [[ "$AWS_ROLE_ARN" =~ ^arn:${partition}:iam::${account}:role/[A-Za-z0-9+=,.@_/-]+$ ]] || \
    fail "AWS role and KMS key must use the same partition and account"

  [ -f "$PUBLIC_KEY_PATH" ] || fail "owner public key is missing"
  local actual_public_key_sha256="sha256:$(sha256sum "$PUBLIC_KEY_PATH" | awk '{print $1}')"
  [ "$actual_public_key_sha256" = "$PUBLIC_KEY_SHA256" ] || fail "owner public key digest does not match policy"
  openssl pkey -pubin -in "$PUBLIC_KEY_PATH" -noout >/dev/null 2>&1 || fail "owner public key is invalid"
}

annotations() {
  printf '%s\n' \
    "io.nexuscrm.source.repository=$OWNER_REPOSITORY" \
    "io.nexuscrm.source.commit=$SOURCE_COMMIT" \
    "io.nexuscrm.source.ref=$SOURCE_REF" \
    "io.nexuscrm.signer.workflow=$WORKFLOW_REF" \
    "io.nexuscrm.signer.key-arn=$KMS_KEY_ARN" \
    "io.nexuscrm.signer.policy-sha256=$POLICY_SHA256"
}

build_annotation_args() {
  cosign_annotation_args=()
  while IFS= read -r annotation; do
    cosign_annotation_args+=(-a "$annotation")
  done < <(annotations)
}

verifier_args() {
  gateway_verifier_args=(
    go run ./cmd/gateway-release-verifier
    --policy "$POLICY_PATH"
    --source-commit "$SOURCE_COMMIT"
    --image-digest "$IMAGE_DIGEST"
  )
  if [ -n "${GATEWAY_OIDC_JWKS_FILE:-}" ]; then
    gateway_verifier_args+=(--jwks "$GATEWAY_OIDC_JWKS_FILE")
  fi
}

verify_cosign_evidence() {
  local evidence_file
  local provenance_evidence_file
  evidence_file="$(mktemp)"
  provenance_evidence_file="$(mktemp)"
  trap "rm -f '$evidence_file' '$provenance_evidence_file'" EXIT
  build_annotation_args
  cosign verify \
    --key "$PUBLIC_KEY_PATH" \
    --new-bundle-format=false \
    --insecure-ignore-tlog \
    "${cosign_annotation_args[@]}" \
    "$IMAGE_NAME@$IMAGE_DIGEST" > "$evidence_file"
  cosign verify-attestation \
    --key "$PUBLIC_KEY_PATH" \
    --new-bundle-format=false \
    --insecure-ignore-tlog \
    --type slsaprovenance1 \
    "$IMAGE_NAME@$IMAGE_DIGEST" > "$provenance_evidence_file"
  verifier_args
  (cd "$REPOSITORY_ROOT" && "${gateway_verifier_args[@]}" \
    --cosign-evidence "$evidence_file" \
    --provenance-evidence "$provenance_evidence_file")
}

[ "${1:-}" = verify-release ] && [ "$#" -eq 3 ] || \
  fail "usage: $0 verify-release <source-commit> <image-digest>"

SOURCE_COMMIT="$2"
IMAGE_DIGEST="$3"
[[ "$SOURCE_COMMIT" =~ ^[0-9a-f]{40}$ ]] || fail "source commit must be a full lowercase commit SHA"
[[ "$IMAGE_DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]] || fail "image digest must be canonical"
load_owner_policy
[ "$PUBLISHER_STATE" = active ] || fail "Gateway publisher is not active"
IMAGE_NAME="$IMAGE_REPOSITORY"
verify_cosign_evidence
