diff --git a/models/asymkey/main_test.go b/models/asymkey/main_test.go index be71f848d9..87b5c22c4a 100644 --- a/models/asymkey/main_test.go +++ b/models/asymkey/main_test.go @@ -14,6 +14,7 @@ func TestMain(m *testing.M) { FixtureFiles: []string{ "gpg_key.yml", "public_key.yml", + "TestParseCommitWithSSHSignature/public_key.yml", "deploy_key.yml", "gpg_key_import.yml", "user.yml", diff --git a/models/asymkey/ssh_key_commit_verification.go b/models/asymkey/ssh_key_commit_verification.go index 27c6df3578..2b802710a8 100644 --- a/models/asymkey/ssh_key_commit_verification.go +++ b/models/asymkey/ssh_key_commit_verification.go @@ -39,6 +39,12 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer * log.Error("GetEmailAddresses: %v", err) } + // Add the noreply email address as verified address. + committerEmailAddresses = append(committerEmailAddresses, &user_model.EmailAddress{ + IsActivated: true, + Email: committer.GetPlaceholderEmail(), + }) + activated := false for _, e := range committerEmailAddresses { if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) { diff --git a/models/asymkey/ssh_key_commit_verification_test.go b/models/asymkey/ssh_key_commit_verification_test.go new file mode 100644 index 0000000000..e1ed409bd7 --- /dev/null +++ b/models/asymkey/ssh_key_commit_verification_test.go @@ -0,0 +1,146 @@ +// Copyright 2023 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package asymkey + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + + "github.com/stretchr/testify/assert" +) + +func TestParseCommitWithSSHSignature(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + sshKey := unittest.AssertExistsAndLoadBean(t, &PublicKey{ID: 1000, OwnerID: 2}) + + t.Run("No commiter", func(t *testing.T) { + commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, &git.Commit{}, &user_model.User{}) + assert.False(t, commitVerification.Verified) + assert.Equal(t, NoKeyFound, commitVerification.Reason) + }) + + t.Run("Commiter without keys", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, &git.Commit{Committer: &git.Signature{Email: user.Email}}, user) + assert.False(t, commitVerification.Verified) + assert.Equal(t, NoKeyFound, commitVerification.Reason) + }) + + t.Run("Correct signature with wrong email", func(t *testing.T) { + gitCommit := &git.Commit{ + Committer: &git.Signature{ + Email: "non-existent", + }, + Signature: &git.CommitGPGSignature{ + Payload: `tree 2d491b2985a7ff848d5c02748e7ea9f9f7619f9f +parent 45b03601635a1f463b81963a4022c7f87ce96ef9 +author user2 1699710556 +0100 +committer user2 1699710556 +0100 + +Using email that isn't known to Forgejo +`, + Signature: `-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgoGSe9Zy7Ez9bSJcaTNjh/Y7p95 +f5DujjqkpzFRtw6CEAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQIMufOuSjZeDUujrkVK4sl7ICa0WwEftas8UAYxx0Thdkiw2qWjR1U1PKfTLm16/w8 +/bS1LX1lZNuzm2LR2qEgw= +-----END SSH SIGNATURE----- +`, + }, + } + commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2) + assert.False(t, commitVerification.Verified) + assert.Equal(t, NoKeyFound, commitVerification.Reason) + }) + + t.Run("Incorrect signature with correct email", func(t *testing.T) { + gitCommit := &git.Commit{ + Committer: &git.Signature{ + Email: "user2@example.com", + }, + Signature: &git.CommitGPGSignature{ + Payload: `tree 853694aae8816094a0d875fee7ea26278dbf5d0f +parent c2780d5c313da2a947eae22efd7dacf4213f4e7f +author user2 1699707877 +0100 +committer user2 1699707877 +0100 + +Add content +`, + Signature: `-----BEGIN SSH SIGNATURE-----`, + }, + } + + commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2) + assert.False(t, commitVerification.Verified) + assert.Equal(t, NoKeyFound, commitVerification.Reason) + }) + + t.Run("Valid signature with correct email", func(t *testing.T) { + gitCommit := &git.Commit{ + Committer: &git.Signature{ + Email: "user2@example.com", + }, + Signature: &git.CommitGPGSignature{ + Payload: `tree 853694aae8816094a0d875fee7ea26278dbf5d0f +parent c2780d5c313da2a947eae22efd7dacf4213f4e7f +author user2 1699707877 +0100 +committer user2 1699707877 +0100 + +Add content +`, + Signature: `-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgoGSe9Zy7Ez9bSJcaTNjh/Y7p95 +f5DujjqkpzFRtw6CEAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQBe2Fwk/FKY3SBCnG6jSYcO6ucyahp2SpQ/0P+otslzIHpWNW8cQ0fGLdhhaFynJXQ +fs9cMpZVM9BfIKNUSO8QY= +-----END SSH SIGNATURE----- +`, + }, + } + + commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2) + assert.True(t, commitVerification.Verified) + assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason) + assert.Equal(t, sshKey, commitVerification.SigningSSHKey) + }) + + t.Run("Valid signature with noreply email", func(t *testing.T) { + defer test.MockVariableValue(&setting.Service.NoReplyAddress, "noreply.example.com")() + + gitCommit := &git.Commit{ + Committer: &git.Signature{ + Email: "user2@noreply.example.com", + }, + Signature: &git.CommitGPGSignature{ + Payload: `tree 4836c7f639f37388bab4050ef5c97bbbd54272fc +parent 795be1b0117ea5c65456050bb9fd84744d4fd9c6 +author user2 1699709594 +0100 +committer user2 1699709594 +0100 + +Commit with noreply +`, + Signature: `-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgoGSe9Zy7Ez9bSJcaTNjh/Y7p95 +f5DujjqkpzFRtw6CEAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQJz83KKxD6Bz/ZvNpqkA3RPOSQ4LQ5FfEItbtoONkbwV9wAWMnmBqgggo/lnXCJ3oq +muPLbvEduU+Ze/1Ol1pgk= +-----END SSH SIGNATURE----- +`, + }, + } + + commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2) + assert.True(t, commitVerification.Verified) + assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason) + assert.Equal(t, sshKey, commitVerification.SigningSSHKey) + }) +} diff --git a/models/fixtures/TestParseCommitWithSSHSignature/public_key.yml b/models/fixtures/TestParseCommitWithSSHSignature/public_key.yml new file mode 100644 index 0000000000..f76dabb1c1 --- /dev/null +++ b/models/fixtures/TestParseCommitWithSSHSignature/public_key.yml @@ -0,0 +1,13 @@ +- + id: 1000 + owner_id: 2 + name: user2@localhost + fingerprint: "SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4" + content: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKBknvWcuxM/W0iXGkzY4f2O6feX+Q7o46pKcxUbcOgh user2@localhost" + # private key (base64-ed) LS0tLS1CRUdJTiBPUEVOU1NIIFBSSVZBVEUgS0VZLS0tLS0KYjNCbGJuTnphQzFyWlhrdGRqRUFBQUFBQkc1dmJtVUFBQUFFYm05dVpRQUFBQUFBQUFBQkFBQUFNd0FBQUF0emMyZ3RaVwpReU5UVXhPUUFBQUNDZ1pKNzFuTHNUUDF0SWx4cE0yT0g5anVuM2wva082T09xU25NVkczRG9JUUFBQUpocG43YTZhWisyCnVnQUFBQXR6YzJndFpXUXlOVFV4T1FBQUFDQ2daSjcxbkxzVFAxdElseHBNMk9IOWp1bjNsL2tPNk9PcVNuTVZHM0RvSVEKQUFBRUFxVm12bmo1LzZ5TW12ck9Ub29xa3F5MmUrc21aK0tBcEtKR0crRnY5MlA2QmtudldjdXhNL1cwaVhHa3pZNGYyTwo2ZmVYK1E3bzQ2cEtjeFViY09naEFBQUFFMmQxYzNSbFpFQm5kWE4wWldRdFltVmhjM1FCQWc9PQotLS0tLUVORCBPUEVOU1NIIFBSSVZBVEUgS0VZLS0tLS0= + mode: 2 + type: 1 + verified: true + created_unix: 1559593109 + updated_unix: 1565224552 + login_source_id: 0