Skip to content

Commit 394fe64

Browse files
committed
adds SSH signature validation for git commits
- adds new package git/signatures - adds validation of SSH signed commits to ssh_signature.go - moves GPG signature validation to gpg_signature.go - adds text fixtures for all SSH and GPG key types including commits and signatures - adds tests for all key/signature combinations - adds wrapper for "Verify(keyRings ...string)" function Signed-off-by: Ricardo Bartels <ricardo.bartels@telekom.de>
1 parent 07d627d commit 394fe64

78 files changed

Lines changed: 4280 additions & 180 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

git/git.go

Lines changed: 72 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@ limitations under the License.
1717
package git
1818

1919
import (
20-
"bytes"
2120
"errors"
2221
"fmt"
2322
"strings"
2423
"time"
2524

26-
"github.com/ProtonMail/go-crypto/openpgp"
25+
"github.com/fluxcd/pkg/git/signatures"
2726
)
2827

2928
const (
@@ -113,19 +112,38 @@ func (c *Commit) AbsoluteReference() string {
113112
return c.Hash.Digest()
114113
}
115114

115+
// wrapper function to ensure backwards compatibility
116+
func (c *Commit) Verify(keyRings ...string) (string, error) {
117+
return c.VerifyGPG(keyRings...)
118+
}
119+
116120
// Verify the Signature of the commit with the given key rings.
117121
// It returns the fingerprint of the key the signature was verified
118122
// with, or an error. It does not verify the signature of the referencing
119123
// tag (if present). Users are expected to explicitly verify the referencing
120124
// tag's signature using `c.ReferencingTag.Verify()`
121-
func (c *Commit) Verify(keyRings ...string) (string, error) {
122-
fingerprint, err := verifySignature(c.Signature, c.Encoded, keyRings...)
125+
func (c *Commit) VerifyGPG(keyRings ...string) (string, error) {
126+
fingerprint, err := signatures.VerifyPGPSignature(c.Signature, c.Encoded, keyRings...)
123127
if err != nil {
124128
return "", fmt.Errorf("unable to verify Git commit: %w", err)
125129
}
126130
return fingerprint, nil
127131
}
128132

133+
// VerifySSH verifies the SSH signature of the commit with the given authorized keys.
134+
// It returns the fingerprint of the key the signature was verified with, or an error.
135+
// It does not verify the signature of the referencing tag (if present). Users are
136+
// expected to explicitly verify the referencing tag's signature using `c.ReferencingTag.VerifySSH()`
137+
func (c *Commit) VerifySSH(authorizedKeys ...string) (string, error) {
138+
// The Encoded field already contains the commit data without the signature
139+
// (it was encoded using EncodeWithoutSignature in BuildCommitWithRef)
140+
fingerprint, err := signatures.VerifySSHSignature(c.Signature, c.Encoded, authorizedKeys...)
141+
if err != nil {
142+
return "", fmt.Errorf("unable to verify Git commit SSH signature: %w", err)
143+
}
144+
return fingerprint, nil
145+
}
146+
129147
// ShortMessage returns the first 50 characters of a commit subject.
130148
func (c *Commit) ShortMessage() string {
131149
subject := strings.Split(c.Message, "\n")[0]
@@ -152,17 +170,34 @@ type Tag struct {
152170
Message string
153171
}
154172

173+
// wrapper function to ensure backwards compatibility
174+
func (t *Tag) Verify(keyRings ...string) (string, error) {
175+
return t.VerifyGPG(keyRings...)
176+
}
177+
155178
// Verify the Signature of the tag with the given key rings.
156179
// It returns the fingerprint of the key the signature was verified
157180
// with, or an error.
158-
func (t *Tag) Verify(keyRings ...string) (string, error) {
159-
fingerprint, err := verifySignature(t.Signature, t.Encoded, keyRings...)
181+
func (t *Tag) VerifyGPG(keyRings ...string) (string, error) {
182+
fingerprint, err := signatures.VerifyPGPSignature(t.Signature, t.Encoded, keyRings...)
160183
if err != nil {
161184
return "", fmt.Errorf("unable to verify Git tag: %w", err)
162185
}
163186
return fingerprint, nil
164187
}
165188

189+
// VerifySSH verifies the SSH signature of the tag with the given authorized keys.
190+
// It returns the fingerprint of the key the signature was verified with, or an error.
191+
func (t *Tag) VerifySSH(authorizedKeys ...string) (string, error) {
192+
// The Encoded field already contains the tag data without the signature
193+
// (it was encoded using EncodeWithoutSignature in BuildCommitWithRef)
194+
fingerprint, err := signatures.VerifySSHSignature(t.Signature, t.Encoded, authorizedKeys...)
195+
if err != nil {
196+
return "", fmt.Errorf("unable to verify Git tag SSH signature: %w", err)
197+
}
198+
return fingerprint, nil
199+
}
200+
166201
// String returns a short string representation of the tag in the format
167202
// of <name@hash>, for eg: "1.0.0@a0c14dc8580a23f79bc654faa79c4f62b46c2c22"
168203
// If the tag is lightweight, it won't have a hash, so it'll simply return
@@ -210,21 +245,36 @@ func IsSignedTag(t Tag) bool {
210245
return t.Signature != ""
211246
}
212247

213-
func verifySignature(sig string, payload []byte, keyRings ...string) (string, error) {
214-
if sig == "" {
215-
return "", fmt.Errorf("unable to verify payload as the provided signature is empty")
216-
}
248+
// IsPGPSigned returns true if the commit has a PGP signature.
249+
func (c *Commit) IsPGPSigned() bool {
250+
return signatures.IsPGPSignature(c.Signature)
251+
}
217252

218-
for _, r := range keyRings {
219-
reader := strings.NewReader(r)
220-
keyring, err := openpgp.ReadArmoredKeyRing(reader)
221-
if err != nil {
222-
return "", fmt.Errorf("unable to read armored key ring: %w", err)
223-
}
224-
signer, err := openpgp.CheckArmoredDetachedSignature(keyring, bytes.NewBuffer(payload), bytes.NewBufferString(sig), nil)
225-
if err == nil {
226-
return signer.PrimaryKey.KeyIdString(), nil
227-
}
228-
}
229-
return "", fmt.Errorf("unable to verify payload with any of the given key rings")
253+
// IsSSHSigned returns true if the commit has an SSH signature.
254+
func (c *Commit) IsSSHSigned() bool {
255+
return signatures.IsSSHSignature(c.Signature)
256+
}
257+
258+
// SignatureType returns the type of the commit signature as a string.
259+
// It returns "pgp" for PGP signatures, "ssh" for SSH signatures,
260+
// and "unknown" for unrecognized or empty signatures.
261+
func (c *Commit) SignatureType() string {
262+
return signatures.GetSignatureType(c.Signature)
263+
}
264+
265+
// IsPGPSigned returns true if the tag has a PGP signature.
266+
func (t *Tag) IsPGPSigned() bool {
267+
return signatures.IsPGPSignature(t.Signature)
268+
}
269+
270+
// IsSSHSigned returns true if the tag has an SSH signature.
271+
func (t *Tag) IsSSHSigned() bool {
272+
return signatures.IsSSHSignature(t.Signature)
273+
}
274+
275+
// SignatureType returns the type of the tag signature as a string.
276+
// It returns "pgp" for PGP signatures, "ssh" for SSH signatures,
277+
// and "unknown" for unrecognized or empty signatures.
278+
func (t *Tag) SignatureType() string {
279+
return signatures.GetSignatureType(t.Signature)
230280
}

0 commit comments

Comments
 (0)