diff options
author | Ori Rawlings <orirawlings@gmail.com> | 2017-11-24 17:38:51 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-11-24 17:38:51 -0500 |
commit | da62c676588f73cc6d1f05f621fead6890c65eea (patch) | |
tree | dff2708f4c2b9fcbb22e73a7fa14ca6a8041b8fc | |
parent | d92d437af0fb23865c79e45b4acd3d7544593260 (diff) | |
parent | e1ce83fcaf620db6a03026185617908a83dfe841 (diff) | |
download | go-git-da62c676588f73cc6d1f05f621fead6890c65eea.tar.gz |
Merge pull request #658 from darkowlzz/tag_sign_and_verify
plumbing: object/tag, add signature and verification support
-rw-r--r-- | plumbing/object/commit.go | 13 | ||||
-rw-r--r-- | plumbing/object/tag.go | 75 | ||||
-rw-r--r-- | plumbing/object/tag_test.go | 91 |
3 files changed, 172 insertions, 7 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 d8b71a0..19e55cf 100644 --- a/plumbing/object/tag.go +++ b/plumbing/object/tag.go @@ -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" @@ -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. @@ -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 { @@ -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 } @@ -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 diff --git a/plumbing/object/tag_test.go b/plumbing/object/tag_test.go index 608fe72..9900093 100644 --- a/plumbing/object/tag_test.go +++ b/plumbing/object/tag_test.go @@ -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) +} |