aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBilly Lynch <billy@chainguard.dev>2024-01-11 14:42:52 -0500
committerBilly Lynch <billy@chainguard.dev>2024-01-18 12:56:55 -0500
commite1bb0e2cb68265458d4540ae566c255263d918a2 (patch)
tree139795e0c80abefba237d422952d7cfa9e84deef
parenta6e934f1f76996c7f92cb4fde708170c1eb5d032 (diff)
downloadgo-git-e1bb0e2cb68265458d4540ae566c255263d918a2.tar.gz
git: worktree_commit, Add crypto.Signer option to CommitOptions.
This change adds a new crypto.Signer option to CommitOptions as an alternative to SignKey to allow alternative commit signers to be used. This change byitself does not add other signing methods (e.g. ssh, x509, gitsign), but gives callers the ability to add their own. This roughly follows git's sign_buffer approach where go-git handles the commit message body encoding, and hands off the encoded []byte to the signing implementation for the signature to be returned. Signed-off-by: Billy Lynch <billy@chainguard.dev>
-rw-r--r--options.go5
-rw-r--r--worktree_commit.go53
-rw-r--r--worktree_commit_test.go1
3 files changed, 48 insertions, 11 deletions
diff --git a/options.go b/options.go
index e748b91..ddd637b 100644
--- a/options.go
+++ b/options.go
@@ -1,6 +1,7 @@
package git
import (
+ "crypto"
"errors"
"fmt"
"regexp"
@@ -507,6 +508,10 @@ type CommitOptions struct {
// commit will not be signed. The private key must be present and already
// decrypted.
SignKey *openpgp.Entity
+ // Signer denotes a cryptographic signer to sign the commit with.
+ // A nil value here means the commit will not be signed.
+ // Takes precedence over SignKey.
+ Signer crypto.Signer
// Amend will create a new commit object and replace the commit that HEAD currently
// points to. Cannot be used with All nor Parents.
Amend bool
diff --git a/worktree_commit.go b/worktree_commit.go
index 4d811f3..18002f2 100644
--- a/worktree_commit.go
+++ b/worktree_commit.go
@@ -2,7 +2,10 @@ package git
import (
"bytes"
+ "crypto"
+ "crypto/rand"
"errors"
+ "io"
"path"
"sort"
"strings"
@@ -14,6 +17,7 @@ import (
"github.com/go-git/go-git/v5/storage"
"github.com/ProtonMail/go-crypto/openpgp"
+ "github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/go-git/go-billy/v5"
)
@@ -125,12 +129,17 @@ func (w *Worktree) buildCommitObject(msg string, opts *CommitOptions, tree plumb
ParentHashes: opts.Parents,
}
- if opts.SignKey != nil {
- sig, err := w.buildCommitSignature(commit, opts.SignKey)
+ // Convert SignKey into a Signer if set. Existing Signer should take priority.
+ signer := opts.Signer
+ if signer == nil && opts.SignKey != nil {
+ signer = &gpgSigner{key: opts.SignKey}
+ }
+ if signer != nil {
+ sig, err := w.buildCommitSignature(commit, signer)
if err != nil {
return plumbing.ZeroHash, err
}
- commit.PGPSignature = sig
+ commit.PGPSignature = string(sig)
}
obj := w.r.Storer.NewEncodedObject()
@@ -140,20 +149,44 @@ func (w *Worktree) buildCommitObject(msg string, opts *CommitOptions, tree plumb
return w.r.Storer.SetEncodedObject(obj)
}
-func (w *Worktree) buildCommitSignature(commit *object.Commit, signKey *openpgp.Entity) (string, error) {
+type gpgSigner struct {
+ key *openpgp.Entity
+}
+
+func (s *gpgSigner) Public() crypto.PublicKey {
+ return s.key.PrimaryKey
+}
+
+func (s *gpgSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
+ var cfg *packet.Config
+ if opts != nil {
+ cfg = &packet.Config{
+ DefaultHash: opts.HashFunc(),
+ }
+ }
+
+ var b bytes.Buffer
+ if err := openpgp.ArmoredDetachSign(&b, s.key, bytes.NewReader(digest), cfg); err != nil {
+ return nil, err
+ }
+ return b.Bytes(), nil
+}
+
+func (w *Worktree) buildCommitSignature(commit *object.Commit, signer crypto.Signer) ([]byte, error) {
encoded := &plumbing.MemoryObject{}
if err := commit.Encode(encoded); err != nil {
- return "", err
+ return nil, err
}
r, err := encoded.Reader()
if err != nil {
- return "", err
+ return nil, err
}
- var b bytes.Buffer
- if err := openpgp.ArmoredDetachSign(&b, signKey, r, nil); err != nil {
- return "", err
+ b, err := io.ReadAll(r)
+ if err != nil {
+ return nil, err
}
- return b.String(), nil
+
+ return signer.Sign(rand.Reader, b, nil)
}
// buildTreeHelper converts a given index.Index file into multiple git objects
diff --git a/worktree_commit_test.go b/worktree_commit_test.go
index 1ac1990..cc3c9a9 100644
--- a/worktree_commit_test.go
+++ b/worktree_commit_test.go
@@ -131,7 +131,6 @@ func (s *WorktreeSuite) TestCommitAmend(c *C) {
_, err = w.Commit("foo\n", &CommitOptions{Author: defaultSignature()})
c.Assert(err, IsNil)
-
amendedHash, err := w.Commit("bar\n", &CommitOptions{Amend: true})
c.Assert(err, IsNil)