Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c65fba8
adds SSH signature validation for git commits
bb-Ricardo Feb 26, 2026
7fa7ba0
adds validation of key fingerprint string
bb-Ricardo Feb 27, 2026
e40355a
adds better git signature detection format
bb-Ricardo Feb 27, 2026
3c4d1e4
adds detection for added signature prefixes
bb-Ricardo Feb 27, 2026
27c8a68
adds tests for git.go
bb-Ricardo Feb 27, 2026
c0075e4
adds requested changes regarding naming
bb-Ricardo Feb 28, 2026
eb8d73b
cleans up tests and removed duplicate test files
bb-Ricardo Mar 2, 2026
9441184
fixes text fixtures public key names
bb-Ricardo Mar 10, 2026
3f787fc
adds 'SignatureTypeEmpty' check and improves description of 'GetSigna…
bb-Ricardo Mar 10, 2026
7ce6c5c
Update git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh
bb-Ricardo Mar 19, 2026
b183696
removes insecure DSA keys from signature test
bb-Ricardo Mar 19, 2026
9031772
updates git go.mod dependencies
bb-Ricardo Apr 27, 2026
b69b404
fixes tests for unsigned tags
bb-Ricardo May 8, 2026
02457e1
renames package to signature and fixes requested changes
bb-Ricardo May 18, 2026
8056671
switches signarture type from public to private
bb-Ricardo May 18, 2026
2b84884
moves buildTag and buildCommitWithRef to internal/build package
bb-Ricardo May 18, 2026
8018f50
improves signature validation to test all provided keys/keyrings if m…
bb-Ricardo May 19, 2026
3e0e191
removes gnupg ed448 generated key test fixtures and adds mention to R…
bb-Ricardo May 19, 2026
dcf6200
removes verfied signers from ssh signature test fixtures
bb-Ricardo May 19, 2026
570faea
updates test fixtures generation scripts and regenerated test fixtures
bb-Ricardo May 19, 2026
45bdf98
fixes text fixtures creation description and adds test for unsigned c…
bb-Ricardo May 19, 2026
a70726c
adds more test for malfromed signatures
bb-Ricardo May 19, 2026
50cc0f1
renames VerifyGPG to VerifyPGP
bb-Ricardo May 28, 2026
2c881b4
git: fix multi-keyring verify fall-through
hiddeco May 28, 2026
843fbcf
git: add sentinel errors for signature verify
hiddeco May 28, 2026
30d95f9
git: drop PGP MESSAGE armor from detection
hiddeco May 28, 2026
cbf288b
git: correct godoc on signature verify API
hiddeco May 28, 2026
3dd2506
git: unexport signature prefix slice vars
hiddeco May 28, 2026
e9ef5b6
git: move test helpers to internal/testutil
hiddeco May 28, 2026
1b84c2a
git: cover more signature verification cases
hiddeco May 28, 2026
ef5d54b
git: stage fixture scripts via mktemp swap
hiddeco May 28, 2026
0c6159f
git: fixes issue with order of imports
bb-Ricardo May 28, 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) {
Comment thread
bb-Ricardo marked this conversation as resolved.
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