summaryrefslogtreecommitdiffstats
path: root/models
diff options
context:
space:
mode:
authorAntoine GIRARD <sapk@users.noreply.github.com>2017-03-22 11:43:54 +0100
committerLunny Xiao <xiaolunwen@gmail.com>2017-03-22 18:43:54 +0800
commit14fe9010ae8aecc0bcd38059b4c71256524b5341 (patch)
tree6a4740387d288ea8cd433412d36624e30912d5ee /models
parent9224405155322e096e7f2d0f9eed35633b937951 (diff)
downloadgitea-14fe9010ae8aecc0bcd38059b4c71256524b5341.tar.gz
gitea-14fe9010ae8aecc0bcd38059b4c71256524b5341.zip
GPG commit validation (#1150)
* GPG commit validation * Add translation + some little fix * Move hash calc after retrieving of potential key + missing translation * Add some little test
Diffstat (limited to 'models')
-rw-r--r--models/gpg_key.go186
-rw-r--r--models/gpg_key_test.go116
2 files changed, 302 insertions, 0 deletions
diff --git a/models/gpg_key.go b/models/gpg_key.go
index e7aac5162a..1c9d17d0e2 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 1ef5838e31..d291ce91c0 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 <sapk@sapk.fr> 1489012989 +0100
+committer Antoine GIRARD <sapk@sapk.fr> 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 <sapk@sapk.fr> 1489013107 +0100
+committer Antoine GIRARD <sapk@sapk.fr> 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")
+}