From 1685de7eba9343043c1e2cb277482eafb578095a Mon Sep 17 00:00:00 2001
From: Gusted <postmaster@gusted.xyz>
Date: Sat, 11 Nov 2023 13:01:24 +0100
Subject: [PATCH] Add noreply email address as verified for SSH signed Git
 commits

- When someone really wants to avoid sharing their email, they could
configure git to use the noreply email for git commits. However if they
also wanted to use SSH signing, it would not show up as verified as the
noreply email address was technically not an activated email address for
the user.
- Add unit tests for the `ParseCommitWithSSHSignature` function.
- Resolves https://codeberg.org/Codeberg/Community/issues/946
---
 models/asymkey/main_test.go                   |   1 +
 models/asymkey/ssh_key_commit_verification.go |   6 +
 .../ssh_key_commit_verification_test.go       | 146 ++++++++++++++++++
 .../public_key.yml                            |  13 ++
 4 files changed, 166 insertions(+)
 create mode 100644 models/asymkey/ssh_key_commit_verification_test.go
 create mode 100644 models/fixtures/TestParseCommitWithSSHSignature/public_key.yml

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 a61f0663b1..b8886b5aab 100644
--- a/models/asymkey/ssh_key_commit_verification.go
+++ b/models/asymkey/ssh_key_commit_verification.go
@@ -36,6 +36,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 <non-existent> 1699710556 +0100
+committer user2 <non-existent> 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 <user2@example.com> 1699707877 +0100
+committer user2 <user2@example.com> 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 <user2@example.com> 1699707877 +0100
+committer user2 <user2@example.com> 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 <user2@noreply.example.com> 1699709594 +0100
+committer user2 <user2@noreply.example.com> 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