From 850b9f81c2f025d8a75c4728520553504e3a425c Mon Sep 17 00:00:00 2001 From: Sunny Date: Fri, 24 Nov 2017 19:19:44 +0530 Subject: plumbing: object/tag, add PGPSignature support --- plumbing/object/tag.go | 44 +++++++++++++++++++++++++++++++++++++++++++- plumbing/object/tag_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/plumbing/object/tag.go b/plumbing/object/tag.go index d8b71a0..6295205 100644 --- a/plumbing/object/tag.go +++ b/plumbing/object/tag.go @@ -6,6 +6,7 @@ import ( "fmt" "io" stdioutil "io/ioutil" + "strings" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/storer" @@ -30,6 +31,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. @@ -124,7 +127,36 @@ 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 } @@ -156,6 +188,16 @@ func (t *Tag) Encode(o plumbing.EncodedObject) error { return err } + if t.PGPSignature != "" { + // 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 } diff --git a/plumbing/object/tag_test.go b/plumbing/object/tag_test.go index 608fe72..edd23f5 100644 --- a/plumbing/object/tag_test.go +++ b/plumbing/object/tag_test.go @@ -285,3 +285,29 @@ 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) +} -- cgit From e2dbd3a4f6ec8c6467eb1bc30ed937399a4456e4 Mon Sep 17 00:00:00 2001 From: Sunny Date: Fri, 24 Nov 2017 19:48:10 +0530 Subject: plumbing: object/tag, add signature verification --- plumbing/object/tag.go | 29 ++++++++++++++++++++ plumbing/object/tag_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/plumbing/object/tag.go b/plumbing/object/tag.go index 6295205..9b4250f 100644 --- a/plumbing/object/tag.go +++ b/plumbing/object/tag.go @@ -8,6 +8,8 @@ import ( 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" "gopkg.in/src-d/go-git.v4/utils/ioutil" @@ -267,6 +269,33 @@ 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) + + // Remove signature. Keep only the tag components. + t.PGPSignature = "" + + encoded := &plumbing.MemoryObject{} + if err := t.Encode(encoded); 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 diff --git a/plumbing/object/tag_test.go b/plumbing/object/tag_test.go index edd23f5..9900093 100644 --- a/plumbing/object/tag_test.go +++ b/plumbing/object/tag_test.go @@ -311,3 +311,68 @@ RUysgqjcpT8+iQM1PblGfHR4XAhuOqN5Fx06PSaFZhqvWFezJ28/CLyX5q+oIVk= 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 "] + c.Assert(ok, Equals, true) +} -- cgit From e1ce83fcaf620db6a03026185617908a83dfe841 Mon Sep 17 00:00:00 2001 From: Sunny Date: Fri, 24 Nov 2017 23:30:34 +0530 Subject: plumbing: object/{commit,tag}, encode method with sig optional Adds Commit.encode() and Tag.encode() with optional `includeSig` parameter to include or exclude signature from the encoded object. --- plumbing/object/commit.go | 13 +++++++------ plumbing/object/tag.go | 12 +++++++----- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/plumbing/object/commit.go b/plumbing/object/commit.go index e54eb7d..a317714 100644 --- a/plumbing/object/commit.go +++ b/plumbing/object/commit.go @@ -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 { @@ -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 } @@ -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() diff --git a/plumbing/object/tag.go b/plumbing/object/tag.go index 9b4250f..19e55cf 100644 --- a/plumbing/object/tag.go +++ b/plumbing/object/tag.go @@ -165,6 +165,10 @@ func (t *Tag) Decode(o plumbing.EncodedObject) (err error) { // 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 { @@ -190,7 +194,7 @@ func (t *Tag) Encode(o plumbing.EncodedObject) error { return err } - if t.PGPSignature != "" { + 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 { @@ -281,11 +285,9 @@ func (t *Tag) Verify(armoredKeyRing string) (*openpgp.Entity, error) { // Extract signature. signature := strings.NewReader(t.PGPSignature) - // Remove signature. Keep only the tag components. - t.PGPSignature = "" - encoded := &plumbing.MemoryObject{} - if err := t.Encode(encoded); err != nil { + // 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() -- cgit