#!/bin/bash

## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

## Used as 'gpg.openpgp.program' by 'git_verify_command_wrapper' (defined in
## 'help-steps/variables') for submodule signature verification in
## 'derivative-update'.

## NOTE SECURITY:
## Do NOT parse `sq verify`'s human-readable output!
## Do not derive GnuPG status fields like keyid, userid, fingerprint, or
## primary_fpr by parsing `sq verify` output!
## Why this is unsafe:
## https://www.kicksecure.com/wiki/OpenPGP#Sequoia-PGP_automation

#set -x
set -o errexit
set -o nounset
set -o pipefail
set -o errtrace
shopt -s inherit_errexit
shopt -s shift_verbose

true "INFO: Currently running script: ${BASH_SOURCE[0]} $*"

## This is what 'git' passes:
## verification:
#--keyid-format=long --status-fd=1 --verify /tmp/user/1000/.git_vtag_tmp75DXqK -
##
## signing:
# --status-fd=2 -bsau Some Real Name <email@example.com>'
## Meaning:
## -b --detach-sign
## -s --sign
## -a --armor
## -u --local-user

sq_bin="${SQ_BIN:-sq}"
debug="${SQ_GIT_WRAPPER_DEBUG:-}"

exit_with_error() {
  printf '%s\n' "$0: ERROR: $*" >&2
  exit 2
}

log() {
  printf '%s\n' "$0: $*" >&2
}

mode=""
sigfile=""
signing_key=""
status_fd=""

while (($#)); do
  case "$1" in
    --keyid-format)
      [ "$#" -ge 2 ] || exit_with_error "missing value for --keyid-format '$1'"
      shift 2
      ;;
    --keyid-format=*)
      shift
      ;;
    --status-fd)
      [ "$#" -ge 2 ] || exit_with_error "missing value for --status-fd"
      status_fd="$2"
      shift 2
      ;;
    --status-fd=*)
      status_fd="${1#--status-fd=}"
      shift
      ;;
    --verify)
      [ "$#" -ge 3 ] || exit_with_error "missing arguments for --verify option"
      mode="verify"
      sigfile="$2"
      [ "$3" = "-" ] || exit_with_error "expected '-' after signature file, got '$3'"
      shift 3
      ;;
    -bsau)
      [ "$#" -ge 2 ] || exit_with_error "missing signing key after -bsau option"
      mode="sign"
      signing_key="$2"
      shift 2
      ;;
    -bsau?*)
      mode="sign"
      signing_key="${1#-bsau}"
      shift
      ;;
    -u)
      [ "$#" -ge 2 ] || exit_with_error "missing signing key after -u option"
      mode="sign"
      signing_key="$2"
      shift 2
      ;;
    -u?*)
      mode="sign"
      signing_key="${1#-u}"
      shift
      ;;
    --local-user)
      [ "$#" -ge 2 ] || exit_with_error "missing signing key after --local-user option"
      mode="sign"
      signing_key="$2"
      shift 2
      ;;
    --local-user=*)
      mode="sign"
      signing_key="${1#--local-user=}"
      shift
      ;;
    -b|-s|-a|--detach-sign|--sign|--armor)
      mode="sign"
      shift
      ;;
    -)
      shift
      break
      ;;
    --)
      shift
      break
      ;;
    -*)
      exit_with_error "unexpected option: '$1'"
      ;;
    *)
      ## Be tolerant if a caller passes the signing key as a leftover arg.
      if [ "${mode:-}" = "sign" ] && [ -z "${signing_key:-}" ]; then
        signing_key="$1"
        shift
      else
        exit_with_error "unexpected argument: '$1'"
      fi
      ;;
  esac
done

case "$mode" in
  verify)
    [ -n "${sigfile:-}" ] || exit_with_error "no signature file supplied"

    ## Git provides the signed payload on stdin.
    ##
    ## LIMITATION: This wrapper's verify mode is inherently weaker than
    ## 'sq-git log' for policy-based verification. git's gpg.openpgp.program
    ## interface only passes a raw signature file and payload -- it does NOT
    ## pass the commit hash. Therefore this wrapper cannot call 'sq-git log'
    ## (which needs a commit ref and natively understands openpgp-policy.toml).
    ## Policy-based submodule verification is handled separately by
    ## 'help-steps/git_sanity_test --mode submodules' using
    ## 'sq-git log' directly.
    ##
    ## Not using '--signer-email "$DEBEMAIL"':
    ## - Verification will pass if the key is in sq keystore and trusted,
    ##   which is useful for developers 'git verify-commit' or 'git merge'.
    cmd=( "$sq_bin" verify --signature-file "$sigfile" - )

    log "${cmd[*]}"

    if "${cmd[@]}" >/dev/null; then
      printf '%s\n' \
        '[GNUPG:] NEWSIG' \
        '[GNUPG:] GOODSIG UNKNOWN signer-unavailable-via-sq-git-wrapper' \
        '[GNUPG:] TRUST_FULLY 0' >&"$status_fd"
      exit 0
    else
      printf '%s\n' \
        '[GNUPG:] NEWSIG' \
        '[GNUPG:] ERRSIG UNKNOWN 0 0 00 0 1 UNKNOWN' >&"$status_fd"
      log "ERROR: '$sq_bin' verify failed"
      exit 1
    fi
    ;;

  sign)
    [ -n "${signing_key:-}" ] || exit_with_error "no signing key supplied"

    cmd=( "$sq_bin" sign )

    case "$signing_key" in
      *'<'*'>'*)
        cmd+=( --signer-userid "$signing_key" )
        ;;
      *@*)
        cmd+=( --signer-email "$signing_key" )
        ;;
      *[[:space:]]*)
        cmd+=( --signer-userid "$signing_key" )
        ;;
      *)
        cmd+=( --signer "$signing_key" )
        ;;
    esac

    ## Detached signature to stdout, payload from stdin.
    cmd+=( --signature-file=- - )

    log "${cmd[*]}"

    if "${cmd[@]}"; then
      ## Git expects GnuPG status on stderr for signing.
      printf '[GNUPG:] SIG_CREATED D 1 0 00 0 0 0\n' >&"$status_fd"
      exit 0
    else
      printf '[GNUPG:] FAILURE sign 1\n' >&"$status_fd"
      log "ERROR: '$sq_bin' sign failed for signer: $signing_key"
      exit 1
    fi
    ;;

  *)
    exit_with_error "could not detect sign or verify mode"
    ;;
esac
