fork of go-git with some jj specific features

git: 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 -2
options.go
··· 1 package git 2 3 import ( 4 - "crypto" 5 "errors" 6 "fmt" 7 "regexp" ··· 516 // Signer denotes a cryptographic signer to sign the commit with. 517 // A nil value here means the commit will not be signed. 518 // Takes precedence over SignKey. 519 - Signer crypto.Signer 520 // Amend will create a new commit object and replace the commit that HEAD currently 521 // points to. Cannot be used with All nor Parents. 522 Amend bool
··· 1 package git 2 3 import ( 4 "errors" 5 "fmt" 6 "regexp" ··· 515 // Signer denotes a cryptographic signer to sign the commit with. 516 // A nil value here means the commit will not be signed. 517 // Takes precedence over SignKey. 518 + Signer Signer 519 // Amend will create a new commit object and replace the commit that HEAD currently 520 // points to. Cannot be used with All nor Parents. 521 Amend bool
+33
signer.go
···
··· 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 + }
+56
signer_test.go
···
··· 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 + Email: "john@example.com", 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 + }
+4 -33
worktree_commit.go
··· 2 3 import ( 4 "bytes" 5 - "crypto" 6 - "crypto/rand" 7 "errors" 8 "io" 9 "path" ··· 135 signer = &gpgSigner{key: opts.SignKey} 136 } 137 if signer != nil { 138 - sig, err := w.buildCommitSignature(commit, signer) 139 if err != nil { 140 return plumbing.ZeroHash, err 141 } ··· 151 152 type gpgSigner struct { 153 key *openpgp.Entity 154 } 155 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 - 168 var b bytes.Buffer 169 - if err := openpgp.ArmoredDetachSign(&b, s.key, bytes.NewReader(digest), cfg); err != nil { 170 return nil, err 171 } 172 return b.Bytes(), nil 173 - } 174 - 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 192 // buildTreeHelper converts a given index.Index file into multiple git objects
··· 2 3 import ( 4 "bytes" 5 "errors" 6 "io" 7 "path" ··· 133 signer = &gpgSigner{key: opts.SignKey} 134 } 135 if signer != nil { 136 + sig, err := signObject(signer, commit) 137 if err != nil { 138 return plumbing.ZeroHash, err 139 } ··· 149 150 type gpgSigner struct { 151 key *openpgp.Entity 152 + cfg *packet.Config 153 } 154 155 + func (s *gpgSigner) Sign(message io.Reader) ([]byte, error) { 156 var b bytes.Buffer 157 + if err := openpgp.ArmoredDetachSign(&b, s.key, message, s.cfg); err != nil { 158 return nil, err 159 } 160 return b.Bytes(), nil 161 } 162 163 // buildTreeHelper converts a given index.Index file into multiple git objects