Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit da62c67

Browse files
authored
Merge pull request #658 from darkowlzz/tag_sign_and_verify
plumbing: object/tag, add signature and verification support
2 parents d92d437 + e1ce83f commit da62c67

File tree

3 files changed

+172
-7
lines changed

3 files changed

+172
-7
lines changed

Diff for: plumbing/object/commit.go

+7-6
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
223223

224224
// Encode transforms a Commit into a plumbing.EncodedObject.
225225
func (b *Commit) Encode(o plumbing.EncodedObject) error {
226+
return b.encode(o, true)
227+
}
228+
229+
func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) error {
226230
o.SetType(plumbing.CommitObject)
227231
w, err := o.Writer()
228232
if err != nil {
@@ -257,7 +261,7 @@ func (b *Commit) Encode(o plumbing.EncodedObject) error {
257261
return err
258262
}
259263

260-
if b.PGPSignature != "" {
264+
if b.PGPSignature != "" && includeSig {
261265
if _, err = fmt.Fprint(w, "pgpsig"); err != nil {
262266
return err
263267
}
@@ -325,12 +329,9 @@ func (c *Commit) Verify(armoredKeyRing string) (*openpgp.Entity, error) {
325329
// Extract signature.
326330
signature := strings.NewReader(c.PGPSignature)
327331

328-
// Remove signature. Keep only the commit components.
329-
c.PGPSignature = ""
330-
331-
// Encode commit and get a reader object.
332332
encoded := &plumbing.MemoryObject{}
333-
if err := c.Encode(encoded); err != nil {
333+
// Encode commit components, excluding signature and get a reader object.
334+
if err := c.encode(encoded, false); err != nil {
334335
return nil, err
335336
}
336337
er, err := encoded.Reader()

Diff for: plumbing/object/tag.go

+74-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import (
66
"fmt"
77
"io"
88
stdioutil "io/ioutil"
9+
"strings"
10+
11+
"golang.org/x/crypto/openpgp"
912

1013
"gopkg.in/src-d/go-git.v4/plumbing"
1114
"gopkg.in/src-d/go-git.v4/plumbing/storer"
@@ -30,6 +33,8 @@ type Tag struct {
3033
Tagger Signature
3134
// Message is an arbitrary text message.
3235
Message string
36+
// PGPSignature is the PGP signature of the tag.
37+
PGPSignature string
3338
// TargetType is the object type of the target.
3439
TargetType plumbing.ObjectType
3540
// Target is the hash of the target object.
@@ -124,13 +129,46 @@ func (t *Tag) Decode(o plumbing.EncodedObject) (err error) {
124129
if err != nil {
125130
return err
126131
}
127-
t.Message = string(data)
132+
133+
var pgpsig bool
134+
// Check if data contains PGP signature.
135+
if bytes.Contains(data, []byte(beginpgp)) {
136+
// Split the lines at newline.
137+
messageAndSig := bytes.Split(data, []byte("\n"))
138+
139+
for _, l := range messageAndSig {
140+
if pgpsig {
141+
if bytes.Contains(l, []byte(endpgp)) {
142+
t.PGPSignature += endpgp + "\n"
143+
pgpsig = false
144+
} else {
145+
t.PGPSignature += string(l) + "\n"
146+
}
147+
continue
148+
}
149+
150+
// Check if it's the beginning of a PGP signature.
151+
if bytes.Contains(l, []byte(beginpgp)) {
152+
t.PGPSignature += beginpgp + "\n"
153+
pgpsig = true
154+
continue
155+
}
156+
157+
t.Message += string(l) + "\n"
158+
}
159+
} else {
160+
t.Message = string(data)
161+
}
128162

129163
return nil
130164
}
131165

132166
// Encode transforms a Tag into a plumbing.EncodedObject.
133167
func (t *Tag) Encode(o plumbing.EncodedObject) error {
168+
return t.encode(o, true)
169+
}
170+
171+
func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) error {
134172
o.SetType(plumbing.TagObject)
135173
w, err := o.Writer()
136174
if err != nil {
@@ -156,6 +194,16 @@ func (t *Tag) Encode(o plumbing.EncodedObject) error {
156194
return err
157195
}
158196

197+
if t.PGPSignature != "" && includeSig {
198+
// Split all the signature lines and write with a newline at the end.
199+
lines := strings.Split(t.PGPSignature, "\n")
200+
for _, line := range lines {
201+
if _, err = fmt.Fprintf(w, "%s\n", line); err != nil {
202+
return err
203+
}
204+
}
205+
}
206+
159207
return err
160208
}
161209

@@ -225,6 +273,31 @@ func (t *Tag) String() string {
225273
)
226274
}
227275

276+
// Verify performs PGP verification of the tag with a provided armored
277+
// keyring and returns openpgp.Entity associated with verifying key on success.
278+
func (t *Tag) Verify(armoredKeyRing string) (*openpgp.Entity, error) {
279+
keyRingReader := strings.NewReader(armoredKeyRing)
280+
keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader)
281+
if err != nil {
282+
return nil, err
283+
}
284+
285+
// Extract signature.
286+
signature := strings.NewReader(t.PGPSignature)
287+
288+
encoded := &plumbing.MemoryObject{}
289+
// Encode tag components, excluding signature and get a reader object.
290+
if err := t.encode(encoded, false); err != nil {
291+
return nil, err
292+
}
293+
er, err := encoded.Reader()
294+
if err != nil {
295+
return nil, err
296+
}
297+
298+
return openpgp.CheckArmoredDetachedSignature(keyring, er, signature)
299+
}
300+
228301
// TagIter provides an iterator for a set of tags.
229302
type TagIter struct {
230303
storer.EncodedObjectIter

Diff for: plumbing/object/tag_test.go

+91
Original file line numberDiff line numberDiff line change
@@ -285,3 +285,94 @@ func (s *TagSuite) TestLongTagNameSerialization(c *C) {
285285
c.Assert(err, IsNil)
286286
c.Assert(decoded.Name, Equals, longName)
287287
}
288+
289+
func (s *TagSuite) TestPGPSignatureSerialization(c *C) {
290+
encoded := &plumbing.MemoryObject{}
291+
decoded := &Tag{}
292+
tag := s.tag(c, plumbing.NewHash("b742a2a9fa0afcfa9a6fad080980fbc26b007c69"))
293+
294+
pgpsignature := `-----BEGIN PGP SIGNATURE-----
295+
296+
iQEcBAABAgAGBQJTZbQlAAoJEF0+sviABDDrZbQH/09PfE51KPVPlanr6q1v4/Ut
297+
LQxfojUWiLQdg2ESJItkcuweYg+kc3HCyFejeDIBw9dpXt00rY26p05qrpnG+85b
298+
hM1/PswpPLuBSr+oCIDj5GMC2r2iEKsfv2fJbNW8iWAXVLoWZRF8B0MfqX/YTMbm
299+
ecorc4iXzQu7tupRihslbNkfvfciMnSDeSvzCpWAHl7h8Wj6hhqePmLm9lAYqnKp
300+
8S5B/1SSQuEAjRZgI4IexpZoeKGVDptPHxLLS38fozsyi0QyDyzEgJxcJQVMXxVi
301+
RUysgqjcpT8+iQM1PblGfHR4XAhuOqN5Fx06PSaFZhqvWFezJ28/CLyX5q+oIVk=
302+
=EFTF
303+
-----END PGP SIGNATURE-----
304+
`
305+
tag.PGPSignature = pgpsignature
306+
307+
err := tag.Encode(encoded)
308+
c.Assert(err, IsNil)
309+
310+
err = decoded.Decode(encoded)
311+
c.Assert(err, IsNil)
312+
c.Assert(decoded.PGPSignature, Equals, pgpsignature)
313+
}
314+
315+
func (s *TagSuite) TestVerify(c *C) {
316+
ts := time.Unix(1511524851, 0)
317+
loc, _ := time.LoadLocation("Asia/Kolkata")
318+
tag := &Tag{
319+
Name: "v0.2",
320+
Tagger: Signature{Name: "Sunny", Email: "me@darkowlzz.space", When: ts.In(loc)},
321+
Message: `This is a signed tag
322+
`,
323+
TargetType: plumbing.CommitObject,
324+
Target: plumbing.NewHash("064f92fe00e70e6b64cb358a65039daa4b6ae8d2"),
325+
PGPSignature: `
326+
-----BEGIN PGP SIGNATURE-----
327+
328+
iQFHBAABCAAxFiEEoRt6IzxHaZkkUslhQyLeMqcmyU4FAloYCg8THG1lQGRhcmtv
329+
d2x6ei5zcGFjZQAKCRBDIt4ypybJTs0cCACjQZe2610t3gfbUPbgQiWDL9uvlCeb
330+
sNSeTC6hLAFSvHTMqLr/6RpiLlfQXyATD7TZUH0DUSLsERLheG82OgVxkOTzPCpy
331+
GL6iGKeZ4eZ1KiV+SBPjqizC9ShhGooPUw9oUSVdj4jsaHDdDHtY63Pjl0KvJmms
332+
OVi9SSxjeMbmaC81C8r0ZuOLTXJh/JRKh2BsehdcnK3736BK+16YRD7ugXLpkQ5d
333+
nsCFVbuYYoLMoJL5NmEun0pbUrpY+MI8VPK0f9HV5NeaC4NksC+ke/xYMT+P2lRL
334+
CN+9zcCIU+mXr2fCl1xOQcnQzwOElObDxpDcPcxVn0X+AhmPc+uj0mqD
335+
=l75D
336+
-----END PGP SIGNATURE-----
337+
`,
338+
}
339+
340+
armoredKeyRing := `
341+
-----BEGIN PGP PUBLIC KEY BLOCK-----
342+
343+
mQENBFmtHgABCADnfThM7q8D4pgUub9jMppSpgFh3ev84g3Csc3yQUlszEOVgXmu
344+
YiSWP1oAiWFQ8ahCydh3LT8TnEB2QvoRNiExUI5XlXFwVfKW3cpDu8gdhtufs90Q
345+
NvpaHOgTqRf/texGEKwXi6fvS47fpyaQ9BKNdN52LeaaHzDDZkVsAFmroE+7MMvj
346+
P4Mq8qDn2WcWnX9zheQKYrX6Cs48Tx80eehHor4f/XnuaP8DLmPQx7URdJ0Igckh
347+
N+i91Qv2ujin8zxUwhkfus66EZS9lQ4qR9iVHs4WHOs3j7whsejd4VhajonilVHj
348+
uqTtqHmpN/4njbIKb8q8uQkS26VQYoSYm2UvABEBAAG0GlN1bm55IDxtZUBkYXJr
349+
b3dsenouc3BhY2U+iQFUBBMBCAA+FiEEoRt6IzxHaZkkUslhQyLeMqcmyU4FAlmt
350+
HgACGwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQQyLeMqcmyU7V
351+
nAf+J5BYu26B2i+iwctOzDRFcPwCLka9cBwe5wcDvoF2qL8QRo8NPWBBH4zWHa/k
352+
BthtGo1b89a53I2hnTwTQ0NOtAUNV+Vvu6nOHJd9Segsx3E1nM43bd2bUfGJ1eeO
353+
jDOlOvtP4ozuV6Ej+0Ln2ouMOc87yAwbAzTfQ9axU6CKUbqy0/t2dW1jdKntGH+t
354+
VPeFxJHL2gXjP89skCSPYA7yKqqyJRPFvC+7rde1OLdCmZi4VwghUiNbh3s1+xM3
355+
gfr2ahsRDTN2SQzwuHu4y1EgZgPtuWfRxzHqduoRoSgfOfFr9H9Il3UMHf2Etleu
356+
rif40YZJhge6STwsIycGh4wOiLkBDQRZrR4AAQgArpUvPdGC/W9X4AuZXrXEShvx
357+
TqM4K2Jk9n0j+ABx87k9fm48qgtae7+TayMbb0i7kcbgnjltKbauTbyRbju/EJvN
358+
CdIw76IPpjy6jUM37wG2QGLFo6Ku3x8/ZpNGGOZ8KMU258/EBqDlJQ/4g4kJ8D+m
359+
9yOH0r6/Xpe/jOY2V8Jo9pdFTm+8eAsSyZF0Cl7drz603Pymq1IS2wrwQbdxQA/w
360+
B75pQ5es7X34Ac7/9UZCwCPmZDAldnjHyw5dZgZe8XLrG84BIfbG0Hj8PjrFdF1D
361+
Czt9bk+PbYAnLORW2oX1oedxVrNFo5UrbWgBSjA1ppbGFjwSDHFlyjuEuxqyFwAR
362+
AQABiQE8BBgBCAAmFiEEoRt6IzxHaZkkUslhQyLeMqcmyU4FAlmtHgACGwwFCQPC
363+
ZwAACgkQQyLeMqcmyU7ZBggArzc8UUVSjde987Vqnu/S5Cv8Qhz+UB7gAFyTW2iF
364+
VYvB86r30H/NnfjvjCVkBE6FHCNHoxWVyDWmuxKviB7nkReHuwqniQHPgdJDcTKC
365+
tBboeX2IYBLJbEvEJuz5NSvnvFuYkIpZHqySFaqdl/qu9XcmoPL5AmIzIFOeiNty
366+
qT0ldkf3ru6yQQDDqBDpkfz4AzkpFnLYL59z6IbJDK2Hz7aKeSEeVOGiZLCjIZZV
367+
uISZThYqh5zUkvF346OHLDqfDdgQ4RZriqd/DTtRJPlz2uL0QcEIjJuYCkG0UWgl
368+
sYyf9RfOnw/KUFAQbdtvLx3ikODQC+D3KBtuKI9ISHQfgw==
369+
=FPev
370+
-----END PGP PUBLIC KEY BLOCK-----
371+
`
372+
373+
e, err := tag.Verify(armoredKeyRing)
374+
c.Assert(err, IsNil)
375+
376+
_, ok := e.Identities["Sunny <me@darkowlzz.space>"]
377+
c.Assert(ok, Equals, true)
378+
}

0 commit comments

Comments
 (0)