Skip to content

Commit 21bc301

Browse files
authored
Merge pull request #1141 from bb-Ricardo/rb-feature/adds-ssh-signature-validation
Add SSH signature verification for git commits
2 parents 6ed3eba + 0c6159f commit 21bc301

75 files changed

Lines changed: 4827 additions & 262 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.

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@
1414
# Dependency directories (remove the comment below to include it)
1515
# vendor/
1616

17-
build/
17+
/build/
1818
bin/
1919
testbin/

git/git.go

Lines changed: 102 additions & 30 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/signature"
2726
)
2827

2928
const (
@@ -81,7 +80,7 @@ type Commit struct {
8180
// Committer is the one performing the commit, might be different from
8281
// Author.
8382
Committer Signature
84-
// Signature is the PGP signature of the commit.
83+
// Signature is the cryptographic signature of the commit (PGP or SSH).
8584
Signature string
8685
// Encoded is the encoded commit, without any signature.
8786
Encoded []byte
@@ -113,16 +112,47 @@ func (c *Commit) AbsoluteReference() string {
113112
return c.Hash.Digest()
114113
}
115114

116-
// Verify the Signature of the commit with the given key rings.
117-
// It returns the fingerprint of the key the signature was verified
118-
// with, or an error. It does not verify the signature of the referencing
119-
// tag (if present). Users are expected to explicitly verify the referencing
120-
// tag's signature using `c.ReferencingTag.Verify()`
115+
// Verify the PGP signature of the commit with the given key rings. It
116+
// returns the key ID of the openPGP key the signature was verified with,
117+
// or an error. It does not verify the signature of the referencing tag
118+
// (if present); users are expected to explicitly verify the referencing
119+
// tag's signature using c.ReferencingTag.VerifyPGP.
120+
//
121+
// Verify only handles PGP signatures. If the commit is SSH-signed, the
122+
// call will fail with a signature.ErrSignatureFormat error wrapped in
123+
// the returned chain; callers should use [Commit.VerifySSH] for SSH
124+
// signatures.
125+
//
126+
// Deprecated: use [Commit.VerifyPGP] for PGP signatures, or
127+
// [Commit.VerifySSH] for SSH signatures.
121128
func (c *Commit) Verify(keyRings ...string) (string, error) {
122-
fingerprint, err := verifySignature(c.Signature, c.Encoded, keyRings...)
129+
return c.VerifyPGP(keyRings...)
130+
}
131+
132+
// VerifyPGP verifies the PGP signature of the commit with the given key
133+
// rings. It returns the key ID of the openPGP key the signature was
134+
// verified with, or an error. It does not verify the signature of the
135+
// referencing tag (if present); users are expected to explicitly verify
136+
// the referencing tag's signature using c.ReferencingTag.VerifyPGP.
137+
func (c *Commit) VerifyPGP(keyRings ...string) (string, error) {
138+
keyID, err := signature.VerifyPGPSignature(c.Signature, c.Encoded, keyRings...)
123139
if err != nil {
124140
return "", fmt.Errorf("unable to verify Git commit: %w", err)
125141
}
142+
return keyID, nil
143+
}
144+
145+
// VerifySSH verifies the SSH signature of the commit with the given
146+
// authorized keys. It returns the SHA256 fingerprint of the SSH key the
147+
// signature was verified with, or an error. It does not verify the
148+
// signature of the referencing tag (if present); users are expected to
149+
// explicitly verify the referencing tag's signature using
150+
// c.ReferencingTag.VerifySSH.
151+
func (c *Commit) VerifySSH(authorizedKeys ...string) (string, error) {
152+
fingerprint, err := signature.VerifySSHSignature(c.Signature, c.Encoded, authorizedKeys...)
153+
if err != nil {
154+
return "", fmt.Errorf("unable to verify Git commit SSH signature: %w", err)
155+
}
126156
return fingerprint, nil
127157
}
128158

@@ -144,22 +174,47 @@ type Tag struct {
144174
Name string
145175
// Author is the original author of the tag.
146176
Author Signature
147-
// Signature is the PGP signature of the tag.
177+
// Signature is the cryptographic signature of the tag (PGP or SSH).
148178
Signature string
149179
// Encoded is the encoded tag, without any signature.
150180
Encoded []byte
151181
// Message is the tag message, containing arbitrary text.
152182
Message string
153183
}
154184

155-
// Verify the Signature of the tag with the given key rings.
156-
// It returns the fingerprint of the key the signature was verified
157-
// with, or an error.
185+
// Verify the PGP signature of the tag with the given key rings. It
186+
// returns the key ID of the openPGP key the signature was verified with,
187+
// or an error.
188+
//
189+
// Verify only handles PGP signatures. If the tag is SSH-signed, the call
190+
// will fail with a signature.ErrSignatureFormat error wrapped in the
191+
// returned chain; callers should use [Tag.VerifySSH] for SSH signatures.
192+
//
193+
// Deprecated: use [Tag.VerifyPGP] for PGP signatures, or [Tag.VerifySSH]
194+
// for SSH signatures.
158195
func (t *Tag) Verify(keyRings ...string) (string, error) {
159-
fingerprint, err := verifySignature(t.Signature, t.Encoded, keyRings...)
196+
return t.VerifyPGP(keyRings...)
197+
}
198+
199+
// VerifyPGP verifies the PGP signature of the tag with the given key
200+
// rings. It returns the key ID of the openPGP key the signature was
201+
// verified with, or an error.
202+
func (t *Tag) VerifyPGP(keyRings ...string) (string, error) {
203+
keyID, err := signature.VerifyPGPSignature(t.Signature, t.Encoded, keyRings...)
160204
if err != nil {
161205
return "", fmt.Errorf("unable to verify Git tag: %w", err)
162206
}
207+
return keyID, nil
208+
}
209+
210+
// VerifySSH verifies the SSH signature of the tag with the given
211+
// authorized keys. It returns the SHA256 fingerprint of the SSH key the
212+
// signature was verified with, or an error.
213+
func (t *Tag) VerifySSH(authorizedKeys ...string) (string, error) {
214+
fingerprint, err := signature.VerifySSHSignature(t.Signature, t.Encoded, authorizedKeys...)
215+
if err != nil {
216+
return "", fmt.Errorf("unable to verify Git tag SSH signature: %w", err)
217+
}
163218
return fingerprint, nil
164219
}
165220

@@ -210,21 +265,38 @@ func IsSignedTag(t Tag) bool {
210265
return t.Signature != ""
211266
}
212267

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-
}
268+
// IsPGPSigned returns true if the commit has a PGP signature.
269+
func (c *Commit) IsPGPSigned() bool {
270+
return signature.IsPGPSignature(c.Signature)
271+
}
217272

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")
273+
// IsSSHSigned returns true if the commit has an SSH signature.
274+
func (c *Commit) IsSSHSigned() bool {
275+
return signature.IsSSHSignature(c.Signature)
276+
}
277+
278+
// SignatureType returns the type of the commit signature as a string:
279+
// "openpgp" for PGP signatures, "ssh" for SSH signatures, "x509" for
280+
// S/MIME signatures, "empty" for an absent signature, and "unknown"
281+
// for an unrecognised one.
282+
func (c *Commit) SignatureType() string {
283+
return signature.GetSignatureType(c.Signature)
284+
}
285+
286+
// IsPGPSigned returns true if the tag has a PGP signature.
287+
func (t *Tag) IsPGPSigned() bool {
288+
return signature.IsPGPSignature(t.Signature)
289+
}
290+
291+
// IsSSHSigned returns true if the tag has an SSH signature.
292+
func (t *Tag) IsSSHSigned() bool {
293+
return signature.IsSSHSignature(t.Signature)
294+
}
295+
296+
// SignatureType returns the type of the tag signature as a string:
297+
// "openpgp" for PGP signatures, "ssh" for SSH signatures, "x509" for
298+
// S/MIME signatures, "empty" for an absent signature, and "unknown"
299+
// for an unrecognised one.
300+
func (t *Tag) SignatureType() string {
301+
return signature.GetSignatureType(t.Signature)
230302
}

0 commit comments

Comments
 (0)