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

plumbing: object/tag, add signature and verification support #658

Merged
merged 3 commits into from
Nov 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions plumbing/object/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {

// Encode transforms a Commit into a plumbing.EncodedObject.
func (b *Commit) Encode(o plumbing.EncodedObject) error {
return b.encode(o, true)
}

func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) error {
o.SetType(plumbing.CommitObject)
w, err := o.Writer()
if err != nil {
Expand Down Expand Up @@ -257,7 +261,7 @@ func (b *Commit) Encode(o plumbing.EncodedObject) error {
return err
}

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

// Remove signature. Keep only the commit components.
c.PGPSignature = ""

// Encode commit and get a reader object.
encoded := &plumbing.MemoryObject{}
if err := c.Encode(encoded); err != nil {
// Encode commit components, excluding signature and get a reader object.
if err := c.encode(encoded, false); err != nil {
return nil, err
}
er, err := encoded.Reader()
Expand Down
75 changes: 74 additions & 1 deletion plumbing/object/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (
"fmt"
"io"
stdioutil "io/ioutil"
"strings"

"golang.org/x/crypto/openpgp"

"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/storer"
Expand All @@ -30,6 +33,8 @@ type Tag struct {
Tagger Signature
// Message is an arbitrary text message.
Message string
// PGPSignature is the PGP signature of the tag.
PGPSignature string
// TargetType is the object type of the target.
TargetType plumbing.ObjectType
// Target is the hash of the target object.
Expand Down Expand Up @@ -124,13 +129,46 @@ func (t *Tag) Decode(o plumbing.EncodedObject) (err error) {
if err != nil {
return err
}
t.Message = string(data)

var pgpsig bool
// Check if data contains PGP signature.
if bytes.Contains(data, []byte(beginpgp)) {
// Split the lines at newline.
messageAndSig := bytes.Split(data, []byte("\n"))

for _, l := range messageAndSig {
if pgpsig {
if bytes.Contains(l, []byte(endpgp)) {
t.PGPSignature += endpgp + "\n"
pgpsig = false
} else {
t.PGPSignature += string(l) + "\n"
}
continue
}

// Check if it's the beginning of a PGP signature.
if bytes.Contains(l, []byte(beginpgp)) {
t.PGPSignature += beginpgp + "\n"
pgpsig = true
continue
}

t.Message += string(l) + "\n"
}
} else {
t.Message = string(data)
}

return nil
}

// Encode transforms a Tag into a plumbing.EncodedObject.
func (t *Tag) Encode(o plumbing.EncodedObject) error {
return t.encode(o, true)
}

func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) error {
o.SetType(plumbing.TagObject)
w, err := o.Writer()
if err != nil {
Expand All @@ -156,6 +194,16 @@ func (t *Tag) Encode(o plumbing.EncodedObject) error {
return err
}

if t.PGPSignature != "" && includeSig {
// Split all the signature lines and write with a newline at the end.
lines := strings.Split(t.PGPSignature, "\n")
for _, line := range lines {
if _, err = fmt.Fprintf(w, "%s\n", line); err != nil {
return err
}
}
}

return err
}

Expand Down Expand Up @@ -225,6 +273,31 @@ func (t *Tag) String() string {
)
}

// Verify performs PGP verification of the tag with a provided armored
// keyring and returns openpgp.Entity associated with verifying key on success.
func (t *Tag) Verify(armoredKeyRing string) (*openpgp.Entity, error) {
keyRingReader := strings.NewReader(armoredKeyRing)
keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader)
if err != nil {
return nil, err
}

// Extract signature.
signature := strings.NewReader(t.PGPSignature)

encoded := &plumbing.MemoryObject{}
// Encode tag components, excluding signature and get a reader object.
if err := t.encode(encoded, false); err != nil {
return nil, err
}
er, err := encoded.Reader()
if err != nil {
return nil, err
}

return openpgp.CheckArmoredDetachedSignature(keyring, er, signature)
}

// TagIter provides an iterator for a set of tags.
type TagIter struct {
storer.EncodedObjectIter
Expand Down
91 changes: 91 additions & 0 deletions plumbing/object/tag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,94 @@ func (s *TagSuite) TestLongTagNameSerialization(c *C) {
c.Assert(err, IsNil)
c.Assert(decoded.Name, Equals, longName)
}

func (s *TagSuite) TestPGPSignatureSerialization(c *C) {
encoded := &plumbing.MemoryObject{}
decoded := &Tag{}
tag := s.tag(c, plumbing.NewHash("b742a2a9fa0afcfa9a6fad080980fbc26b007c69"))

pgpsignature := `-----BEGIN PGP SIGNATURE-----

iQEcBAABAgAGBQJTZbQlAAoJEF0+sviABDDrZbQH/09PfE51KPVPlanr6q1v4/Ut
LQxfojUWiLQdg2ESJItkcuweYg+kc3HCyFejeDIBw9dpXt00rY26p05qrpnG+85b
hM1/PswpPLuBSr+oCIDj5GMC2r2iEKsfv2fJbNW8iWAXVLoWZRF8B0MfqX/YTMbm
ecorc4iXzQu7tupRihslbNkfvfciMnSDeSvzCpWAHl7h8Wj6hhqePmLm9lAYqnKp
8S5B/1SSQuEAjRZgI4IexpZoeKGVDptPHxLLS38fozsyi0QyDyzEgJxcJQVMXxVi
RUysgqjcpT8+iQM1PblGfHR4XAhuOqN5Fx06PSaFZhqvWFezJ28/CLyX5q+oIVk=
=EFTF
-----END PGP SIGNATURE-----
`
tag.PGPSignature = pgpsignature

err := tag.Encode(encoded)
c.Assert(err, IsNil)

err = decoded.Decode(encoded)
c.Assert(err, IsNil)
c.Assert(decoded.PGPSignature, Equals, pgpsignature)
}

func (s *TagSuite) TestVerify(c *C) {
ts := time.Unix(1511524851, 0)
loc, _ := time.LoadLocation("Asia/Kolkata")
tag := &Tag{
Name: "v0.2",
Tagger: Signature{Name: "Sunny", Email: "me@darkowlzz.space", When: ts.In(loc)},
Message: `This is a signed tag
`,
TargetType: plumbing.CommitObject,
Target: plumbing.NewHash("064f92fe00e70e6b64cb358a65039daa4b6ae8d2"),
PGPSignature: `
-----BEGIN PGP SIGNATURE-----

iQFHBAABCAAxFiEEoRt6IzxHaZkkUslhQyLeMqcmyU4FAloYCg8THG1lQGRhcmtv
d2x6ei5zcGFjZQAKCRBDIt4ypybJTs0cCACjQZe2610t3gfbUPbgQiWDL9uvlCeb
sNSeTC6hLAFSvHTMqLr/6RpiLlfQXyATD7TZUH0DUSLsERLheG82OgVxkOTzPCpy
GL6iGKeZ4eZ1KiV+SBPjqizC9ShhGooPUw9oUSVdj4jsaHDdDHtY63Pjl0KvJmms
OVi9SSxjeMbmaC81C8r0ZuOLTXJh/JRKh2BsehdcnK3736BK+16YRD7ugXLpkQ5d
nsCFVbuYYoLMoJL5NmEun0pbUrpY+MI8VPK0f9HV5NeaC4NksC+ke/xYMT+P2lRL
CN+9zcCIU+mXr2fCl1xOQcnQzwOElObDxpDcPcxVn0X+AhmPc+uj0mqD
=l75D
-----END PGP SIGNATURE-----
`,
}

armoredKeyRing := `
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQENBFmtHgABCADnfThM7q8D4pgUub9jMppSpgFh3ev84g3Csc3yQUlszEOVgXmu
YiSWP1oAiWFQ8ahCydh3LT8TnEB2QvoRNiExUI5XlXFwVfKW3cpDu8gdhtufs90Q
NvpaHOgTqRf/texGEKwXi6fvS47fpyaQ9BKNdN52LeaaHzDDZkVsAFmroE+7MMvj
P4Mq8qDn2WcWnX9zheQKYrX6Cs48Tx80eehHor4f/XnuaP8DLmPQx7URdJ0Igckh
N+i91Qv2ujin8zxUwhkfus66EZS9lQ4qR9iVHs4WHOs3j7whsejd4VhajonilVHj
uqTtqHmpN/4njbIKb8q8uQkS26VQYoSYm2UvABEBAAG0GlN1bm55IDxtZUBkYXJr
b3dsenouc3BhY2U+iQFUBBMBCAA+FiEEoRt6IzxHaZkkUslhQyLeMqcmyU4FAlmt
HgACGwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQQyLeMqcmyU7V
nAf+J5BYu26B2i+iwctOzDRFcPwCLka9cBwe5wcDvoF2qL8QRo8NPWBBH4zWHa/k
BthtGo1b89a53I2hnTwTQ0NOtAUNV+Vvu6nOHJd9Segsx3E1nM43bd2bUfGJ1eeO
jDOlOvtP4ozuV6Ej+0Ln2ouMOc87yAwbAzTfQ9axU6CKUbqy0/t2dW1jdKntGH+t
VPeFxJHL2gXjP89skCSPYA7yKqqyJRPFvC+7rde1OLdCmZi4VwghUiNbh3s1+xM3
gfr2ahsRDTN2SQzwuHu4y1EgZgPtuWfRxzHqduoRoSgfOfFr9H9Il3UMHf2Etleu
rif40YZJhge6STwsIycGh4wOiLkBDQRZrR4AAQgArpUvPdGC/W9X4AuZXrXEShvx
TqM4K2Jk9n0j+ABx87k9fm48qgtae7+TayMbb0i7kcbgnjltKbauTbyRbju/EJvN
CdIw76IPpjy6jUM37wG2QGLFo6Ku3x8/ZpNGGOZ8KMU258/EBqDlJQ/4g4kJ8D+m
9yOH0r6/Xpe/jOY2V8Jo9pdFTm+8eAsSyZF0Cl7drz603Pymq1IS2wrwQbdxQA/w
B75pQ5es7X34Ac7/9UZCwCPmZDAldnjHyw5dZgZe8XLrG84BIfbG0Hj8PjrFdF1D
Czt9bk+PbYAnLORW2oX1oedxVrNFo5UrbWgBSjA1ppbGFjwSDHFlyjuEuxqyFwAR
AQABiQE8BBgBCAAmFiEEoRt6IzxHaZkkUslhQyLeMqcmyU4FAlmtHgACGwwFCQPC
ZwAACgkQQyLeMqcmyU7ZBggArzc8UUVSjde987Vqnu/S5Cv8Qhz+UB7gAFyTW2iF
VYvB86r30H/NnfjvjCVkBE6FHCNHoxWVyDWmuxKviB7nkReHuwqniQHPgdJDcTKC
tBboeX2IYBLJbEvEJuz5NSvnvFuYkIpZHqySFaqdl/qu9XcmoPL5AmIzIFOeiNty
qT0ldkf3ru6yQQDDqBDpkfz4AzkpFnLYL59z6IbJDK2Hz7aKeSEeVOGiZLCjIZZV
uISZThYqh5zUkvF346OHLDqfDdgQ4RZriqd/DTtRJPlz2uL0QcEIjJuYCkG0UWgl
sYyf9RfOnw/KUFAQbdtvLx3ikODQC+D3KBtuKI9ISHQfgw==
=FPev
-----END PGP PUBLIC KEY BLOCK-----
`

e, err := tag.Verify(armoredKeyRing)
c.Assert(err, IsNil)

_, ok := e.Identities["Sunny <me@darkowlzz.space>"]
c.Assert(ok, Equals, true)
}