Skip to content

feat(verify): KMS/public-key bundle verification (aicr verify --key)#1238

Merged
lockwobr merged 1 commit into
mainfrom
feat/verify-key
Jun 9, 2026
Merged

feat(verify): KMS/public-key bundle verification (aicr verify --key)#1238
lockwobr merged 1 commit into
mainfrom
feat/verify-key

Conversation

@lockwobr

@lockwobr lockwobr commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds aicr verify --key <kms-uri-or-pem>, verifying a key-signed bundle attestation against its public key. This is the verification counterpart to #407's KMS-backed signing, accepting a KMS key URI (awskms:// | gcpkms:// | azurekms://) or a local PEM public-key file.

Motivation / Context

Epic #1149 (Closed Supply Chain, V1) requires that every signing mode have a matching verification path in the same environment. KMS-backed signing (#407) shipped, but aicr verify could only verify keyless bundles against the public-good trusted root, so a KMS-signed bundle was unverifiable by the toolchain. This closes that asymmetry for the key-based path.

Fixes: #1152
Related: #1149 (epic), #407 (KMS signing, the counterpart), #1153 / #1154 (private trust root / offline verify, future siblings), #1214 (KMS integration test)

Type of Change

  • New feature (non-breaking change that adds functionality)
  • Refactoring (no functional changes) — keyless verification routed through the new composer with behavior preserved

Component(s) Affected

  • CLI (cmd/aicr, pkg/cli)
  • Bundlers (pkg/bundler, pkg/component/*)
  • Docs/examples (docs/, examples/)

Implementation Notes

Implemented as the verification dual of the signing-side interface composition. Two small interfaces in pkg/bundler/attestationVerificationIdentity (trust anchors + signer-matching policy) and VerifyTransparencyPolicy (tlog requirement) — are composed by VerifyStatementWith, mirroring SigningIdentity × TransparencyPolicySignStatementWith.

  • The existing keyless path was refactored to flow through the same composer; behavior is unchanged (existing tests are the regression gate).
  • keyVerificationIdentity combines the public-good trusted root (so the Rekor tlog still verifies, since KMS signing uploads by default) with the caller's public key, using verify.WithKey(). KMS public-key resolution is shared with the signing path via an extracted resolveKMSPublicKey; local PEM is loaded with a bounded read against defaults.MaxPublicKeyPEMBytes.
  • --key swaps only the bundle attestation verification. The binary attestation still runs and stays NVIDIA-keyless-pinned, so --key and --certificate-identity-regexp coexist (they verify different attestations). This diverges from the issue's literal "mutually exclusive" wording; rationale is in the issue thread.
  • No-downgrade is enforced by sigstore-go itself: a Fulcio-cert bundle cannot verify under WithKey() ("expected key signature, not certificate").
  • Private trust root (feat(verify): private Sigstore trust root for bundle verification #1153) and offline/skip-tlog (feat(verify): offline / air-gapped verification (skip tlog) #1154) slot in later as additional implementations of these two interfaces, with no new branching in the verifier.

Testing

make test-coverage   # full -short unit suite, race
make lint            # golangci-lint + yamllint + vet + license + agents-sync
  • make test-coverage: full unit suite green, module total 75.7% ≥ 75% threshold.
  • make lint: golangci-lint 0 issues, vet / license headers / AGENTS.md-sync clean.
  • New exported funcs all covered (no 0%). Per-package: pkg/bundler/attestation 78.3% → 76.0% (the uncovered lines are live-KMS resolution and the sigstore crypto-success path, which require live KMS + Rekor and are covered by integration test Integration-test KMS-backed bundle signing via LocalStack (AWS) #1214); pkg/bundler/verifier 64.0% → 65.1%.
  • Not run locally (require infra; left to CI): e2e (KWOK/kind cluster) and grype scan.

Risk Assessment

  • Medium — Touches the bundle-verification core across pkg/bundler/{attestation,verifier} and pkg/cli.

Rollout notes: Additive and backwards compatible. The keyless verification path is preserved (refactored through the new composer, behavior unchanged); --key is opt-in. No migration required.

Checklist

  • Tests pass locally (make test with -race)
  • Linter passes (make lint)
  • I did not skip/disable tests to make CI green
  • I added/updated tests for new functionality
  • I updated docs if user-facing behavior changed
  • Changes follow existing patterns in the codebase
  • Commits are cryptographically signed (git commit -S)

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds native verification for key-signed bundles: a verification core (VerifyStatementWith + interfaces), keyless and key-based VerificationIdentity implementations, centralized KMS URI resolution with timeout/error classification, PEM public-key loading with a size cap, verifier wiring toggled by VerifyOptions.Key, CLI flag/docs updates for --key, and unit/integration tests covering KMS/PEM and verification behaviors.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • NVIDIA/aicr#1205: Modifies shared KMS infrastructure and is closely related to KMS URI handling and provider resolution.

Suggested reviewers

  • njhensley
  • mchmarny
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(verify): KMS/public-key bundle verification (aicr verify --key)' directly and concisely summarizes the main feature addition: native KMS/public-key verification in the verify command.
Description check ✅ Passed The PR description is comprehensive and directly related to the changeset, clearly explaining the motivation, implementation approach, and how the feature addresses issue #1152.
Linked Issues check ✅ Passed The PR successfully implements all objectives from issue #1152: adds --key flag supporting KMS URIs and local PEM files, integrates with KMS-backed signing, enables verification of key-signed bundles, and maintains test/lint quality.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing KMS/public-key bundle verification: new verification identity/policy interfaces, key resolution logic, CLI flag integration, test coverage, and documentation updates. No unrelated refactoring or feature creep detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/verify-key

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/bundler/verifier/verifier_test.go (1)

364-375: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Check Close() error for the writable temp file.

f.Close() is currently ignored in the oversized-file test. Please assert the close result so write flush failures don’t get silently dropped.

Proposed fix
-	f.Close()
+	if closeErr := f.Close(); closeErr != nil {
+		t.Fatal(closeErr)
+	}

As per coding guidelines, writable file handles must check Close() errors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/bundler/verifier/verifier_test.go` around lines 364 - 375, In the
oversized-file test in verifier_test.go, don't ignore the result of f.Close():
after the write loop, call err = f.Close() (or closeErr := f.Close()) and assert
it (e.g., t.Fatal or t.Fatalf with the error) so any flush/close failures are
surfaced; locate the writable temp file variable f and replace the bare
f.Close() with an error check that fails the test on non-nil error.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/user/cli-reference.md`:
- Around line 2464-2465: Update the `--key` CLI option description to explicitly
state that using a KMS URI (awskms://, gcpkms://, azurekms://) will perform
network/KMS provider calls to resolve the public key (i.e., verification is not
purely offline), note potential failure modes (network errors, permission/access
denied from the KMS, and unresolved keys) and recommend fallback to a local PEM
file when offline; ensure the same clarification is added for the duplicate
`--key` occurrence noted near the `--format` option so both descriptions mention
network calls and failure modes.

In `@pkg/bundler/attestation/keyverifyidentity.go`:
- Around line 56-74: Thread the parent context into loadPEMPublicKey and enforce
a bounded deadline: change loadPEMPublicKey to accept ctx (e.g.,
loadPEMPublicKey(ctx context.Context, keyRef string)), and from
NewKeyVerificationIdentity call it with a context.WithTimeout(ctx, <reasonable
duration>) and defer cancel(); ensure the timeout-wrapped context is used for
the file open/read so hung NFS/FUSE/pipe I/O is bounded. Also update the other
call site referenced (lines ~111-124) to use the new signature and apply the
same context.WithTimeout pattern; import time and remember to call cancel().
- Around line 48-55: NewKeyVerificationIdentity is incorrectly treating
hashivault:// refs as local PEM paths; update the KMS-scheme handling so
hashivault:// is treated as a KMS URI and routed through the shared KMS
resolution path. Specifically, ensure the KMS detection used by
NewKeyVerificationIdentity (via isKMSURI in kmsidentity.go) recognizes the
"hashivault" scheme, and make any necessary updates to resolveKMSPublicKey (or
its provider mapping) so hashivault:// URIs are resolved via the KMS resolution
seam rather than file-read logic; this will keep signing/verifying parity and
prevent hashivault refs from falling into the PEM code path.

In `@pkg/bundler/attestation/kmsidentity.go`:
- Around line 54-66: isKMSURI currently does a case-sensitive prefix check so
mixed-case KMS URIs (e.g., "GCPKMS://...") are treated as local paths; change
the check to be case-insensitive by normalizing the input (e.g., lowercasing
ref) before comparing against kmsURISchemes in isKMSURI so any scheme case will
match the known schemes (keep references to isKMSURI and kmsURISchemes).

In `@pkg/bundler/attestation/kmspublickey.go`:
- Around line 58-76: The KMS call error paths currently map all errors to
ErrCodeUnavailable; detect context deadline/cancel errors from the kms.Get and
sv.PublicKey failures (e.g., via errors.Is(err, context.DeadlineExceeded) or
errors.Is(err, context.Canceled)) and return errors.WrapWithContext(...,
errors.ErrCodeTimeout, ...) instead of ErrCodeUnavailable for those cases,
preserving the existing message and context map (ctxKeySigningKey: keyURI) for
both the kms.Get error branch and the sv.PublicKey error branch.

In `@pkg/bundler/verifier/verifier.go`:
- Around line 376-379: The readBoundedFile call in verifier.go currently wraps
all errors as ErrCodeInternal (see the data, err := readBoundedFile(bundlePath,
...) block and similar blocks at other locations), which clobbers structured
pkg/errors codes like ErrCodeInvalidRequest; change the error handling to check
whether err already has a structured code (i.e., is a pkg/errors coded error)
and if so return it unchanged, otherwise wrap it with
errors.Wrap(errors.ErrCodeInternal, "failed to read sigstore bundle:
"+bundlePath, err); apply the same pattern to the other bounded-read sites
referenced (lines around 401-404 and 426-429) so existing coded errors are
propagated and only uncoded errors are re-wrapped.

---

Outside diff comments:
In `@pkg/bundler/verifier/verifier_test.go`:
- Around line 364-375: In the oversized-file test in verifier_test.go, don't
ignore the result of f.Close(): after the write loop, call err = f.Close() (or
closeErr := f.Close()) and assert it (e.g., t.Fatal or t.Fatalf with the error)
so any flush/close failures are surfaced; locate the writable temp file variable
f and replace the bare f.Close() with an error check that fails the test on
non-nil error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 14a9f56b-b501-4a3e-b92b-8dfb7e7870fc

📥 Commits

Reviewing files that changed from the base of the PR and between 767f17d and 9b5c02b.

📒 Files selected for processing (16)
  • docs/user/cli-reference.md
  • pkg/bundler/attestation/keylessverifyidentity.go
  • pkg/bundler/attestation/keyverifyidentity.go
  • pkg/bundler/attestation/keyverifyidentity_test.go
  • pkg/bundler/attestation/kmsidentity.go
  • pkg/bundler/attestation/kmspublickey.go
  • pkg/bundler/attestation/kmspublickey_test.go
  • pkg/bundler/attestation/verifying.go
  • pkg/bundler/attestation/verifying_test.go
  • pkg/bundler/attestation/verifytransparency.go
  • pkg/bundler/verifier/verifier.go
  • pkg/bundler/verifier/verifier_test.go
  • pkg/cli/bundle.go
  • pkg/cli/bundle_verify.go
  • pkg/cli/bundle_verify_test.go
  • pkg/defaults/timeouts.go

Comment thread docs/user/cli-reference.md
Comment thread pkg/bundler/attestation/keyverifyidentity.go
Comment thread pkg/bundler/attestation/keyverifyidentity.go
Comment thread pkg/bundler/attestation/kmsidentity.go
Comment thread pkg/bundler/attestation/kmspublickey.go
Comment thread pkg/bundler/verifier/verifier.go
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Coverage Report ✅

Metric Value
Coverage 76.4%
Threshold 75%
Status Pass
Coverage Badge
![Coverage](https://img.shields.io/badge/coverage-76.4%25-green)

Merging this branch changes the coverage (1 decrease, 2 increase)

Impacted Packages Coverage Δ 🤖
github.com/NVIDIA/aicr/pkg/bundler/attestation 76.81% (-1.88%) 👎
github.com/NVIDIA/aicr/pkg/bundler/verifier 64.19% (+0.99%) 👍
github.com/NVIDIA/aicr/pkg/cli 63.79% (+1.41%) 👍
github.com/NVIDIA/aicr/pkg/defaults 100.00% (ø)

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/NVIDIA/aicr/pkg/bundler/attestation/keylessverifyidentity.go 16.67% (+16.67%) 6 (+6) 1 (+1) 5 (+5) 🎉
github.com/NVIDIA/aicr/pkg/bundler/attestation/keyverifyidentity.go 76.92% (+76.92%) 39 (+39) 30 (+30) 9 (+9) 🌟
github.com/NVIDIA/aicr/pkg/bundler/attestation/kmsidentity.go 94.12% (+19.12%) 17 (-3) 16 (+1) 1 (-4) 🎉
github.com/NVIDIA/aicr/pkg/bundler/attestation/kmspublickey.go 65.22% (+65.22%) 23 (+23) 15 (+15) 8 (+8) 🌟
github.com/NVIDIA/aicr/pkg/bundler/attestation/verifying.go 64.86% (+64.86%) 37 (+37) 24 (+24) 13 (+13) 🌟
github.com/NVIDIA/aicr/pkg/bundler/attestation/verifytransparency.go 100.00% (+100.00%) 2 (+2) 2 (+2) 0 🌟
github.com/NVIDIA/aicr/pkg/bundler/verifier/verifier.go 60.11% (+0.82%) 178 (-21) 107 (-11) 71 (-10) 👍
github.com/NVIDIA/aicr/pkg/cli/bundle.go 54.55% (ø) 198 108 90
github.com/NVIDIA/aicr/pkg/cli/bundle_verify.go 74.19% (+36.49%) 62 (+1) 46 (+23) 16 (-22) 🌟
github.com/NVIDIA/aicr/pkg/defaults/timeouts.go 0.00% (ø) 0 0 0

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
docs/user/cli-reference.md (1)

2464-2465: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Update the section summary to remove the “fully offline” contradiction.

The new --key documentation correctly explains KMS/network behavior, but it conflicts with the earlier statement at Line 2450 (“Verification is fully offline — no network calls are made.”). Please align that summary with the current behavior so users don’t get contradictory guidance.

As per coding guidelines, “Document configuration boundaries and failure modes in documentation” and “Clearly distinguish current behavior from future intent in documentation.”

Suggested doc tweak
-Verify the integrity and attestation chain of a bundle. Verification is fully offline — no network calls are made.
+Verify the integrity and attestation chain of a bundle.
+Verification is offline when using local material (for example, a PEM key and a warmed trusted-root cache).
+Using `--key` with a KMS URI may require network calls to the configured KMS provider.

Also applies to: 2507-2510

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/user/cli-reference.md` around lines 2464 - 2465, The summary sentence
that currently claims "Verification is fully offline — no network calls are
made." must be updated to reflect that using --key with KMS URIs (awskms://,
gcpkms://, azurekms://) can perform network calls and may fail due to
network/KMS auth issues; change the summary to state that verification is
offline for local PEM public-key files but will contact KMS for awskms://,
gcpkms:// or azurekms:// keys and document the potential failure modes (network,
auth, KMS unavailability) and how it differs from --certificate-identity-regexp;
apply the same wording change to the corresponding summary elsewhere in the file
that covers lines around the second instance.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/user/cli-reference.md`:
- Around line 2507-2510: Remove the empty line inside the blockquote that starts
with "**`--key` network behavior:**" so there are no blank lines between quoted
paragraphs (fixing markdownlint MD028); edit the blockquote in
docs/user/cli-reference.md to either join the paragraphs without an intervening
blank line or split into two separate blockquotes, ensuring no blank line
remains within any single blockquote.

In `@pkg/bundler/attestation/keyverifyidentity.go`:
- Around line 49-51: Update the exported comment for the key-based
VerificationIdentity to accurately reflect supported KMS schemes: include
"hashivault://" alongside "awskms:// | gcpkms:// | azurekms://" or replace the
explicit list with a generic reference to the shared scheme-detection used by
the --key handling (mentioning keyRef and the key-based VerificationIdentity
factory). Locate the comment around the key-based VerificationIdentity/keyRef
description in keyverifyidentity.go and edit it so maintainers/users aren't
misled about supported schemes.

In `@pkg/bundler/attestation/verifying.go`:
- Around line 124-133: The function loadSigstoreBundleBytes is treating
malformed/invalid caller-supplied bundle bytes as internal errors; change the
error codes used when protojson.Unmarshal fails and when bundle.NewBundle
returns an error from errors.ErrCodeInternal to errors.ErrCodeInvalidRequest so
these are classified as client/request validation failures; update the two
errors created around protojson.Unmarshal and bundle.NewBundle to wrap with
ErrCodeInvalidRequest while keeping the existing messages and error wrapping.

In `@pkg/bundler/verifier/verifier.go`:
- Around line 82-85: Update the VerifyOptions.Key documentation comment in
verifier.go to accurately reflect supported KMS URI schemes: either add
"hashivault://" to the listed schemes (awskms:// | gcpkms:// | azurekms:// |
hashivault://) or replace the hardcoded list with a brief note referencing the
shared KMS detection logic used by the bundler so the comment stays correct if
supported schemes change; modify the comment that describes VerifyOptions.Key to
point to the shared detection/registry or include hashivault:// as appropriate.

---

Duplicate comments:
In `@docs/user/cli-reference.md`:
- Around line 2464-2465: The summary sentence that currently claims
"Verification is fully offline — no network calls are made." must be updated to
reflect that using --key with KMS URIs (awskms://, gcpkms://, azurekms://) can
perform network calls and may fail due to network/KMS auth issues; change the
summary to state that verification is offline for local PEM public-key files but
will contact KMS for awskms://, gcpkms:// or azurekms:// keys and document the
potential failure modes (network, auth, KMS unavailability) and how it differs
from --certificate-identity-regexp; apply the same wording change to the
corresponding summary elsewhere in the file that covers lines around the second
instance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 5458b410-ab0c-4de0-9422-c30907f55797

📥 Commits

Reviewing files that changed from the base of the PR and between 9b5c02b and cc80385.

📒 Files selected for processing (16)
  • docs/user/cli-reference.md
  • pkg/bundler/attestation/keylessverifyidentity.go
  • pkg/bundler/attestation/keyverifyidentity.go
  • pkg/bundler/attestation/keyverifyidentity_test.go
  • pkg/bundler/attestation/kmsidentity.go
  • pkg/bundler/attestation/kmspublickey.go
  • pkg/bundler/attestation/kmspublickey_test.go
  • pkg/bundler/attestation/verifying.go
  • pkg/bundler/attestation/verifying_test.go
  • pkg/bundler/attestation/verifytransparency.go
  • pkg/bundler/verifier/verifier.go
  • pkg/bundler/verifier/verifier_test.go
  • pkg/cli/bundle.go
  • pkg/cli/bundle_verify.go
  • pkg/cli/bundle_verify_test.go
  • pkg/defaults/timeouts.go

Comment thread docs/user/cli-reference.md Outdated
Comment thread pkg/bundler/attestation/keyverifyidentity.go
Comment thread pkg/bundler/attestation/verifying.go
Comment thread pkg/bundler/verifier/verifier.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
pkg/bundler/verifier/verifier_test.go (1)

364-376: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Check the writable file Close() error in the oversized-file test.

At Line 375, f.Close() ignores writeback/flush failure and can mask test setup failures.

Suggested fix
-	f.Close()
+	if closeErr := f.Close(); closeErr != nil {
+		t.Fatal(closeErr)
+	}

As per coding guidelines, “Writable file handles must check Close() error; use closeErr := f.Close() and propagate if prior error is nil”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/bundler/verifier/verifier_test.go` around lines 364 - 376, The test
writes an oversized file but ignores the f.Close() error; update the test in
verifier_test.go to capture and check closeErr after f.Close() (e.g., closeErr
:= f.Close()) and if closeErr != nil and there is no prior fatal error, call
t.Fatal or t.Fatalf to fail the test; reference the os.File variable f and
ensure the Close() error is propagated/checked according to the writable-handle
guideline.

Source: Coding guidelines

pkg/cli/bundle_verify.go (1)

17-24: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject explicit empty --key to prevent silent downgrade to keyless verification.

At Line 133, --key is accepted as-is. If a caller passes an empty value (--key ''), the flow degrades to keyless verification instead of failing fast.

Suggested fix
 import (
 	"context"
 	"encoding/json"
 	"fmt"
 	"io"
 	"log/slog"
 	"path/filepath"
+	"strings"
@@
 	verifyOpts := &verifier.VerifyOptions{}
@@
-	verifyOpts.Key = cmd.String("key")
+	if cmd.IsSet("key") {
+		keyRef := strings.TrimSpace(cmd.String("key"))
+		if keyRef == "" {
+			return errors.New(errors.ErrCodeInvalidRequest, "--key must not be empty")
+		}
+		verifyOpts.Key = keyRef
+	}

Also applies to: 124-134

♻️ Duplicate comments (1)
docs/user/cli-reference.md (1)

2507-2510: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove blank line inside blockquote (markdownlint MD028).

Line 2510 contains a blank line inside the blockquote, which violates markdownlint rule MD028 (no-blanks-blockquote). This is a duplicate of a previous review comment.

📝 Proposed fix
 > **`--key` network behavior:** Resolving a **KMS URI** (`awskms://`, `gcpkms://`, `azurekms://`) makes network calls to the KMS provider to fetch the public key, so credentials for that provider must be available in the environment. A **local PEM** public-key file is read from disk with no provider calls; export it once with `cosign public-key --key <kms-uri>` (or your provider's console) and verify anywhere.
->
 > Note that resolving the key is only part of verification. By default the bundle's Rekor transparency-log entry is also checked. Its inclusion proof is embedded in the bundle, so no live Rekor call is made, but the check needs the Sigstore trusted root: that root is loaded from the local cache when present and otherwise fetched over the network, so run `aicr trust update` once to pre-populate it. A local PEM key therefore makes verification fully offline only when the trusted-root cache is already warm. Verification that drops the transparency-log requirement entirely, for true air-gapped use, is tracked in [`#1154`](https://github.com/NVIDIA/aicr/issues/1154).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/user/cli-reference.md` around lines 2507 - 2510, In the
docs/user/cli-reference.md blockquote that starts with "**`--key` network
behavior:**", remove the blank line inside the blockquote (the empty line after
the first paragraph) so the entire blockquote has no internal blank lines
(resolving markdownlint MD028); locate the paragraph by its opening text and
delete the stray empty line to match the previous corrected instance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/user/cli-reference.md`:
- Around line 2186-2190: The directional reference is wrong: change the phrase
"See the [`aicr verify`](`#aicr-verify`) flags above" to reference the section
below. Update the text in the paragraph referencing the `aicr verify` anchor
(the link text `aicr verify` / anchor `#aicr-verify`) so it says "See the [`aicr
verify`](`#aicr-verify`) flags below" (or reword to "See the [`aicr
verify`](`#aicr-verify`) section" without "above") to accurately point to the
later `aicr verify` section.

In `@pkg/bundler/attestation/keylessverifyidentity.go`:
- Around line 42-49: The TrustedMaterial method on keylessVerificationIdentity
accepts a context but never checks it before calling trust.GetTrustedMaterial;
update the method signature to name the context parameter (e.g., ctx) and
immediately honor cancellation by checking ctx.Err() or selecting on ctx.Done()
and returning a wrapped context.Canceled/Err if cancelled, before invoking
trust.GetTrustedMaterial so you avoid doing work after cancellation; keep the
existing error wrapping behavior for trust.GetTrustedMaterial failures.

In `@pkg/bundler/attestation/keyverifyidentity.go`:
- Around line 56-67: The stored identity in NewKeyVerificationIdentity is using
the caller-provided keyRef (preserving casing) which can cause mismatches; when
isKMSURI(keyRef) is true, canonicalize the URI scheme before saving by passing
keyRef through normalizeURIScheme (or equivalent) and set
keyVerificationIdentity.identity to that normalized value; also ensure the
signing-side NewKMSIdentity uses the same normalizeURIScheme normalization so
both signing (NewKMSIdentity) and verification (NewKeyVerificationIdentity)
store identical canonical URIs.

In `@pkg/bundler/attestation/verifying_test.go`:
- Around line 130-151: Add a positive test case to TestSignerIdentityFromResult
that exercises the successful extraction path: in the tests slice, add an entry
where result is a *verify.VerificationResult containing a non-nil Signature
(verify.SignatureVerificationResult) whose Certificate has a SAN (e.g. DNS or
URI field populated) and set want to the expected SAN string; this will call
signerIdentityFromResult and verify it returns the populated SAN instead of an
empty string. Ensure you reference the existing test harness
(TestSignerIdentityFromResult) and the types verify.VerificationResult,
verify.SignatureVerificationResult, and signerIdentityFromResult when adding the
new case.

In `@pkg/bundler/attestation/verifying.go`:
- Around line 156-166: The current containsCertChainError function is too broad
because the plain "x509" token matches unrelated errors; remove the generic
"x509" entry from staleIndicators and instead only treat x509-related messages
as stale-root when they include a specific certificate chain indicator (e.g.,
require both "x509" and "signed by unknown authority" or another explicit
phrase). Update containsCertChainError (and its loop over staleIndicators) so
the generic "x509" is not matched alone and add a separate conditional that
returns true only when the lowercased error string contains "x509" plus a
specific chain-failure phrase like "signed by unknown authority" to avoid
mislabeling expiry and other non-root issues.

In `@pkg/cli/bundle_verify_test.go`:
- Around line 69-77: The test TestBundleVerifyCmd_KeyFlagPlumbing currently uses
an empty directory so it never reaches the key-verification branch; update the
fixture used in that test (and the similar case at lines 97-113) to point to a
bundle directory that contains valid checksums and includes a bundle attestation
file so verification proceeds to attestation validation, then keep the
verifyOpts.Key = cmd.String("key") plumbing and assert that the returned error
is the key-resolution/verification error (i.e., from resolving/validating the
key) rather than a missing-checksums error; locate the test by
TestBundleVerifyCmd_KeyFlagPlumbing and the verifyOpts.Key assignment to modify
the fixture and adjust the expected error assertions accordingly.

---

Outside diff comments:
In `@pkg/bundler/verifier/verifier_test.go`:
- Around line 364-376: The test writes an oversized file but ignores the
f.Close() error; update the test in verifier_test.go to capture and check
closeErr after f.Close() (e.g., closeErr := f.Close()) and if closeErr != nil
and there is no prior fatal error, call t.Fatal or t.Fatalf to fail the test;
reference the os.File variable f and ensure the Close() error is
propagated/checked according to the writable-handle guideline.

---

Duplicate comments:
In `@docs/user/cli-reference.md`:
- Around line 2507-2510: In the docs/user/cli-reference.md blockquote that
starts with "**`--key` network behavior:**", remove the blank line inside the
blockquote (the empty line after the first paragraph) so the entire blockquote
has no internal blank lines (resolving markdownlint MD028); locate the paragraph
by its opening text and delete the stray empty line to match the previous
corrected instance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 7eec5ac1-8613-4f2b-8b82-1b66fbf68ca6

📥 Commits

Reviewing files that changed from the base of the PR and between cc80385 and 4283df2.

📒 Files selected for processing (16)
  • docs/user/cli-reference.md
  • pkg/bundler/attestation/keylessverifyidentity.go
  • pkg/bundler/attestation/keyverifyidentity.go
  • pkg/bundler/attestation/keyverifyidentity_test.go
  • pkg/bundler/attestation/kmsidentity.go
  • pkg/bundler/attestation/kmspublickey.go
  • pkg/bundler/attestation/kmspublickey_test.go
  • pkg/bundler/attestation/verifying.go
  • pkg/bundler/attestation/verifying_test.go
  • pkg/bundler/attestation/verifytransparency.go
  • pkg/bundler/verifier/verifier.go
  • pkg/bundler/verifier/verifier_test.go
  • pkg/cli/bundle.go
  • pkg/cli/bundle_verify.go
  • pkg/cli/bundle_verify_test.go
  • pkg/defaults/timeouts.go

Comment thread docs/user/cli-reference.md
Comment thread pkg/bundler/attestation/keylessverifyidentity.go
Comment thread pkg/bundler/attestation/keyverifyidentity.go
Comment thread pkg/bundler/attestation/verifying_test.go
Comment thread pkg/bundler/attestation/verifying.go
Comment thread pkg/cli/bundle_verify_test.go
@lockwobr lockwobr enabled auto-merge (squash) June 9, 2026 18:44

@mchmarny mchmarny left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Solid PR — the composer-shaped interface refactor (VerificationIdentity × VerifyTransparencyPolicyVerifyStatementWith) cleanly isolates the key-based path, the keyless path is preserved through the same composer with no behavior change, and the new tests cover the identity surface well (PEM happy path, bad PEM, oversize, missing file, unknown KMS scheme). CI is fully green including all three GPU jobs.

Three minor findings inline; nothing blocks merge. One out-of-diff observation: pkg/cli/bundle_verify.go:138 wraps verifier.Verify's structured return with ErrCodeInternal, flattening the ErrCodeUnauthorized/ErrCodeInvalidRequest codes the verifier already carries — pre-existing on main, worth a separate cleanup but out of scope here.

Comment thread docs/user/cli-reference.md Outdated
Comment thread pkg/bundler/attestation/keyverifyidentity.go Outdated
Comment thread pkg/bundler/attestation/verifying.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/cli/bundle_verify.go (1)

133-139: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve verifier error codes for --key failures.

Line 138 currently reclassifies all verifier.Verify(...) failures as ErrCodeInternal. With --key, user-input failures (bad PEM path, invalid/unsupported key URI) should keep their original structured codes; otherwise CLI exit semantics are wrong for automation.

Based on learnings: if an error is already structured/coded, propagate it as-is and prefer errors.PropagateOrWrap(...) over unconditional re-wrap. As per coding guidelines, keep structured error classification intact.

Proposed fix
 	result, err := verifier.Verify(ctx, absDir, verifyOpts)
 	if err != nil {
-		return errors.Wrap(errors.ErrCodeInternal, "bundle verification failed", err)
+		return errors.PropagateOrWrap(err, errors.ErrCodeInternal, "bundle verification failed")
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/cli/bundle_verify.go` around lines 133 - 139, The current error handling
after calling verifier.Verify(ctx, absDir, verifyOpts) always wraps errors with
errors.ErrCodeInternal, losing structured verifier error codes for --key
failures; change the return to use errors.PropagateOrWrap(err,
errors.ErrCodeInternal, "bundle verification failed") (or the equivalent
PropagateOrWrap API used in the codebase) so that if the verifier returned a
coded/structured error it's propagated as-is, otherwise it is wrapped with
ErrCodeInternal and the "bundle verification failed" message; reference
verifyOpts.Key and verifier.Verify to locate the call site and replace the
unconditional errors.Wrap usage accordingly.

Sources: Coding guidelines, Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@pkg/cli/bundle_verify.go`:
- Around line 133-139: The current error handling after calling
verifier.Verify(ctx, absDir, verifyOpts) always wraps errors with
errors.ErrCodeInternal, losing structured verifier error codes for --key
failures; change the return to use errors.PropagateOrWrap(err,
errors.ErrCodeInternal, "bundle verification failed") (or the equivalent
PropagateOrWrap API used in the codebase) so that if the verifier returned a
coded/structured error it's propagated as-is, otherwise it is wrapped with
ErrCodeInternal and the "bundle verification failed" message; reference
verifyOpts.Key and verifier.Verify to locate the call site and replace the
unconditional errors.Wrap usage accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 3a868c9f-d2af-4a5b-893e-f2bb33312711

📥 Commits

Reviewing files that changed from the base of the PR and between 4283df2 and 728c746.

📒 Files selected for processing (16)
  • docs/user/cli-reference.md
  • pkg/bundler/attestation/keylessverifyidentity.go
  • pkg/bundler/attestation/keyverifyidentity.go
  • pkg/bundler/attestation/keyverifyidentity_test.go
  • pkg/bundler/attestation/kmsidentity.go
  • pkg/bundler/attestation/kmspublickey.go
  • pkg/bundler/attestation/kmspublickey_test.go
  • pkg/bundler/attestation/verifying.go
  • pkg/bundler/attestation/verifying_test.go
  • pkg/bundler/attestation/verifytransparency.go
  • pkg/bundler/verifier/verifier.go
  • pkg/bundler/verifier/verifier_test.go
  • pkg/cli/bundle.go
  • pkg/cli/bundle_verify.go
  • pkg/cli/bundle_verify_test.go
  • pkg/defaults/timeouts.go

#1152)

Add `aicr verify --key <kms-uri-or-pem>` to verify a key-signed bundle
attestation against its public key, the verification counterpart to #407's
KMS-backed signing. Accepts a KMS key URI (awskms:// | gcpkms:// | azurekms://)
or a local PEM public-key file.

Implemented as the verification dual of the signing-side interface composition:
two small interfaces in pkg/bundler/attestation, VerificationIdentity and
VerifyTransparencyPolicy, composed by VerifyStatementWith (mirroring
SigningIdentity x TransparencyPolicy -> SignStatementWith). The keyless path is
refactored to flow through the same composer; a key-based identity combines the
public-good trusted root (so the Rekor tlog still verifies, since KMS signing
uploads by default) with the caller's public key, using verify.WithKey(). KMS
public-key resolution is shared with the signing path via resolveKMSPublicKey.

--key swaps only the bundle-attestation verification; the binary attestation
stays NVIDIA-keyless-pinned and still runs, so --key and
--certificate-identity-regexp coexist (they verify different attestations). The
no-downgrade property is enforced by sigstore-go itself: a Fulcio-cert bundle
cannot verify under WithKey().

Private trust root (#1153) and offline/skip-tlog (#1154) slot in later as
additional implementations of the two interfaces. Successful end-to-end crypto
verification with live KMS + Rekor is covered by integration test #1214.

Fixes #1152

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
pkg/bundler/verifier/verifier_test.go (1)

364-376: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Check Close() error on the writable temp file.

TestReadBoundedFile_OversizedFile writes to f but ignores f.Close() errors, which can mask flush/write failures and make the test outcome misleading.

Suggested fix
-	f.Close()
+	if closeErr := f.Close(); closeErr != nil {
+		t.Fatal(closeErr)
+	}

As per coding guidelines, “Writable file handles must check Close() error; use closeErr := f.Close() and propagate if prior error is nil”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/bundler/verifier/verifier_test.go` around lines 364 - 376, The test
TestReadBoundedFile_OversizedFile currently ignores the error returned by
f.Close(); change the code to capture and check the Close() error (e.g.,
closeErr := f.Close()) and fail the test if Close() returns an error (or
propagate it if a previous error variable is used), ensuring any flush/write
failures on the temp file are detected; update references around the variable f
in verifier_test.go accordingly.

Source: Coding guidelines

pkg/cli/bundle_verify.go (1)

124-134: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject explicit empty --key to prevent silent fallback to keyless verification.

When --key is explicitly set to empty/whitespace, verifyOpts.Key becomes empty and verification silently uses the keyless branch. That can bypass the caller’s intended key-pinned verification mode.

Suggested fix
 import (
 	"context"
 	"encoding/json"
 	"fmt"
 	"io"
 	"log/slog"
 	"path/filepath"
+	"strings"
@@
 	verifyOpts := &verifier.VerifyOptions{}
@@
-	verifyOpts.Key = cmd.String("key")
+	keyRef := strings.TrimSpace(cmd.String("key"))
+	if cmd.IsSet("key") && keyRef == "" {
+		return errors.New(errors.ErrCodeInvalidRequest, "--key must not be empty")
+	}
+	verifyOpts.Key = keyRef
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/cli/bundle_verify.go` around lines 124 - 134, Detect and reject an
explicitly empty or whitespace `--key` instead of letting it silently fall back
to keyless verification: after reading the flag value from cmd.String("key"),
use strings.TrimSpace on that value and if it is empty AND the flag was
explicitly provided (using the CLI's flag-present check, e.g., cmd.IsSet("key")
or equivalent), return an error; only assign verifyOpts.Key when the trimmed
value is non-empty. This change affects the verifyOpts assignment in the
VerifyOptions construction (verifyOpts, VerifyOptions, verifyOpts.Key,
cmd.String("key")).
♻️ Duplicate comments (1)
docs/user/cli-reference.md (1)

2507-2511: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix MD028: blank line inside blockquote.

There is a blank line inside the blockquote (appears to be around line 2509), which triggers MD028 (no-blanks-blockquote). This is the same issue flagged in past review comments.

📝 Proposed fix

Remove the blank > line between the two paragraphs in the blockquote, joining them directly:

 > **`--key` network behavior:** Resolving a **KMS URI** (`awskms://`, `gcpkms://`, `azurekms://`) makes network calls to the KMS provider to fetch the public key, so credentials for that provider must be available in the environment. A **local PEM** public-key file is read from disk with no provider calls; export it once with `cosign public-key --key <kms-uri>` (or your provider's console) and verify anywhere.
->
 > Resolving the key is only part of verification. By default the bundle's Rekor transparency-log entry is also checked. Its inclusion proof is embedded in the bundle, so no live Rekor call is made, but the check needs the Sigstore trusted root: that root is loaded from the local cache when present and otherwise fetched over the network, so run `aicr trust update` once to pre-populate it. A local PEM key therefore makes verification fully offline only when the trusted-root cache is already warm. Verification that drops the transparency-log requirement entirely, for true air-gapped use, is tracked in [`#1154`](https://github.com/NVIDIA/aicr/issues/1154).

Alternatively, split into two separate blockquotes if the logical separation is needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/user/cli-reference.md` around lines 2507 - 2511, The blockquote that
starts with "**`--key` network behavior:**" contains an empty ">" line between
paragraphs triggering MD028; remove that blank blockquote marker (or merge the
two paragraphs into one) so there is no blank ">" line between them, or
alternatively split them into two separate blockquotes if you want distinct
quotes; ensure the resulting text no longer contains an isolated ">" line inside
that blockquote.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@pkg/bundler/verifier/verifier_test.go`:
- Around line 364-376: The test TestReadBoundedFile_OversizedFile currently
ignores the error returned by f.Close(); change the code to capture and check
the Close() error (e.g., closeErr := f.Close()) and fail the test if Close()
returns an error (or propagate it if a previous error variable is used),
ensuring any flush/write failures on the temp file are detected; update
references around the variable f in verifier_test.go accordingly.

In `@pkg/cli/bundle_verify.go`:
- Around line 124-134: Detect and reject an explicitly empty or whitespace
`--key` instead of letting it silently fall back to keyless verification: after
reading the flag value from cmd.String("key"), use strings.TrimSpace on that
value and if it is empty AND the flag was explicitly provided (using the CLI's
flag-present check, e.g., cmd.IsSet("key") or equivalent), return an error; only
assign verifyOpts.Key when the trimmed value is non-empty. This change affects
the verifyOpts assignment in the VerifyOptions construction (verifyOpts,
VerifyOptions, verifyOpts.Key, cmd.String("key")).

---

Duplicate comments:
In `@docs/user/cli-reference.md`:
- Around line 2507-2511: The blockquote that starts with "**`--key` network
behavior:**" contains an empty ">" line between paragraphs triggering MD028;
remove that blank blockquote marker (or merge the two paragraphs into one) so
there is no blank ">" line between them, or alternatively split them into two
separate blockquotes if you want distinct quotes; ensure the resulting text no
longer contains an isolated ">" line inside that blockquote.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: df715a54-77b7-4897-b2f5-0df935a2d9c6

📥 Commits

Reviewing files that changed from the base of the PR and between 728c746 and d87d54f.

📒 Files selected for processing (16)
  • docs/user/cli-reference.md
  • pkg/bundler/attestation/keylessverifyidentity.go
  • pkg/bundler/attestation/keyverifyidentity.go
  • pkg/bundler/attestation/keyverifyidentity_test.go
  • pkg/bundler/attestation/kmsidentity.go
  • pkg/bundler/attestation/kmspublickey.go
  • pkg/bundler/attestation/kmspublickey_test.go
  • pkg/bundler/attestation/verifying.go
  • pkg/bundler/attestation/verifying_test.go
  • pkg/bundler/attestation/verifytransparency.go
  • pkg/bundler/verifier/verifier.go
  • pkg/bundler/verifier/verifier_test.go
  • pkg/cli/bundle.go
  • pkg/cli/bundle_verify.go
  • pkg/cli/bundle_verify_test.go
  • pkg/defaults/timeouts.go

@lockwobr lockwobr merged commit e3460fc into main Jun 9, 2026
34 checks passed
@lockwobr lockwobr deleted the feat/verify-key branch June 9, 2026 21:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(verify): KMS public-key verification (aicr verify --key)

2 participants