Skip to content

Fix OpenPGP key-fingerprint panic under GODEBUG=fips140=only - #458

Open
sameerforge wants to merge 3 commits into
carvel-dev:developfrom
sameerforge:topic/sameerkh/fips140-openpgp-armor-panic
Open

Fix OpenPGP key-fingerprint panic under GODEBUG=fips140=only#458
sameerforge wants to merge 3 commits into
carvel-dev:developfrom
sameerforge:topic/sameerkh/fips140-openpgp-armor-panic

Conversation

@sameerforge

@sameerforge sameerforge commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes two runtime panics in vendir's OpenPGP git-signature verification when running under GODEBUG=fips140=only (Go native FIPS 140-3 strict enforcement):

  1. Parsing public keys unconditionally computes a SHA-1 key fingerprint, which panics.
  2. Verifying a signature made with a non-FIPS-approved algorithm (SHA-1, MD5, DSA) panics.

Changes

1. Key-fingerprint panic (pkg/vendir/openpgparmor/armor.go)

Parsing an OpenPGP key via openpgp.ReadArmoredKeyRing computes an RFC 4880 v4 fingerprint using SHA-1 as part of key identification, not as a trust decision. This call is now wrapped in crypto/fips140.WithoutEnforcement, since the fingerprint is only used as a lookup key for later signature verification — it plays no part in verifying or trusting a signature, which continues to be enforced independently.

2. Non-FIPS-approved signature algorithms (pkg/vendir/fetch/git/verification.go)

Before verifying a signature, vendir now inspects its declared hash and public-key algorithm. Under GODEBUG=fips140=only:

  • Default: if either algorithm is not FIPS-approved (SHA-1, MD5, or DSA), vendir returns a clear, actionable error naming the exact non-approved algorithm(s) — CheckArmoredDetachedSignature is never called in this case, so nothing reaches the code path that would otherwise panic.
  • Opt-in — verification.allowLegacySignatures: a new configuration field on DirectoryContentsGitVerification. When set to true for a given git source, a non-approved algorithm is downgraded from an error to a logged warning, and verification proceeds — the actual CheckArmoredDetachedSignature call is wrapped in crypto/fips140.WithoutEnforcement only in this opt-in case, since that's the only scenario where a non-approved algorithm reaches it.
  • Outside strict enforcement, or when the signature already uses an approved algorithm, behavior is fully unchanged.

This addresses the production concern that outright failing on legacy-signed history (SHA-1/DSA-signed commits or tags) would break existing consumers with such history in their tree — they can now opt in deliberately via config, rather than vendir silently trusting or unconditionally rejecting legacy signatures for everyone.

allowLegacySignatures is exposed as a configuration field rather than a CLI flag, so that systems which drive vendir purely through a YAML configuration document (rather than additional CLI arguments) are able to set it.

Testing

  • Unit tests added covering the hash/pubkey-algorithm pre-check and its reject/warn/allow behavior (pkg/vendir/fetch/git/verification_test.go).
  • Existing tests updated/passing for the key-fingerprint fix (pkg/vendir/openpgparmor/armor_test.go).
  • Full suite (go build, go vet, go test) validated both with and without GODEBUG=fips140=only, confirming parity for non-FIPS builds and correct reject/warn behavior under strict enforcement.

Backwards compatibility

No behavior change for any existing configuration:

  • Outside GODEBUG=fips140=only, nothing changes.
  • Under strict enforcement, only signatures using non-FIPS-approved algorithms are affected, and only if verification.allowLegacySignatures is not explicitly set.

Note on kapp-controller usage

kapp-controller (a primary downstream consumer of vendir) invokes vendir as a subprocess, driving it entirely through a generated YAML configuration piped over stdin (vendir sync -f - --lock-file /dev/null) — it has no mechanism for passing additional CLI flags, which is why allowLegacySignatures is exposed as a configuration field here rather than a flag.

We also confirmed that kapp-controller does not currently set verification at all when building the git configuration it passes to vendir. Its AppFetchGit API type has no signature-verification concept today, so the code path this PR changes — including the previously-panicking behavior — is not reachable through any App or PackageInstall resource in existing kapp-controller deployments. Wiring up verification support (and, by extension, allowLegacySignatures) in kapp-controller would be net-new work, tracked separately from this PR.

@sameerforge
sameerforge force-pushed the topic/sameerkh/fips140-openpgp-armor-panic branch 12 times, most recently from 47a44b0 to 6273fcb Compare August 14, 2026 10:24
@sameerforge

Copy link
Copy Markdown
Contributor Author

@joaopapereira Please review the PR.

@joaopapereira joaopapereira 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.

I do have some doubts about the implementation here and the removal of the FIPS check

Comment thread pkg/vendir/openpgparmor/armor_test.go Outdated
Comment on lines +36 to +42
keys, err := ReadArmoredKeys(testPublicKey)
if err != nil {
t.Fatalf("ReadArmoredKeys: %s", err)
}
if len(keys) != 1 {
t.Fatalf("expected 1 key, got %d", len(keys))
}

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.

we should be using require.Error() and require.Len() in here instead of t.Fatalf because that is the pattern we have been using throughout carvel

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.

updated to use require.NoError and require.Len to match the pattern used elsewhere in carvel. Thanks!

Comment thread pkg/vendir/openpgparmor/armor.go Outdated
// as a key identifier for later signature lookups (see
// fetch/git/verification.go); it is not used to verify or trust
// any signature, so relaxing FIPS enforcement for just this parse
// is safe. The signature check itself is unaffected and stays enforced.

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.

That is not true given the companion change we are wrapping the signature check in WithoutEnforcement too. The comment should be corrected so it doesn't mislead a future reader into thinking verification remains FIPS-enforced.

@sameerforge sameerforge Aug 18, 2026

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.

You're right, fixed the comment so it accurately reflects that enforcement is relaxed around both operations, scoped narrowly rather than implying verification stays fully enforced.


Comment thread pkg/vendir/fetch/git/verification.go Outdated
Comment on lines +56 to +62
// Verifying a signature made with non-FIPS-approved
// algorithms panics under GODEBUG=fips140=only.
// Wrapping this call in WithoutEnforcement allows signature
// verification of legacy signed commits/tags without panicking.
fips140.WithoutEnforcement(func() {
_, err = openpgp.CheckArmoredDetachedSignature(publicKeys, target, sig)
})

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.

This change in itself is a big complicated, because here we are saying that we will no longer verify, if in a FIPS environment, the keys are good or not. Also not sure if we can consider this backwards compatible.
We could do something like parsing the signature packet, read its hash algorithm, and if it's non-FIPS (SHA-1/MD5) return a clear error; otherwise verify normally under enforcement?
Another option would be for us to try to recover the panic and just get an error out, not the greatest option but that would be an option as well

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.

I think erroring out isn't viable — it would break vendir sync for existing repos with older SHA-1/DSA-signed commits/tags, which is a real production concern for consumers.

So we added a pre-check (hash + public-key algorithm), but instead of erroring, we log a warning naming the non-approved algorithm and let verification proceed. Verification itself is unchanged — the signature/key is still checked and a bad match still fails; we only wrap the call in WithoutEnforcement so it doesn't panic on the non-approved primitive. Outside fips140=only this is a no-op, so it's backwards compatible.

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.

Thanks for explaining the production concern, that's legitimate and I don't want to make life harder for people with legacy signed history for no reason.
But I don't think warn-and-proceed should be the default here. The algorithm pre-check you already added is doing the hard part, so let's just use it to return an error instead of a log warning, and skip the CheckArmoredDetachedSignature call entirely when the algorithm isn't FIPS-approved. That means we don't need WithoutEnforcement around the actual verify call at all, only around the key-parsing/fingerprint step in armor.go, which I'm fine with since that's just a lookup id and not a trust decision.
That gets us a clean, actionable error under fips140=only instead of a panic or a silent pass that's easy to miss in CI. It also matches what someone turning that flag on is actually asking for. I'd rather fail loudly by default than have vendir quietly trust a SHA-1/DSA signature in a mode whose whole point is to disallow that.
For your production concern, let's add an explicit opt-out, something like --allow-legacy-signatures or a config field, so consumers who know they need to keep syncing repos with older signatures can do that on purpose instead of us deciding it for everyone.
Can you take a pass at reworking it this way? Should be a small change from what's already there, happy to re-review quickly once it's up.

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.

Done — reworked as suggested. The algorithm pre-check now returns an error by default and short-circuits before CheckArmoredDetachedSignature is ever called, so non-approved signatures never reach the verify call at all. WithoutEnforcement is gone from the default path entirely; it's now only used inside the new opt-in (when a caller explicitly sets it), since that's the one case where we do need to let a non-approved algorithm through to verify. Went with a config field (verification.allowLegacySignatures) over a CLI flag for the opt-out, since it composes better with how some of our consumers drive vendir purely through YAML config rather than extra CLI args.

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.

Note : kapp-controller (a primary consumer of vendir) invokes vendir as a subprocess, driving it entirely through a generated YAML configuration piped over stdin (vendir sync -f - --lock-file /dev/null) — it has no mechanism for passing additional CLI flags, which is why allowLegacySignatures is exposed as a configuration field here rather than a flag.

We also confirmed that kapp-controller does not currently set verification at all when building the git configuration it passes to vendir. Its AppFetchGit API type has no signature-verification concept today, so the code path this PR changes — including the previously-panicking behavior — is not reachable through any App or PackageInstall resource in existing kapp-controller deployments. Wiring up verification support (and, by extension, allowLegacySignatures) in kapp-controller would be net-new work, tracked separately from this PR.

@github-project-automation github-project-automation Bot moved this to In Progress in Carvel Aug 15, 2026
@sameerforge
sameerforge force-pushed the topic/sameerkh/fips140-openpgp-armor-panic branch 2 times, most recently from 5d136b7 to 1b7539c Compare August 18, 2026 06:36
Parsing public keys via openpgp.ReadArmoredKeyRing unconditionally
computes an RFC 4880 V4 key fingerprint using SHA-1, which panics
under GODEBUG=fips140=only before any signature is ever checked.

The fingerprint is used only as a key identifier for later signature
lookups; it plays no part in verifying or trusting a signature.
Wrap the parsing call in crypto/fips140.WithoutEnforcement so key
parsing succeeds under strict FIPS enforcement without weakening
signature verification, which continues to enforce FIPS independently.

Adds a unit test covering key parsing under GODEBUG=fips140=only.

Signed-off-by: Sameer <sameer.khan@broadcom.com>
@sameerforge
sameerforge force-pushed the topic/sameerkh/fips140-openpgp-armor-panic branch from 1b7539c to 8f90ad1 Compare August 18, 2026 06:38
sameerforge and others added 2 commits August 21, 2026 15:21
Verifying a signature made with a non-FIPS-approved hash or
public-key algorithm (SHA-1, MD5, DSA) panics under
GODEBUG=fips140=only. Under strict enforcement, reject such
signatures outright by default, with an error naming the exact
non-approved algorithm(s) and pointing at the new
verification.allowLegacySignatures configuration field.

Add verification.allowLegacySignatures to
DirectoryContentsGitVerification for consumers who need to keep
syncing repositories with legacy-signed commits/tags under FIPS
enforcement: when set, a non-approved algorithm only logs a warning
instead of failing, and the actual CheckArmoredDetachedSignature
call is wrapped in crypto/fips140.WithoutEnforcement (only reached
in this opt-in case, since it would otherwise panic on the same
non-approved algorithm). Outside of strict enforcement, or when the
signature already uses an approved algorithm, behavior is unchanged
and WithoutEnforcement is never invoked.

This is exposed as a configuration field rather than a CLI flag so
that systems driving vendir purely through a YAML configuration
document (rather than additional CLI arguments) can set it.

Adds unit tests covering the hash/pubkey-algorithm pre-check and its
reject/warn behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Sameer <sameer.khan@broadcom.com>
- Update Go version to 1.26.5 in go.mod
- Bump carvel.dev/imgpkg dependency to v0.48.1
- Update golangci-lint to align with Go version
- Run `go mod tidy` and `go mod vendor` to update go.sum and vendor/

Signed-off-by: Sameer <sameer.khan@broadcom.com>
@sameerforge
sameerforge force-pushed the topic/sameerkh/fips140-openpgp-armor-panic branch from 8f90ad1 to 2517fc5 Compare August 21, 2026 10:07
@sameerforge

Copy link
Copy Markdown
Contributor Author

@joaopapereira Updated the PR as per you review comments. PTAL!

@sameerforge

Copy link
Copy Markdown
Contributor Author

@joaopapereira Please review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

3 participants