Skip to content

Commit c771cd6

Browse files
committed
refactor ParseCommitWithSSHSignature
1 parent e3ec65f commit c771cd6

File tree

19 files changed

+92
-173
lines changed

19 files changed

+92
-173
lines changed

models/fixtures/branch.yml

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -201,15 +201,3 @@
201201
is_deleted: false
202202
deleted_by_id: 0
203203
deleted_unix: 0
204-
205-
-
206-
id: 25
207-
repo_id: 63
208-
name: 'master'
209-
commit_id: 'b7fca3aa0d80144f9718b0486681944f9c587e33'
210-
commit_message: "Initial commit with signed file"
211-
commit_time: 1602935385
212-
pusher_id: 2
213-
is_deleted: false
214-
deleted_by_id: 0
215-
deleted_unix: 0

models/fixtures/repo_unit.yml

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -733,44 +733,3 @@
733733
type: 3
734734
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
735735
created_unix: 946684810
736-
737-
-
738-
id: 111
739-
repo_id: 63
740-
type: 4
741-
config: "{}"
742-
created_unix: 946684810
743-
744-
-
745-
id: 112
746-
repo_id: 63
747-
type: 5
748-
config: "{}"
749-
created_unix: 946684810
750-
751-
-
752-
id: 113
753-
repo_id: 63
754-
type: 1
755-
config: "{}"
756-
created_unix: 946684810
757-
758-
-
759-
id: 114
760-
repo_id: 63
761-
type: 2
762-
config: "{}"
763-
created_unix: 946684810
764-
765-
-
766-
id: 115
767-
repo_id: 63
768-
type: 8
769-
created_unix: 946684810
770-
771-
-
772-
id: 116
773-
repo_id: 63
774-
type: 3
775-
config: "{}"
776-
created_unix: 946684810

models/fixtures/repository.yml

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1786,34 +1786,3 @@
17861786
size: 0
17871787
is_fsck_enabled: true
17881788
close_issues_via_commit_in_any_branch: false
1789-
1790-
-
1791-
id: 63
1792-
owner_id: 2
1793-
owner_name: user2
1794-
lower_name: repo-test-trusted-ssh-keys
1795-
name: repo-test-trusted-ssh-keys
1796-
default_branch: master
1797-
num_watches: 0
1798-
num_stars: 0
1799-
num_forks: 0
1800-
num_issues: 0
1801-
num_closed_issues: 0
1802-
num_pulls: 0
1803-
num_closed_pulls: 0
1804-
num_milestones: 0
1805-
num_closed_milestones: 0
1806-
num_projects: 0
1807-
num_closed_projects: 0
1808-
is_private: false
1809-
is_empty: false
1810-
is_archived: false
1811-
is_mirror: false
1812-
status: 0
1813-
is_fork: false
1814-
fork_id: 0
1815-
is_template: false
1816-
template_id: 0
1817-
size: 0
1818-
is_fsck_enabled: true
1819-
close_issues_via_commit_in_any_branch: false

models/fixtures/user.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
num_followers: 2
6868
num_following: 1
6969
num_stars: 2
70-
num_repos: 15
70+
num_repos: 14
7171
num_teams: 0
7272
num_members: 0
7373
visibility: 0

models/repo/repo_list_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,27 +138,27 @@ func getTestCases() []struct {
138138
{
139139
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
140140
opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: optional.Some(false)},
141-
count: 35,
141+
count: 34,
142142
},
143143
{
144144
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
145145
opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: optional.Some(false)},
146-
count: 40,
146+
count: 39,
147147
},
148148
{
149149
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
150150
opts: repo_model.SearchRepoOptions{Keyword: "test", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true},
151-
count: 16,
151+
count: 15,
152152
},
153153
{
154154
name: "AllPublic/PublicAndPrivateRepositoriesOfUser2IncludingCollaborativeByName",
155155
opts: repo_model.SearchRepoOptions{Keyword: "test", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, AllPublic: true},
156-
count: 14,
156+
count: 13,
157157
},
158158
{
159159
name: "AllPublic/PublicRepositoriesOfOrganization",
160160
opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: optional.Some(false), Template: optional.Some(false)},
161-
count: 35,
161+
count: 34,
162162
},
163163
{
164164
name: "AllTemplates",

services/asymkey/commit.go

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package asymkey
66
import (
77
"context"
88
"fmt"
9+
"slices"
910
"strings"
1011

1112
asymkey_model "code.gitea.io/gitea/models/asymkey"
@@ -359,24 +360,39 @@ func VerifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, si
359360
return nil
360361
}
361362

363+
func verifySSHCommitVerificationByInstanceKey(c *git.Commit, committerUser, signerUser *user_model.User, committerGitEmail, publicKeyContent string) *asymkey_model.CommitVerification {
364+
fingerprint, err := asymkey_model.CalcFingerprint(publicKeyContent)
365+
if err != nil {
366+
log.Error("Error calculating the fingerprint public key %q, err: %v", publicKeyContent, err)
367+
return nil
368+
}
369+
sshPubKey := &asymkey_model.PublicKey{
370+
Verified: true,
371+
Content: publicKeyContent,
372+
Fingerprint: fingerprint,
373+
HasUsed: true,
374+
}
375+
return verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, sshPubKey, committerUser, signerUser, committerGitEmail)
376+
}
377+
362378
// ParseCommitWithSSHSignature check if signature is good against keystore.
363-
func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *user_model.User) *asymkey_model.CommitVerification {
379+
func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committerUser *user_model.User) *asymkey_model.CommitVerification {
364380
// Now try to associate the signature with the committer, if present
365-
if committer.ID != 0 {
381+
if committerUser.ID != 0 {
366382
keys, err := db.Find[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
367-
OwnerID: committer.ID,
383+
OwnerID: committerUser.ID,
368384
NotKeytype: asymkey_model.KeyTypePrincipal,
369385
})
370386
if err != nil { // Skipping failed to get ssh keys of user
371387
log.Error("ListPublicKeys: %v", err)
372388
return &asymkey_model.CommitVerification{
373-
CommittingUser: committer,
389+
CommittingUser: committerUser,
374390
Verified: false,
375391
Reason: "gpg.error.failed_retrieval_gpg_keys",
376392
}
377393
}
378394

379-
committerEmailAddresses, err := cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, committer.ID, user_model.GetEmailAddresses)
395+
committerEmailAddresses, err := cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, committerUser.ID, user_model.GetEmailAddresses)
380396
if err != nil {
381397
log.Error("GetEmailAddresses: %v", err)
382398
}
@@ -391,64 +407,51 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
391407

392408
for _, k := range keys {
393409
if k.Verified && activated {
394-
commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, k, committer, committer, c.Committer.Email)
410+
commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, k, committerUser, committerUser, c.Committer.Email)
395411
if commitVerification != nil {
396412
return commitVerification
397413
}
398414
}
399415
}
400416
}
401-
// Trust more than one key for every User
417+
418+
// Try the pre-set trusted keys (for key-rotation purpose)
402419
for _, k := range setting.Repository.Signing.TrustedSSHKeys {
403-
if fingerprint, err := asymkey_model.CalcFingerprint(k); err != nil {
404-
log.Error("Error calculating the fingerprint public key: %s %v", k, err)
405-
} else if commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, &asymkey_model.PublicKey{
406-
Verified: true,
407-
Content: k,
408-
Fingerprint: fingerprint,
409-
HasUsed: true,
410-
}, committer, committer, c.Committer.Email); commitVerification != nil {
420+
// FIXME: why here uses "commiterUser" as "signerUser" but below don't? why here uses "c.Committer.Email" but below uses "gpgSettings.Email"?
421+
signerUser := committerUser
422+
commitVerification := verifySSHCommitVerificationByInstanceKey(c, committerUser, signerUser, c.Committer.Email, k)
423+
if commitVerification != nil && commitVerification.Verified {
411424
return commitVerification
412425
}
413426
}
414427

415-
defaultReason := asymkey_model.NoKeyFound
416-
417-
// Covers ssh verification for the default SSH signing key specified in gitea config
418-
if setting.Repository.Signing.SigningFormat == git.KeyTypeSSH && setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" {
419-
// OK we should try the default key
428+
// Try the configured instance-wide SSH public key
429+
if setting.Repository.Signing.SigningFormat == git.KeyTypeSSH && !slices.Contains([]string{"", "default", "none"}, setting.Repository.Signing.SigningKey) {
420430
gpgSettings := git.GPGSettings{
421431
Sign: true,
422432
KeyID: setting.Repository.Signing.SigningKey,
423433
Name: setting.Repository.Signing.SigningName,
424434
Email: setting.Repository.Signing.SigningEmail,
425435
Format: setting.Repository.Signing.SigningFormat,
426436
}
427-
if err := gpgSettings.LoadPublicKeyContent(); err != nil {
428-
log.Error("Error getting default signing key: %s %v", gpgSettings.KeyID, err)
429-
} else if fingerprint, err := asymkey_model.CalcFingerprint(gpgSettings.PublicKeyContent); err != nil {
430-
log.Error("Error calculating the fingerprint public key: %s %v", gpgSettings.KeyID, err)
431-
} else if commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, &asymkey_model.PublicKey{
432-
Verified: true,
433-
Content: gpgSettings.PublicKeyContent,
434-
Fingerprint: fingerprint,
435-
HasUsed: true,
436-
}, committer, &user_model.User{
437+
signerUser := &user_model.User{
437438
Name: gpgSettings.Name,
438439
Email: gpgSettings.Email,
439-
}, gpgSettings.Email); commitVerification != nil {
440-
if commitVerification.Reason == asymkey_model.BadSignature {
441-
defaultReason = asymkey_model.BadSignature
442-
} else {
440+
}
441+
if err := gpgSettings.LoadPublicKeyContent(); err != nil {
442+
log.Error("Error getting instance-wide SSH signing key %q, err: %v", gpgSettings.KeyID, err)
443+
} else {
444+
commitVerification := verifySSHCommitVerificationByInstanceKey(c, committerUser, signerUser, gpgSettings.Email, gpgSettings.PublicKeyContent)
445+
if commitVerification != nil && commitVerification.Verified {
443446
return commitVerification
444447
}
445448
}
446449
}
447450

448451
return &asymkey_model.CommitVerification{
449-
CommittingUser: committer,
452+
CommittingUser: committerUser,
450453
Verified: false,
451-
Reason: defaultReason,
454+
Reason: asymkey_model.NoKeyFound,
452455
}
453456
}
454457

services/asymkey/commit_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package asymkey
5+
6+
import (
7+
"strings"
8+
"testing"
9+
10+
user_model "code.gitea.io/gitea/models/user"
11+
"code.gitea.io/gitea/modules/git"
12+
"code.gitea.io/gitea/modules/setting"
13+
"code.gitea.io/gitea/modules/test"
14+
15+
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
func TestParseCommitWithSSHSignature(t *testing.T) {
20+
// Here we only test the TrustedSSHKeys. The complete signing test is in tests/integration/gpg_ssh_git_test.go
21+
t.Run("TrustedSSHKey", func(t *testing.T) {
22+
defer test.MockVariableValue(&setting.Repository.Signing.TrustedSSHKeys, []string{"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH6Y4idVaW3E+bLw1uqoAfJD7o5Siu+HqS51E9oQLPE9"})()
23+
24+
commit, err := git.CommitFromReader(nil, git.Sha1ObjectFormat.EmptyObjectID(), strings.NewReader(`tree 9a93ffa76e8b72bdb6431910b3a506fa2b39f42e
25+
author User Two <[email protected]> 1749230009 +0200
26+
committer User Two <[email protected]> 1749230009 +0200
27+
gpgsig -----BEGIN SSH SIGNATURE-----
28+
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgfpjiJ1VpbcT5svDW6qgB8kPujl
29+
KK74epLnUT2hAs8T0AAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
30+
AAAAQDX2t2iHuuLxEWHLJetYXKsgayv3c43r0pJNfAzdLN55Q65pC5M7rG6++gT2bxcpOu
31+
Y6EXbpLqia9sunEF3+LQY=
32+
-----END SSH SIGNATURE-----
33+
34+
Initial commit with signed file
35+
`))
36+
require.NoError(t, err)
37+
ret := ParseCommitWithSSHSignature(t.Context(), commit, &user_model.User{Name: "foo", Email: "[email protected]"})
38+
require.NotNil(t, ret)
39+
assert.True(t, ret.Verified)
40+
assert.False(t, ret.Warning)
41+
assert.NotNil(t, ret.CommittingUser) // FIXME: test the CommittingUser and SigningUser correctly
42+
assert.NotNil(t, ret.SigningUser) // FIXME: test the CommittingUser and SigningUser correctly
43+
})
44+
}

tests/gitea-repositories-meta/user2/repo-test-trusted-ssh-keys.git/HEAD

Lines changed: 0 additions & 1 deletion
This file was deleted.

tests/gitea-repositories-meta/user2/repo-test-trusted-ssh-keys.git/config

Lines changed: 0 additions & 6 deletions
This file was deleted.

tests/gitea-repositories-meta/user2/repo-test-trusted-ssh-keys.git/description

Lines changed: 0 additions & 1 deletion
This file was deleted.

tests/gitea-repositories-meta/user2/repo-test-trusted-ssh-keys.git/info/exclude

Lines changed: 0 additions & 6 deletions
This file was deleted.

tests/gitea-repositories-meta/user2/repo-test-trusted-ssh-keys.git/objects/b7/fca3aa0d80144f9718b0486681944f9c587e33

Lines changed: 0 additions & 4 deletions
This file was deleted.

tests/gitea-repositories-meta/user2/repo-test-trusted-ssh-keys.git/packed-refs

Lines changed: 0 additions & 1 deletion
This file was deleted.

tests/gitea-repositories-meta/user2/repo-test-trusted-ssh-keys.git/refs/heads/master

Lines changed: 0 additions & 1 deletion
This file was deleted.

tests/integration/api_repo_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,9 @@ func TestAPISearchRepo(t *testing.T) {
9494
}{
9595
{
9696
name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{
97-
nil: {count: 37},
98-
user: {count: 37},
99-
user2: {count: 37},
97+
nil: {count: 36},
98+
user: {count: 36},
99+
user2: {count: 36},
100100
},
101101
},
102102
{

tests/integration/gpg_ssh_git_test.go

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -300,23 +300,3 @@ func importTestingKey() (*openpgp.Entity, error) {
300300
// There should only be one entity in this file.
301301
return keyring[0], nil
302302
}
303-
304-
func TestTrustedSSHKeys(t *testing.T) {
305-
defer tests.PrepareTestEnv(t)()
306-
defer test.MockVariableValue(&setting.Repository.Signing.TrustedSSHKeys, []string{"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH6Y4idVaW3E+bLw1uqoAfJD7o5Siu+HqS51E9oQLPE9"})()
307-
308-
username := "user2"
309-
testCtx := NewAPITestContext(t, username, "repo-test-trusted-ssh-keys", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
310-
t.Run("CheckMasterBranchSignedVerified", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
311-
require.NotNil(t, branch.Commit, "no commit provided with branch! %v", branch)
312-
require.NotNil(t, branch.Commit.Verification, "no verification provided with branch commit! %v", branch.Commit)
313-
require.True(t, branch.Commit.Verification.Verified)
314-
}))
315-
316-
setting.Repository.Signing.TrustedSSHKeys = []string{}
317-
t.Run("CheckMasterBranchSignedUnverified", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
318-
require.NotNil(t, branch.Commit, "no commit provided with branch! %v", branch)
319-
require.NotNil(t, branch.Commit.Verification, "no verification provided with branch commit! %v", branch.Commit)
320-
require.False(t, branch.Commit.Verification.Verified)
321-
}))
322-
}

tests/integration/oauth_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -691,10 +691,6 @@ func TestOAuth_GrantScopesReadRepositoryFailOrganization(t *testing.T) {
691691
FullRepoName: "user2/commitsonpr",
692692
Private: false,
693693
},
694-
{
695-
FullRepoName: "user2/repo-test-trusted-ssh-keys",
696-
Private: false,
697-
},
698694
}
699695
assert.Equal(t, reposExpected, reposCaptured)
700696

0 commit comments

Comments
 (0)