Skip to content

Commit 255dc05

Browse files
committed
Signer: fix usage of crypto.Signer interface
crypto.Signer was incorrectly used before. Signer documentation says that Signer.Sign should be used on digests, whereas we were using this on message bodies. To fix this, create our own Signer interface (+ signableObject borrowed from #705) that describes more accurately what we want. As before, the expectation is that signer implementations only need to worry about acting on encoded message bodies rather than needing to encode objects themselves. This is technically a breaking change from the previous Signer implementation, but since this is new and hasn't made it into cut release yet, this seems like an acceptible change. Also adds example test showing how signers can be made (uses base64 for consistent outputs).
1 parent 0dfff25 commit 255dc05

File tree

4 files changed

+94
-35
lines changed

4 files changed

+94
-35
lines changed

options.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package git
22

33
import (
4-
"crypto"
54
"errors"
65
"fmt"
76
"regexp"
@@ -516,7 +515,7 @@ type CommitOptions struct {
516515
// Signer denotes a cryptographic signer to sign the commit with.
517516
// A nil value here means the commit will not be signed.
518517
// Takes precedence over SignKey.
519-
Signer crypto.Signer
518+
Signer Signer
520519
// Amend will create a new commit object and replace the commit that HEAD currently
521520
// points to. Cannot be used with All nor Parents.
522521
Amend bool

signer.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package git
2+
3+
import (
4+
"io"
5+
6+
"github.com/go-git/go-git/v5/plumbing"
7+
)
8+
9+
// signableObject is an object which can be signed.
10+
type signableObject interface {
11+
EncodeWithoutSignature(o plumbing.EncodedObject) error
12+
}
13+
14+
// Signer is an interface for signing git objects.
15+
// message is a reader containing the encoded object to be signed.
16+
// Implementors should return the encoded signature and an error if any.
17+
// See https://git-scm.com/docs/gitformat-signature for more information.
18+
type Signer interface {
19+
Sign(message io.Reader) ([]byte, error)
20+
}
21+
22+
func signObject(signer Signer, obj signableObject) ([]byte, error) {
23+
encoded := &plumbing.MemoryObject{}
24+
if err := obj.EncodeWithoutSignature(encoded); err != nil {
25+
return nil, err
26+
}
27+
r, err := encoded.Reader()
28+
if err != nil {
29+
return nil, err
30+
}
31+
32+
return signer.Sign(r)
33+
}

signer_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package git
2+
3+
import (
4+
"encoding/base64"
5+
"fmt"
6+
"io"
7+
"time"
8+
9+
"github.com/go-git/go-billy/v5/memfs"
10+
"github.com/go-git/go-git/v5/plumbing/object"
11+
"github.com/go-git/go-git/v5/storage/memory"
12+
)
13+
14+
type b64signer struct{}
15+
16+
// This is not secure, and is only used as an example for testing purposes.
17+
// Please don't do this.
18+
func (b64signer) Sign(message io.Reader) ([]byte, error) {
19+
b, err := io.ReadAll(message)
20+
if err != nil {
21+
return nil, err
22+
}
23+
out := make([]byte, base64.StdEncoding.EncodedLen(len(b)))
24+
base64.StdEncoding.Encode(out, b)
25+
return out, nil
26+
}
27+
28+
func ExampleSigner() {
29+
repo, err := Init(memory.NewStorage(), memfs.New())
30+
if err != nil {
31+
panic(err)
32+
}
33+
w, err := repo.Worktree()
34+
if err != nil {
35+
panic(err)
36+
}
37+
commit, err := w.Commit("example commit", &CommitOptions{
38+
Author: &object.Signature{
39+
Name: "John Doe",
40+
41+
When: time.UnixMicro(1234567890).UTC(),
42+
},
43+
Signer: b64signer{},
44+
AllowEmptyCommits: true,
45+
})
46+
if err != nil {
47+
panic(err)
48+
}
49+
50+
obj, err := repo.CommitObject(commit)
51+
if err != nil {
52+
panic(err)
53+
}
54+
fmt.Println(obj.PGPSignature)
55+
// Output: dHJlZSA0YjgyNWRjNjQyY2I2ZWI5YTA2MGU1NGJmOGQ2OTI4OGZiZWU0OTA0CmF1dGhvciBKb2huIERvZSA8am9obkBleGFtcGxlLmNvbT4gMTIzNCArMDAwMApjb21taXR0ZXIgSm9obiBEb2UgPGpvaG5AZXhhbXBsZS5jb20+IDEyMzQgKzAwMDAKCmV4YW1wbGUgY29tbWl0
56+
}

worktree_commit.go

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package git
22

33
import (
44
"bytes"
5-
"crypto"
6-
"crypto/rand"
75
"errors"
86
"io"
97
"path"
@@ -135,7 +133,7 @@ func (w *Worktree) buildCommitObject(msg string, opts *CommitOptions, tree plumb
135133
signer = &gpgSigner{key: opts.SignKey}
136134
}
137135
if signer != nil {
138-
sig, err := w.buildCommitSignature(commit, signer)
136+
sig, err := signObject(signer, commit)
139137
if err != nil {
140138
return plumbing.ZeroHash, err
141139
}
@@ -151,44 +149,17 @@ func (w *Worktree) buildCommitObject(msg string, opts *CommitOptions, tree plumb
151149

152150
type gpgSigner struct {
153151
key *openpgp.Entity
152+
cfg *packet.Config
154153
}
155154

156-
func (s *gpgSigner) Public() crypto.PublicKey {
157-
return s.key.PrimaryKey
158-
}
159-
160-
func (s *gpgSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
161-
var cfg *packet.Config
162-
if opts != nil {
163-
cfg = &packet.Config{
164-
DefaultHash: opts.HashFunc(),
165-
}
166-
}
167-
155+
func (s *gpgSigner) Sign(message io.Reader) ([]byte, error) {
168156
var b bytes.Buffer
169-
if err := openpgp.ArmoredDetachSign(&b, s.key, bytes.NewReader(digest), cfg); err != nil {
157+
if err := openpgp.ArmoredDetachSign(&b, s.key, message, s.cfg); err != nil {
170158
return nil, err
171159
}
172160
return b.Bytes(), nil
173161
}
174162

175-
func (w *Worktree) buildCommitSignature(commit *object.Commit, signer crypto.Signer) ([]byte, error) {
176-
encoded := &plumbing.MemoryObject{}
177-
if err := commit.Encode(encoded); err != nil {
178-
return nil, err
179-
}
180-
r, err := encoded.Reader()
181-
if err != nil {
182-
return nil, err
183-
}
184-
b, err := io.ReadAll(r)
185-
if err != nil {
186-
return nil, err
187-
}
188-
189-
return signer.Sign(rand.Reader, b, nil)
190-
}
191-
192163
// buildTreeHelper converts a given index.Index file into multiple git objects
193164
// reading the blobs from the given filesystem and creating the trees from the
194165
// index structure. The created objects are pushed to a given Storer.

0 commit comments

Comments
 (0)