diff --git a/models/gpg_key.go b/models/gpg_key.go index e7aac5162..1c9d17d0e 100644 --- a/models/gpg_key.go +++ b/models/gpg_key.go @@ -6,13 +6,21 @@ package models import ( "bytes" + "container/list" + "crypto" "encoding/base64" "fmt" + "hash" + "io" "strings" "time" + "code.gitea.io/git" + "code.gitea.io/gitea/modules/log" + "github.com/go-xorm/xorm" "golang.org/x/crypto/openpgp" + "golang.org/x/crypto/openpgp/armor" "golang.org/x/crypto/openpgp/packet" ) @@ -274,3 +282,181 @@ func DeleteGPGKey(doer *User, id int64) (err error) { return nil } + +// CommitVerification represents a commit validation of signature +type CommitVerification struct { + Verified bool + Reason string + SigningUser *User + SigningKey *GPGKey +} + +// SignCommit represents a commit with validation of signature. +type SignCommit struct { + Verification *CommitVerification + *UserCommit +} + +func readerFromBase64(s string) (io.Reader, error) { + bs, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return nil, err + } + return bytes.NewBuffer(bs), nil +} + +func populateHash(hashFunc crypto.Hash, msg []byte) (hash.Hash, error) { + h := hashFunc.New() + if _, err := h.Write(msg); err != nil { + return nil, err + } + return h, nil +} + +// readArmoredSign read an armored signature block with the given type. https://sourcegraph.com/github.com/golang/crypto/-/blob/openpgp/read.go#L24:6-24:17 +func readArmoredSign(r io.Reader) (body io.Reader, err error) { + block, err := armor.Decode(r) + if err != nil { + return + } + if block.Type != openpgp.SignatureType { + return nil, fmt.Errorf("expected '" + openpgp.SignatureType + "', got: " + block.Type) + } + return block.Body, nil +} + +func extractSignature(s string) (*packet.Signature, error) { + r, err := readArmoredSign(strings.NewReader(s)) + if err != nil { + return nil, fmt.Errorf("Failed to read signature armor") + } + p, err := packet.Read(r) + if err != nil { + return nil, fmt.Errorf("Failed to read signature packet") + } + sig, ok := p.(*packet.Signature) + if !ok { + return nil, fmt.Errorf("Packet is not a signature") + } + return sig, nil +} + +func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error { + //Check if key can sign + if !k.CanSign { + return fmt.Errorf("key can not sign") + } + //Decode key + b, err := readerFromBase64(k.Content) + if err != nil { + return err + } + //Read key + p, err := packet.Read(b) + if err != nil { + return err + } + + //Check type + pkey, ok := p.(*packet.PublicKey) + if !ok { + return fmt.Errorf("key is not a public key") + } + + return pkey.VerifySignature(h, s) +} + +// ParseCommitWithSignature check if signature is good against keystore. +func ParseCommitWithSignature(c *git.Commit) *CommitVerification { + + if c.Signature != nil { + + //Parsing signature + sig, err := extractSignature(c.Signature.Signature) + if err != nil { //Skipping failed to extract sign + log.Error(3, "SignatureRead err: %v", err) + return &CommitVerification{ + Verified: false, + Reason: "gpg.error.extract_sign", + } + } + + //Find Committer account + committer, err := GetUserByEmail(c.Committer.Email) + if err != nil { //Skipping not user for commiter + log.Error(3, "NoCommitterAccount: %v", err) + return &CommitVerification{ + Verified: false, + Reason: "gpg.error.no_committer_account", + } + } + + keys, err := ListGPGKeys(committer.ID) + if err != nil || len(keys) == 0 { //Skipping failed to get gpg keys of user + log.Error(3, "ListGPGKeys: %v", err) + return &CommitVerification{ + Verified: false, + Reason: "gpg.error.failed_retrieval_gpg_keys", + } + } + + //Generating hash of commit + hash, err := populateHash(sig.Hash, []byte(c.Signature.Payload)) + if err != nil { //Skipping ailed to generate hash + log.Error(3, "PopulateHash: %v", err) + return &CommitVerification{ + Verified: false, + Reason: "gpg.error.generate_hash", + } + } + + for _, k := range keys { + //We get PK + if err := verifySign(sig, hash, k); err == nil { + return &CommitVerification{ //Everything is ok + Verified: true, + Reason: fmt.Sprintf("%s <%s> / %s", c.Committer.Name, c.Committer.Email, k.KeyID), + SigningUser: committer, + SigningKey: k, + } + } + //And test also SubsKey + for _, sk := range k.SubsKey { + if err := verifySign(sig, hash, sk); err == nil { + return &CommitVerification{ //Everything is ok + Verified: true, + Reason: fmt.Sprintf("%s <%s> / %s", c.Committer.Name, c.Committer.Email, sk.KeyID), + SigningUser: committer, + SigningKey: sk, + } + } + } + } + return &CommitVerification{ //Default at this stage + Verified: false, + Reason: "gpg.error.no_gpg_keys_found", + } + } + + return &CommitVerification{ + Verified: false, //Default value + Reason: "gpg.error.not_signed_commit", //Default value + } +} + +// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys. +func ParseCommitsWithSignature(oldCommits *list.List) *list.List { + var ( + newCommits = list.New() + e = oldCommits.Front() + ) + for e != nil { + c := e.Value.(UserCommit) + newCommits.PushBack(SignCommit{ + UserCommit: &c, + Verification: ParseCommitWithSignature(c.Commit), + }) + e = e.Next() + } + return newCommits +} diff --git a/models/gpg_key_test.go b/models/gpg_key_test.go index 1ef5838e3..d291ce91c 100644 --- a/models/gpg_key_test.go +++ b/models/gpg_key_test.go @@ -46,3 +46,119 @@ MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg== assert.Nil(t, err, "Could not parse a valid GPG armored key", key) //TODO verify value of key } + +func TestExtractSignature(t *testing.T) { + testGPGArmor := `-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFh91QoBCADciaDd7aqegYkn4ZIG7J0p1CRwpqMGjxFroJEMg6M1ZiuEVTRv +z49P4kcr1+98NvFmcNc+x5uJgvPCwr/N8ZW5nqBUs2yrklbFF4MeQomyZJJegP8m +/dsRT3BwIT8YMUtJuCj0iqD9vuKYfjrztcMgC1sYwcE9E9OlA0pWBvUdU2i0TIB1 +vOq6slWGvHHa5l5gPfm09idlVxfH5+I+L1uIMx5ovbiVVU5x2f1AR1T18f0t2TVN +0agFTyuoYE1ATmvJHmMcsfgM1Gpd9hIlr9vlupT2kKTPoNzVzsJsOU6Ku/Lf/bac +mF+TfSbRCtmG7dkYZ4metLj7zG/WkW8IvJARABEBAAG0HUFudG9pbmUgR0lSQVJE +IDxzYXBrQHNhcGsuZnI+iQFUBBMBCAA+FiEEEIOwJg/1vpF1itJ4roJVuKDYKOQF +Alh91QoCGwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQroJVuKDY +KORreggAlIkC2QjHP5tb7b0+LksB2JMXdY+UzZBcJxtNmvA7gNQaGvWRrhrbePpa +MKDP+3A4BPDBsWFbbB7N56vQ5tROpmWbNKuFOVER4S1bj0JZV0E+xkDLqt9QwQtQ +ojd7oIZJwDUwdud1PvCza2mjgBqqiFE+twbc3i9xjciCGspMniUul1eQYLxRJ0w+ +sbvSOUnujnq5ByMSz9ij00O6aiPfNQS5oB5AALfpjYZDvWAAljLVrtmlQJWZ6dZo +T/YNwsW2dECPuti8+Nmu5FxPGDTXxdbnRaeJTQ3T6q1oUVAv7yTXBx5NXfXkMa5i +iEayQIH8Joq5Ev5ja/lRGQQhArMQ2bkBDQRYfdUKAQgAv7B3coLSrOQbuTZSlgWE +QeT+7DWbmqE1LAQA1pQPcUPXLBUVd60amZJxF9nzUYcY83ylDi0gUNJS+DJGOXpT +pzX2IOuOMGbtUSeKwg5s9O4SUO7f2yCc3RGaegER5zgESxelmOXG+b/hoNt7JbdU +JtxcnLr91Jw2PBO/Xf0ZKJ01CQG2Yzdrrj6jnrHyx94seHy0i6xH1o0OuvfVMLfN +/Vbb/ZHh6ym2wHNqRX62b0VAbchcJXX/MEehXGknKTkO6dDUd+mhRgWMf9ZGRFWx +ag4qALimkf1FXtAyD0vxFYeyoWUQzrOvUsm2BxIN/986R08fhkBQnp5nz07mrU02 +cQARAQABiQE8BBgBCAAmFiEEEIOwJg/1vpF1itJ4roJVuKDYKOQFAlh91QoCGwwF +CQPCZwAACgkQroJVuKDYKOT32wf/UZqMdPn5OhyhffFzjQx7wolrf92WkF2JkxtH +6c3Htjlt/p5RhtKEeErSrNAxB4pqB7dznHaJXiOdWEZtRVXXjlNHjrokGTesqtKk +lHWtK62/MuyLdr+FdCl68F3ewuT2iu/MDv+D4HPqA47zma9xVgZ9ZNwJOpv3fCOo +RfY66UjGEnfgYifgtI5S84/mp2jaSc9UNvlZB6RSf8cfbJUL74kS2lq+xzSlf0yP +Av844q/BfRuVsJsK1NDNG09LC30B0l3LKBqlrRmRTUMHtgchdX2dY+p7GPOoSzlR +MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg== +=i9b7 +-----END PGP PUBLIC KEY BLOCK-----` + ekey, err := checkArmoredGPGKeyString(testGPGArmor) + assert.Nil(t, err, "Could not parse a valid GPG armored key", ekey) + + pubkey := ekey.PrimaryKey + content, err := base64EncPubKey(pubkey) + assert.Nil(t, err, "Could not base64 encode a valid PublicKey content", ekey) + + key := &GPGKey{ + KeyID: pubkey.KeyIdString(), + Content: content, + Created: pubkey.CreationTime, + CanSign: pubkey.CanSign(), + CanEncryptComms: pubkey.PubKeyAlgo.CanEncrypt(), + CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(), + CanCertify: pubkey.PubKeyAlgo.CanSign(), + } + + cannotsignkey := &GPGKey{ + KeyID: pubkey.KeyIdString(), + Content: content, + Created: pubkey.CreationTime, + CanSign: false, + CanEncryptComms: false, + CanEncryptStorage: false, + CanCertify: false, + } + + testGoodSigArmor := `-----BEGIN PGP SIGNATURE----- + +iQEzBAABCAAdFiEEEIOwJg/1vpF1itJ4roJVuKDYKOQFAljAiQIACgkQroJVuKDY +KORvCgf6A/Ehh0r7QbO2tFEghT+/Ab+bN7jRN3zP9ed6/q/ophYmkrU0NibtbJH9 +AwFVdHxCmj78SdiRjaTKyevklXw34nvMftmvnOI4lBNUdw6KWl25/n/7wN0l2oZW +rW3UawYpZgodXiLTYarfEimkDQmT67ArScjRA6lLbkEYKO0VdwDu+Z6yBUH3GWtm +45RkXpnsF6AXUfuD7YxnfyyDE1A7g7zj4vVYUAfWukJjqow/LsCUgETETJOqj9q3 +52/oQDs04fVkIEtCDulcY+K/fKlukBPJf9WceNDEqiENUzN/Z1y0E+tJ07cSy4bk +yIJb+d0OAaG8bxloO7nJq4Res1Qa8Q== +=puvG +-----END PGP SIGNATURE-----` + testGoodPayload := `tree 56ae8d2799882b20381fc11659db06c16c68c61a +parent c7870c39e4e6b247235ca005797703ec4254613f +author Antoine GIRARD 1489012989 +0100 +committer Antoine GIRARD 1489012989 +0100 + +Goog GPG +` + + testBadSigArmor := `-----BEGIN PGP SIGNATURE----- + +iQEzBAABCAAdFiEE5yr4rn9ulbdMxJFiPYI/ySNrtNkFAljAiYkACgkQPYI/ySNr +tNmDdQf+NXhVRiOGt0GucpjJCGrOnK/qqVUmQyRUfrqzVUdb/1/Ws84V5/wE547I +6z3oxeBKFsJa1CtIlxYaUyVhYnDzQtphJzub+Aw3UG0E2ywiE+N7RCa1Ufl7pPxJ +U0SD6gvNaeTDQV/Wctu8v8DkCtEd3N8cMCDWhvy/FQEDztVtzm8hMe0Vdm0ozEH6 +P0W93sDNkLC5/qpWDN44sFlYDstW5VhMrnF0r/ohfaK2kpYHhkPk7WtOoHSUwQSg +c4gfhjvXIQrWFnII1Kr5jFGlmgNSR02qpb31VGkMzSnBhWVf2OaHS/kI49QHJakq +AhVDEnoYLCgoDGg9c3p1Ll2452/c6Q== +=uoGV +-----END PGP SIGNATURE-----` + testBadPayload := `tree 3074ff04951956a974e8b02d57733b0766f7cf6c +parent fd3577542f7ad1554c7c7c0eb86bb57a1324ad91 +author Antoine GIRARD 1489013107 +0100 +committer Antoine GIRARD 1489013107 +0100 + +Unkonwn GPG key with good email +` + //Reading Sign + goodSig, err := extractSignature(testGoodSigArmor) + assert.Nil(t, err, "Could not parse a valid GPG armored signature", testGoodSigArmor) + badSig, err := extractSignature(testBadSigArmor) + assert.Nil(t, err, "Could not parse a valid GPG armored signature", testBadSigArmor) + + //Generating hash of commit + goodHash, err := populateHash(goodSig.Hash, []byte(testGoodPayload)) + assert.Nil(t, err, "Could not generate a valid hash of payload", testGoodPayload) + badHash, err := populateHash(badSig.Hash, []byte(testBadPayload)) + assert.Nil(t, err, "Could not generate a valid hash of payload", testBadPayload) + + //Verify + err = verifySign(goodSig, goodHash, key) + assert.Nil(t, err, "Could not validate a good signature") + err = verifySign(badSig, badHash, key) + assert.NotNil(t, err, "Validate a bad signature") + err = verifySign(goodSig, goodHash, cannotsignkey) + assert.NotNil(t, err, "Validate a bad signature with a kay that can not sign") +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 829c46061..f8e21b5df 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1349,3 +1349,13 @@ no_read = You do not have any read notifications. pin = Pin notification mark_as_read = Mark as read mark_as_unread = Mark as unread + + +[gpg] +error.extract_sign = Failed to extract signature +error.generate_hash = Failed to generate hash of commit +error.no_committer_account = No account linked to committer email +error.no_gpg_keys_found = "Failed to retrieve publics keys of committer" +error.no_gpg_keys_found = "No known key found for this signature in database" +error.not_signed_commit = "Not a signed commit" +error.failed_retrieval_gpg_keys = "Failed to retrieve any key attached to the commiter account" diff --git a/public/css/index.css b/public/css/index.css index 3863042bc..01ffd7e92 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -1924,8 +1924,29 @@ footer .ui.language .menu { padding-left: 15px; } .repository #commits-table thead .sha { - font-size: 13px; - padding: 6px 40px 4px 35px; + text-align: center; + width: 140px; +} +.repository #commits-table td.sha .sha.label { + margin: 0; +} +.repository #commits-table td.sha .sha.label.isSigned { + border: 1px solid #BBB; +} +.repository #commits-table td.sha .sha.label.isSigned .detail.icon { + background: #FAFAFA; + margin: -6px -10px -4px 0px; + padding: 5px 3px 5px 6px; + border-left: 1px solid #BBB; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.repository #commits-table td.sha .sha.label.isSigned.isVerified { + border: 1px solid #21BA45; + background: #21BA4518; +} +.repository #commits-table td.sha .sha.label.isSigned.isVerified .detail.icon { + border-left: 1px solid #21BA4580; } .repository #commits-table.ui.basic.striped.table tbody tr:nth-child(2n) { background-color: rgba(0, 0, 0, 0.02) !important; @@ -2239,6 +2260,16 @@ footer .ui.language .menu { margin-left: 26px; padding-top: 0; } +.repository .ui.attached.isSigned.isVerified:not(.positive) { + border-left: 1px solid #A3C293; + border-right: 1px solid #A3C293; +} +.repository .ui.attached.isSigned.isVerified.top:not(.positive) { + border-top: 1px solid #A3C293; +} +.repository .ui.attached.isSigned.isVerified:not(.positive):last-child { + border-bottom: 1px solid #A3C293; +} .user-cards .list { padding: 0; } diff --git a/public/less/_repository.less b/public/less/_repository.less index 200984767..406dfb2ac 100644 --- a/public/less/_repository.less +++ b/public/less/_repository.less @@ -800,8 +800,31 @@ padding-left: 15px; } .sha { - font-size: 13px; - padding: 6px 40px 4px 35px; + text-align: center; + width: 140px; + } + } + td.sha{ + .sha.label{ + margin: 0; + &.isSigned{ + border: 1px solid #BBB; + .detail.icon{ + background: #FAFAFA; + margin: -6px -10px -4px 0px; + padding: 5px 3px 5px 6px; + border-left: 1px solid #BBB; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + } + &.isSigned.isVerified{ + border: 1px solid #21BA45; + background: #21BA4518; + .detail.icon{ + border-left: 1px solid #21BA4580; + } + } } } &.ui.basic.striped.table tbody tr:nth-child(2n) { @@ -1206,6 +1229,18 @@ } } } + .ui.attached.isSigned.isVerified{ + &:not(.positive){ + border-left: 1px solid #A3C293; + border-right: 1px solid #A3C293; + } + &.top:not(.positive){ + border-top: 1px solid #A3C293; + } + &:not(.positive):last-child { + border-bottom: 1px solid #A3C293; + } + } } // End of .repository diff --git a/routers/api/v1/convert/convert.go b/routers/api/v1/convert/convert.go index 7485ac9e2..153993ce8 100644 --- a/routers/api/v1/convert/convert.go +++ b/routers/api/v1/convert/convert.go @@ -44,6 +44,7 @@ func ToCommit(c *git.Commit) *api.PayloadCommit { if err == nil { committerUsername = committer.Name } + verif := models.ParseCommitWithSignature(c) return &api.PayloadCommit{ ID: c.ID.String(), Message: c.Message(), @@ -59,6 +60,12 @@ func ToCommit(c *git.Commit) *api.PayloadCommit { UserName: committerUsername, }, Timestamp: c.Author.When, + Verification: &api.PayloadCommitVerification{ + Verified: verif.Verified, + Reason: verif.Reason, + Signature: c.Signature.Signature, + Payload: c.Signature.Payload, + }, } } diff --git a/routers/repo/commit.go b/routers/repo/commit.go index 7f4457c52..62f55a52e 100644 --- a/routers/repo/commit.go +++ b/routers/repo/commit.go @@ -68,6 +68,7 @@ func Commits(ctx *context.Context) { } commits = renderIssueLinks(commits, ctx.Repo.RepoLink) commits = models.ValidateCommitsWithEmails(commits) + commits = models.ParseCommitsWithSignature(commits) ctx.Data["Commits"] = commits ctx.Data["Username"] = ctx.Repo.Owner.Name @@ -121,6 +122,7 @@ func SearchCommits(ctx *context.Context) { } commits = renderIssueLinks(commits, ctx.Repo.RepoLink) commits = models.ValidateCommitsWithEmails(commits) + commits = models.ParseCommitsWithSignature(commits) ctx.Data["Commits"] = commits ctx.Data["Keyword"] = keyword @@ -167,6 +169,7 @@ func FileHistory(ctx *context.Context) { } commits = renderIssueLinks(commits, ctx.Repo.RepoLink) commits = models.ValidateCommitsWithEmails(commits) + commits = models.ParseCommitsWithSignature(commits) ctx.Data["Commits"] = commits ctx.Data["Username"] = ctx.Repo.Owner.Name @@ -222,6 +225,7 @@ func Diff(ctx *context.Context) { ctx.Data["IsImageFile"] = commit.IsImageFile ctx.Data["Title"] = commit.Summary() + " ยท " + base.ShortSha(commitID) ctx.Data["Commit"] = commit + ctx.Data["Verification"] = models.ParseCommitWithSignature(commit) ctx.Data["Author"] = models.ValidateCommitWithEmail(commit) ctx.Data["Diff"] = diff ctx.Data["Parents"] = parents @@ -276,6 +280,7 @@ func CompareDiff(ctx *context.Context) { return } commits = models.ValidateCommitsWithEmails(commits) + commits = models.ParseCommitsWithSignature(commits) ctx.Data["CommitRepoLink"] = ctx.Repo.RepoLink ctx.Data["Commits"] = commits diff --git a/templates/repo/commits_table.tmpl b/templates/repo/commits_table.tmpl index a2c2c352c..8e473f22d 100644 --- a/templates/repo/commits_table.tmpl +++ b/templates/repo/commits_table.tmpl @@ -21,7 +21,8 @@ {{.i18n.Tr "repo.commits.author"}} - SHA1 {{.i18n.Tr "repo.commits.message"}} + SHA1 + {{.i18n.Tr "repo.commits.message"}} {{.i18n.Tr "repo.commits.date"}} @@ -40,9 +41,21 @@   {{.Author.Name}} {{end}} - + + + {{ShortSha .ID.String}} + {{if .Signature}} +
+ {{if .Verification.Verified}} + + {{else}} + + {{end}} +
+ {{end}} +
+ - {{ShortSha .ID.String}} {{RenderCommitMessage false .Summary $.RepoLink $.Repository.ComposeMetas}} {{TimeSince .Author.When $.Lang}} diff --git a/templates/repo/diff/page.tmpl b/templates/repo/diff/page.tmpl index 90ee68057..d4a9d72db 100644 --- a/templates/repo/diff/page.tmpl +++ b/templates/repo/diff/page.tmpl @@ -5,13 +5,13 @@ {{if .IsDiffCompare }} {{template "repo/commits_table" .}} {{else}} -
+
{{.i18n.Tr "repo.diff.browse_source"}} {{RenderCommitMessage true .Commit.Message $.RepoLink $.Repository.ComposeMetas}}
-
+
{{if .Author}} {{if .Author.FullName}} @@ -41,6 +41,21 @@
+ {{if .Commit.Signature}} + {{if .Verification.Verified }} +
+ + Signed by : + {{.Commit.Committer.Name}} <{{.Commit.Committer.Email}}> + GPG key ID: {{.Verification.SigningKey.KeyID}} +
+ {{else}} +
+ + {{.i18n.Tr .Verification.Reason}} +
+ {{end}} + {{end}} {{end}} {{template "repo/diff/box" .}} diff --git a/vendor/code.gitea.io/git/commit.go b/vendor/code.gitea.io/git/commit.go index fa5e18561..28dd26483 100644 --- a/vendor/code.gitea.io/git/commit.go +++ b/vendor/code.gitea.io/git/commit.go @@ -6,6 +6,7 @@ package git import ( "bufio" + "bytes" "container/list" "fmt" "net/http" @@ -22,11 +23,30 @@ type Commit struct { Author *Signature Committer *Signature CommitMessage string + Signature *CommitGPGSignature parents []SHA1 // SHA1 strings submoduleCache *ObjectCache } +// CommitGPGSignature represents a git commit signature part. +type CommitGPGSignature struct { + Signature string + Payload string //TODO check if can be reconstruct from the rest of commit information to not have duplicate data +} + +// similar to https://github.com/git/git/blob/3bc53220cb2dcf709f7a027a3f526befd021d858/commit.c#L1128 +func newGPGSignatureFromCommitline(data []byte, signatureStart int) (*CommitGPGSignature, error) { + sig := new(CommitGPGSignature) + signatureEnd := bytes.LastIndex(data, []byte("-----END PGP SIGNATURE-----")) + if signatureEnd == -1 { + return nil, fmt.Errorf("end of commit signature not found") + } + sig.Signature = strings.Replace(string(data[signatureStart:signatureEnd+27]), "\n ", "\n", -1) + sig.Payload = string(data[:signatureStart-8]) + string(data[signatureEnd+27:]) + return sig, nil +} + // Message returns the commit message. Same as retrieving CommitMessage directly. func (c *Commit) Message() string { return c.CommitMessage diff --git a/vendor/code.gitea.io/git/repo_commit.go b/vendor/code.gitea.io/git/repo_commit.go index 97f44abda..37219734a 100644 --- a/vendor/code.gitea.io/git/repo_commit.go +++ b/vendor/code.gitea.io/git/repo_commit.go @@ -78,6 +78,12 @@ l: return nil, err } commit.Committer = sig + case "gpgsig": + sig, err := newGPGSignatureFromCommitline(data, nextline+spacepos+1) + if err != nil { + return nil, err + } + commit.Signature = sig } nextline += eol + 1 case eol == 0: diff --git a/vendor/code.gitea.io/sdk/gitea/hook.go b/vendor/code.gitea.io/sdk/gitea/hook.go index c2f88f4a3..4b4506812 100644 --- a/vendor/code.gitea.io/sdk/gitea/hook.go +++ b/vendor/code.gitea.io/sdk/gitea/hook.go @@ -137,12 +137,21 @@ type PayloadUser struct { // PayloadCommit FIXME: consider use same format as API when commits API are added. type PayloadCommit struct { - ID string `json:"id"` - Message string `json:"message"` - URL string `json:"url"` - Author *PayloadUser `json:"author"` - Committer *PayloadUser `json:"committer"` - Timestamp time.Time `json:"timestamp"` + ID string `json:"id"` + Message string `json:"message"` + URL string `json:"url"` + Author *PayloadUser `json:"author"` + Committer *PayloadUser `json:"committer"` + Verification *PayloadCommitVerification `json:"verification"` + Timestamp time.Time `json:"timestamp"` +} + +// PayloadCommitVerification represent the GPG verification part of a commit. FIXME: like PayloadCommit consider use same format as API when commits API are added. +type PayloadCommitVerification struct { + Verified bool `json:"verified"` + Reason string `json:"reason"` + Signature string `json:"signature"` + Payload string `json:"payload"` } var ( diff --git a/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go b/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go index 911e63f1a..c8afe92c9 100644 --- a/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go +++ b/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go @@ -38,6 +38,12 @@ type CreateGPGKeyOption struct { ArmoredKey string `json:"armored_public_key" binding:"Required"` } +// ListGPGKeys list all the GPG keys of the user +func (c *Client) ListGPGKeys(user string) ([]*GPGKey, error) { + keys := make([]*GPGKey, 0, 10) + return keys, c.getParsedResponse("GET", fmt.Sprintf("/users/%s/gpg_keys", user), nil, nil, &keys) +} + // ListMyGPGKeys list all the GPG keys of current user func (c *Client) ListMyGPGKeys() ([]*GPGKey, error) { keys := make([]*GPGKey, 0, 10) diff --git a/vendor/vendor.json b/vendor/vendor.json index 91d889c6f..4b24848ea 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -3,16 +3,16 @@ "ignore": "test", "package": [ { - "checksumSHA1": "nt2y/SNJe3Rl0tzdaEyGQfCc4L4=", + "checksumSHA1": "bKoCvndU5ZVC5vqtwYjuU3YPJ6k=", "path": "code.gitea.io/git", - "revision": "b4c06a53d0f619e84a99eb042184663d4ad8a32b", - "revisionTime": "2017-02-22T02:52:05Z" + "revision": "337468881d5961d36de8e950a607d6033e73dcf0", + "revisionTime": "2017-03-13T15:07:03Z" }, { - "checksumSHA1": "qXD1HI8bTn7qNJZJOeZqQgxo354=", + "checksumSHA1": "32qRX47gRmdBW4l4hCKGRZbuIJk=", "path": "code.gitea.io/sdk/gitea", - "revision": "8807a1d2ced513880b288a5e2add39df6bf72144", - "revisionTime": "2017-03-04T10:22:44Z" + "revision": "9ceaabb8c70aba1ff73718332db2356356e26ffb", + "revisionTime": "2017-03-09T22:08:57Z" }, { "checksumSHA1": "IyfS7Rbl6OgR83QR7TOfKdDCq+M=",