Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
72c5d21
adds SSH signature validation for git commits
bb-Ricardo Feb 26, 2026
ac65ad4
adds validation of key fingerprint string
bb-Ricardo Feb 27, 2026
ef179c7
adds better git signature detection format
bb-Ricardo Feb 27, 2026
d2d7d48
adds detection for added signature prefixes
bb-Ricardo Feb 27, 2026
fff526a
adds tests for git.go
bb-Ricardo Feb 27, 2026
a0d3a0f
adds requested changes regarding naming
bb-Ricardo Feb 28, 2026
215a8f6
cleans up tests and removed duplicate test files
bb-Ricardo Mar 2, 2026
9683c6f
fixes text fixtures public key names
bb-Ricardo Mar 10, 2026
4f844e4
adds 'SignatureTypeEmpty' check and improves description of 'GetSigna…
bb-Ricardo Mar 10, 2026
8e1ae7c
Update git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh
bb-Ricardo Mar 19, 2026
b78fdf1
removes insecure DSA keys from signature test
bb-Ricardo Mar 19, 2026
54214d5
updates git go.mod dependencies
bb-Ricardo Apr 27, 2026
36fc961
fixes tests for unsigned tags
bb-Ricardo May 8, 2026
5e870f6
renames package to signature and fixes requested changes
bb-Ricardo May 18, 2026
2dfb2d1
switches signarture type from public to private
bb-Ricardo May 18, 2026
1661b58
moves buildTag and buildCommitWithRef to internal/build package
bb-Ricardo May 18, 2026
fc4b507
improves signature validation to test all provided keys/keyrings if m…
bb-Ricardo May 19, 2026
71d7b3f
removes gnupg ed448 generated key test fixtures and adds mention to R…
bb-Ricardo May 19, 2026
8322227
removes verfied signers from ssh signature test fixtures
bb-Ricardo May 19, 2026
5a4d56c
updates test fixtures generation scripts and regenerated test fixtures
bb-Ricardo May 19, 2026
44f9b66
fixes text fixtures creation description and adds test for unsigned c…
bb-Ricardo May 19, 2026
7495ab3
adds more test for malfromed signatures
bb-Ricardo May 19, 2026
cf11371
renames VerifyGPG to VerifyPGP
bb-Ricardo May 28, 2026
0e07307
git: fix multi-keyring verify fall-through
hiddeco May 28, 2026
51828ae
git: add sentinel errors for signature verify
hiddeco May 28, 2026
274233b
git: drop PGP MESSAGE armor from detection
hiddeco May 28, 2026
781ed6c
git: correct godoc on signature verify API
hiddeco May 28, 2026
92039d8
git: unexport signature prefix slice vars
hiddeco May 28, 2026
2c910e7
git: move test helpers to internal/testutil
hiddeco May 28, 2026
9966079
git: cover more signature verification cases
hiddeco May 28, 2026
ae4b47b
git: stage fixture scripts via mktemp swap
hiddeco May 28, 2026
656ea35
git: fixes issue with order of imports
bb-Ricardo May 28, 2026
df041d0
git: add Signer alias for go-git interface
hiddeco May 29, 2026
0a67d0d
git: add NewOpenPGPSigner constructor
hiddeco May 29, 2026
c80583d
git: add NewSSHSigner constructor (happy path)
hiddeco May 29, 2026
57fc904
git: handle encrypted SSH signing keys
hiddeco May 29, 2026
ab49529
git: restrict NewSSHSigner to safe algorithms
hiddeco May 29, 2026
d51f3a4
git: migrate CommitOptions.Signer to interface
hiddeco May 29, 2026
96ed375
git: cover commit with OpenPGP signer end-to-end
hiddeco May 29, 2026
15dc78e
git: cover commit with SSH ed25519 signer e2e
hiddeco May 29, 2026
e96c480
git: cover commit with SSH ECDSA P-256 signer e2e
hiddeco May 29, 2026
449eed0
git: assert WithSigner(nil) yields unsigned commit
hiddeco May 29, 2026
762e604
git: document SSH allowlist on NewSSHSigner
hiddeco May 29, 2026
f46ad90
git: expose ErrSSHPassphraseRequired sentinel
hiddeco May 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
# Dependency directories (remove the comment below to include it)
# vendor/

build/
/build/
bin/
testbin/
132 changes: 102 additions & 30 deletions git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ limitations under the License.
package git

import (
"bytes"
"errors"
"fmt"
"strings"
"time"

"github.com/ProtonMail/go-crypto/openpgp"
"github.com/fluxcd/pkg/git/signature"
)

const (
Expand Down Expand Up @@ -81,7 +80,7 @@ type Commit struct {
// Committer is the one performing the commit, might be different from
// Author.
Committer Signature
// Signature is the PGP signature of the commit.
// Signature is the cryptographic signature of the commit (PGP or SSH).
Signature string
// Encoded is the encoded commit, without any signature.
Encoded []byte
Expand Down Expand Up @@ -113,16 +112,47 @@ func (c *Commit) AbsoluteReference() string {
return c.Hash.Digest()
}

// Verify the Signature of the commit with the given key rings.
// It returns the fingerprint of the key the signature was verified
// with, or an error. It does not verify the signature of the referencing
// tag (if present). Users are expected to explicitly verify the referencing
// tag's signature using `c.ReferencingTag.Verify()`
// Verify the PGP signature of the commit with the given key rings. It
// returns the key ID of the openPGP key the signature was verified with,
// or an error. It does not verify the signature of the referencing tag
// (if present); users are expected to explicitly verify the referencing
// tag's signature using c.ReferencingTag.VerifyPGP.
//
// Verify only handles PGP signatures. If the commit is SSH-signed, the
// call will fail with a signature.ErrSignatureFormat error wrapped in
// the returned chain; callers should use [Commit.VerifySSH] for SSH
// signatures.
//
// Deprecated: use [Commit.VerifyPGP] for PGP signatures, or
// [Commit.VerifySSH] for SSH signatures.
func (c *Commit) Verify(keyRings ...string) (string, error) {
fingerprint, err := verifySignature(c.Signature, c.Encoded, keyRings...)
return c.VerifyPGP(keyRings...)
}

// VerifyPGP verifies the PGP signature of the commit with the given key
// rings. It returns the key ID of the openPGP key the signature was
// verified with, or an error. It does not verify the signature of the
// referencing tag (if present); users are expected to explicitly verify
// the referencing tag's signature using c.ReferencingTag.VerifyPGP.
func (c *Commit) VerifyPGP(keyRings ...string) (string, error) {
keyID, err := signature.VerifyPGPSignature(c.Signature, c.Encoded, keyRings...)
if err != nil {
return "", fmt.Errorf("unable to verify Git commit: %w", err)
}
return keyID, nil
}

// VerifySSH verifies the SSH signature of the commit with the given
// authorized keys. It returns the SHA256 fingerprint of the SSH key the
// signature was verified with, or an error. It does not verify the
// signature of the referencing tag (if present); users are expected to
// explicitly verify the referencing tag's signature using
// c.ReferencingTag.VerifySSH.
func (c *Commit) VerifySSH(authorizedKeys ...string) (string, error) {
fingerprint, err := signature.VerifySSHSignature(c.Signature, c.Encoded, authorizedKeys...)
if err != nil {
return "", fmt.Errorf("unable to verify Git commit SSH signature: %w", err)
}
return fingerprint, nil
}

Expand All @@ -144,22 +174,47 @@ type Tag struct {
Name string
// Author is the original author of the tag.
Author Signature
// Signature is the PGP signature of the tag.
// Signature is the cryptographic signature of the tag (PGP or SSH).
Signature string
// Encoded is the encoded tag, without any signature.
Encoded []byte
// Message is the tag message, containing arbitrary text.
Message string
}

// Verify the Signature of the tag with the given key rings.
// It returns the fingerprint of the key the signature was verified
// with, or an error.
// Verify the PGP signature of the tag with the given key rings. It
// returns the key ID of the openPGP key the signature was verified with,
// or an error.
//
// Verify only handles PGP signatures. If the tag is SSH-signed, the call
// will fail with a signature.ErrSignatureFormat error wrapped in the
// returned chain; callers should use [Tag.VerifySSH] for SSH signatures.
//
// Deprecated: use [Tag.VerifyPGP] for PGP signatures, or [Tag.VerifySSH]
// for SSH signatures.
func (t *Tag) Verify(keyRings ...string) (string, error) {
fingerprint, err := verifySignature(t.Signature, t.Encoded, keyRings...)
return t.VerifyPGP(keyRings...)
}

// VerifyPGP verifies the PGP signature of the tag with the given key
// rings. It returns the key ID of the openPGP key the signature was
// verified with, or an error.
func (t *Tag) VerifyPGP(keyRings ...string) (string, error) {
keyID, err := signature.VerifyPGPSignature(t.Signature, t.Encoded, keyRings...)
if err != nil {
return "", fmt.Errorf("unable to verify Git tag: %w", err)
}
return keyID, nil
}

// VerifySSH verifies the SSH signature of the tag with the given
// authorized keys. It returns the SHA256 fingerprint of the SSH key the
// signature was verified with, or an error.
func (t *Tag) VerifySSH(authorizedKeys ...string) (string, error) {
fingerprint, err := signature.VerifySSHSignature(t.Signature, t.Encoded, authorizedKeys...)
if err != nil {
return "", fmt.Errorf("unable to verify Git tag SSH signature: %w", err)
}
return fingerprint, nil
}

Expand Down Expand Up @@ -210,21 +265,38 @@ func IsSignedTag(t Tag) bool {
return t.Signature != ""
}

func verifySignature(sig string, payload []byte, keyRings ...string) (string, error) {
if sig == "" {
return "", fmt.Errorf("unable to verify payload as the provided signature is empty")
}
// IsPGPSigned returns true if the commit has a PGP signature.
func (c *Commit) IsPGPSigned() bool {
return signature.IsPGPSignature(c.Signature)
}

for _, r := range keyRings {
reader := strings.NewReader(r)
keyring, err := openpgp.ReadArmoredKeyRing(reader)
if err != nil {
return "", fmt.Errorf("unable to read armored key ring: %w", err)
}
signer, err := openpgp.CheckArmoredDetachedSignature(keyring, bytes.NewBuffer(payload), bytes.NewBufferString(sig), nil)
if err == nil {
return signer.PrimaryKey.KeyIdString(), nil
}
}
return "", fmt.Errorf("unable to verify payload with any of the given key rings")
// IsSSHSigned returns true if the commit has an SSH signature.
func (c *Commit) IsSSHSigned() bool {
return signature.IsSSHSignature(c.Signature)
}

// SignatureType returns the type of the commit signature as a string:
// "openpgp" for PGP signatures, "ssh" for SSH signatures, "x509" for
// S/MIME signatures, "empty" for an absent signature, and "unknown"
// for an unrecognised one.
func (c *Commit) SignatureType() string {
return signature.GetSignatureType(c.Signature)
}

// IsPGPSigned returns true if the tag has a PGP signature.
func (t *Tag) IsPGPSigned() bool {
return signature.IsPGPSignature(t.Signature)
}

// IsSSHSigned returns true if the tag has an SSH signature.
func (t *Tag) IsSSHSigned() bool {
return signature.IsSSHSignature(t.Signature)
}

// SignatureType returns the type of the tag signature as a string:
// "openpgp" for PGP signatures, "ssh" for SSH signatures, "x509" for
// S/MIME signatures, "empty" for an absent signature, and "unknown"
// for an unrecognised one.
func (t *Tag) SignatureType() string {
return signature.GetSignatureType(t.Signature)
}
Loading
Loading