Skip to content

refactor(actions): step 12, move gpg-verify-trust-chain checks into a script - #58

Open
seantronsen wants to merge 5 commits into
seantronsen/feat/sarif-govulncheckfrom
seantronsen/feat/refactor-gpg-chain-verify
Open

seantronsen wants to merge 5 commits into
seantronsen/feat/sarif-govulncheckfrom
seantronsen/feat/refactor-gpg-chain-verify

Conversation

@seantronsen

@seantronsen seantronsen commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Description

Moves gpg-verify-trust-chain's checks out of the action's inline run: into scripts/gpg-verify-trust-chain.sh, so they can run outside Actions. The distributor (central repo for #43) will fetch the script by commit SHA and call it directly, which lets its admit job verify, download, and check a release in one script instead of three phases wrapped around the action.

  • scripts/gpg-verify-trust-chain.sh: the action's logic, unchanged. Takes the inputs as environment variables (MASTER_PUBLIC_KEY, MASTER_FINGERPRINT, REPO_KEY_FILE, SIGNING_KEY_FILE, FILES, REPO); unset ones default to the action's defaults, explicitly empty ones still fail as before. On success it appends fingerprint-{master,repo,signing}=<fpr> to OUTPUT_FILE (default stdout). Needs gpg; installs stay in the action.
  • actions/gpg-verify-trust-chain/action.yml: the verify step now calls the script with OUTPUT_FILE="$GITHUB_OUTPUT". Inputs, outputs, and the GnuPG install step are unchanged.
  • Action README: points to the script.
  • gpg-sign-artifacts: repins the action from the orphaned b95f3bd to the refactored one.

Stacked on #53.

Behavior changes

  • Two error messages name the environment variables instead of the action inputs (MASTER_PUBLIC_KEY / MASTER_FINGERPRINT instead of public-key-master / fingerprint-master). Nothing else changes for callers.

Testing

Testbed: OpenCHAMI/github-actions-testbed#17 (sign on a v* tag push).

Locally in ubuntu:24.04 with a real master -> repo -> signing key chain: a valid chain passes and writes all three fingerprints; a wrong master fingerprint, a wrong REPO, and a tampered checksums.txt each fail with the expected error. The action's new run line, with GITHUB_ACTION_PATH / GITHUB_OUTPUT simulated, passes and writes the outputs; an empty FILES fails. Not yet run in Actions; gpg-sign-artifacts exercises the action on a v* tag push in the testbed.

Checklist

  • My code follows the style guidelines of this project
  • I have added/updated comments where needed
  • I have added tests that prove my fix is effective or my feature works
  • I have run make test (or equivalent) locally and all tests pass
  • I have updated the relevant documentation (CLI examples, man pages, README, other docs, etc.)
  • DCO Sign-off: All commits are signed off (git commit -s) with my real name and email
  • REUSE Compliance:
    • Each new/modified source file has SPDX copyright and license headers
    • Any non-commentable files include a <filename>.license sidecar
    • All referenced licenses are present in the LICENSES/ directory

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Dependency update
  • Build system/CI

@seantronsen seantronsen self-assigned this Oct 26, 2026
@seantronsen
seantronsen changed the base branch from main to seantronsen/feat/sarif-govulncheck October 26, 2026 19:26
@seantronsen seantronsen changed the title Seantronsen/feat/refactor gpg chain verify refactor(actions): step 12, move gpg-verify-trust-chain checks into a script Oct 26, 2026
@seantronsen seantronsen added this to the v4.0.0 milestone Oct 26, 2026
@seantronsen
seantronsen added this pull request to stack #40 October 26, 2026 19:26
@seantronsen
seantronsen marked this pull request as ready for review October 26, 2026 19:42
fi

# shellcheck disable=SC2086 # intentional word splitting
set -- $FILES

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a quick question: are we purposefully supporting glob expansion here?

E.g. FILES='checksums[1].txt' expanding into checksums1.txt.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seantronsen
seantronsen force-pushed the seantronsen/feat/refactor-gpg-chain-verify branch from 4dc5bc0 to a696a31 Compare November 2, 2026 21:08
@seantronsen
seantronsen force-pushed the seantronsen/feat/refactor-gpg-chain-verify branch from a696a31 to 2d9aa88 Compare November 2, 2026 21:34
Comment thread scripts/gpg-verify-trust-chain.sh Outdated
gpg --batch --with-colons --check-sigs "$subject" 2>/dev/null \
| awk -F: -v id="${signer_id^^}" -v prefix="${prefix,,}" '
$1=="uid" { name=tolower($10); ok=(prefix=="" || name==prefix || index(name, prefix " ")==1) }
$1=="sig" && $2=="!" && ok && toupper($5)==id { found=1 }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved from #50 (#50 (comment)), originally posted by @synackd:


With this implementation, revoked and expired certifications still pass verification.

certified_by() currently accepts any cryptographically valid certification from the expected issuer:

$1=="sig" && $2=="!" && ok && toupper($5)==id { found=1 }

GPG's ! does not establish that the certification is still effective. sig:! proves the certification's cryptographic validity, but the code ignores certification expiry and revocation records. Expired certifications remain sig:!, and revoked certifications remain present alongside a rev:! record. usable() checks the primary keys, so it does not catch either case. Consequently, a repo key whose authorization the root has revoked can still pass release admission.

I reproduced this with isolated keys using GPG 2.2.41:

valid-chain                 exit=0
expired-cert                exit=0   # expected failure
revoked-unexpired-cert      exit=0   # expected failure
wrong-repo-control          exit=1
tampered-file-control       exit=1

For the revocation case, all keys were unexpired. After the root revoked its certification using:

gpg --batch --yes --local-user "$master_fpr" \
  --quick-revoke-sig "$repo_fpr" "$master_fpr"

--check-sigs emitted both records:

rev:!::22:F36D951D3599FDDF:1793059097:...
sig:!::22:F36D951D3599FDDF:1793059096:...

Certification expiry and revocation should be checked per-UID and per-issuer, for both links in the chain. An awk fix prototype:

# Pass the full expected issuer fingerprint and the current time.
awk -F: -v issuer="${1^^}" -v prefix="${3,,}" \
  -v now="$(date +%s)" '
  function finish_uid() {
    if (eligible && cert_time > rev_time)
      found = 1
  }

  $1 == "uid" {
    finish_uid()
    cert_time = rev_time = 0
    name = tolower($10)
    eligible = ($2 != "r" && $2 != "e") &&
      (prefix == "" || name == prefix ||
       index(name, prefix " ") == 1)
    next
  }

  $1 == "pub" || $1 == "sub" {
    finish_uid()
    eligible = 0
    cert_time = rev_time = 0
    next
  }

  eligible && $2 == "!" && toupper($13) == issuer {
    if ($1 == "rev" && $11 ~ /^30/ && $6 > rev_time)
      rev_time = $6 + 0

    if ($1 == "sig" && $11 ~ /^1[0-3]/ &&
        $6 <= now && ($7 == "" || $7 == 0 || $7 > now) &&
        $6 > cert_time)
      cert_time = $6 + 0
  }

  END {
    finish_uid()
    exit !found
  }
'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signed-off-by: Sean Tronsen <sean.tronsen@gmail.com>
Signed-off-by: Sean Tronsen <sean.tronsen@gmail.com>
@seantronsen
seantronsen force-pushed the seantronsen/feat/refactor-gpg-chain-verify branch from 28d312e to d0e9585 Compare November 2, 2026 22:56
Signed-off-by: Sean Tronsen <sean.tronsen@gmail.com>
Signed-off-by: Sean Tronsen <sean.tronsen@gmail.com>
@seantronsen
seantronsen force-pushed the seantronsen/feat/refactor-gpg-chain-verify branch from 106d615 to b20b294 Compare November 2, 2026 23:32
Signed-off-by: Sean Tronsen <sean.tronsen@gmail.com>
@seantronsen
seantronsen requested a review from synackd November 2, 2026 23:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants