You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

gpg_git_test.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "encoding/base64"
  6. "fmt"
  7. "net/url"
  8. "os"
  9. "testing"
  10. auth_model "code.gitea.io/gitea/models/auth"
  11. "code.gitea.io/gitea/models/unittest"
  12. user_model "code.gitea.io/gitea/models/user"
  13. "code.gitea.io/gitea/modules/process"
  14. "code.gitea.io/gitea/modules/setting"
  15. api "code.gitea.io/gitea/modules/structs"
  16. "code.gitea.io/gitea/modules/test"
  17. "code.gitea.io/gitea/tests"
  18. "github.com/stretchr/testify/assert"
  19. "golang.org/x/crypto/openpgp"
  20. "golang.org/x/crypto/openpgp/armor"
  21. )
  22. func TestGPGGit(t *testing.T) {
  23. tmpDir := t.TempDir() // use a temp dir to avoid messing with the user's GPG keyring
  24. err := os.Chmod(tmpDir, 0o700)
  25. assert.NoError(t, err)
  26. oldGNUPGHome := os.Getenv("GNUPGHOME")
  27. err = os.Setenv("GNUPGHOME", tmpDir)
  28. assert.NoError(t, err)
  29. defer os.Setenv("GNUPGHOME", oldGNUPGHome)
  30. // Need to create a root key
  31. rootKeyPair, err := importTestingKey(tmpDir, "gitea", "gitea@fake.local")
  32. if !assert.NoError(t, err, "importTestingKey") {
  33. return
  34. }
  35. defer test.MockVariableValue(&setting.Repository.Signing.SigningKey, rootKeyPair.PrimaryKey.KeyIdShortString())()
  36. defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "gitea")()
  37. defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "gitea@fake.local")()
  38. defer test.MockVariableValue(&setting.Repository.Signing.InitialCommit, []string{"never"})()
  39. defer test.MockVariableValue(&setting.Repository.Signing.CRUDActions, []string{"never"})()
  40. username := "user2"
  41. user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: username})
  42. baseAPITestContext := NewAPITestContext(t, username, "repo1")
  43. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  44. u.Path = baseAPITestContext.GitPath()
  45. t.Run("Unsigned-Initial", func(t *testing.T) {
  46. defer tests.PrintCurrentTest(t)()
  47. testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  48. t.Run("CreateRepository", doAPICreateRepository(testCtx, false))
  49. t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
  50. assert.NotNil(t, branch.Commit)
  51. assert.NotNil(t, branch.Commit.Verification)
  52. assert.False(t, branch.Commit.Verification.Verified)
  53. assert.Empty(t, branch.Commit.Verification.Signature)
  54. }))
  55. t.Run("CreateCRUDFile-Never", crudActionCreateFile(
  56. t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) {
  57. assert.False(t, response.Verification.Verified)
  58. }))
  59. t.Run("CreateCRUDFile-Never", crudActionCreateFile(
  60. t, testCtx, user, "never", "never2", "unsigned-never2.txt", func(t *testing.T, response api.FileResponse) {
  61. assert.False(t, response.Verification.Verified)
  62. }))
  63. })
  64. setting.Repository.Signing.CRUDActions = []string{"parentsigned"}
  65. t.Run("Unsigned-Initial-CRUD-ParentSigned", func(t *testing.T) {
  66. defer tests.PrintCurrentTest(t)()
  67. testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  68. t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile(
  69. t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) {
  70. assert.False(t, response.Verification.Verified)
  71. }))
  72. t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile(
  73. t, testCtx, user, "parentsigned", "parentsigned2", "signed-parent2.txt", func(t *testing.T, response api.FileResponse) {
  74. assert.False(t, response.Verification.Verified)
  75. }))
  76. })
  77. setting.Repository.Signing.CRUDActions = []string{"never"}
  78. t.Run("Unsigned-Initial-CRUD-Never", func(t *testing.T) {
  79. defer tests.PrintCurrentTest(t)()
  80. testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  81. t.Run("CreateCRUDFile-Never", crudActionCreateFile(
  82. t, testCtx, user, "parentsigned", "parentsigned-never", "unsigned-never2.txt", func(t *testing.T, response api.FileResponse) {
  83. assert.False(t, response.Verification.Verified)
  84. }))
  85. })
  86. setting.Repository.Signing.CRUDActions = []string{"always"}
  87. t.Run("Unsigned-Initial-CRUD-Always", func(t *testing.T) {
  88. defer tests.PrintCurrentTest(t)()
  89. testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  90. t.Run("CreateCRUDFile-Always", crudActionCreateFile(
  91. t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) {
  92. assert.NotNil(t, response.Verification)
  93. if response.Verification == nil {
  94. assert.FailNow(t, "no verification provided with response! %v", response)
  95. return
  96. }
  97. assert.True(t, response.Verification.Verified)
  98. if !response.Verification.Verified {
  99. t.FailNow()
  100. return
  101. }
  102. assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
  103. }))
  104. t.Run("CreateCRUDFile-ParentSigned-always", crudActionCreateFile(
  105. t, testCtx, user, "parentsigned", "parentsigned-always", "signed-parent2.txt", func(t *testing.T, response api.FileResponse) {
  106. assert.NotNil(t, response.Verification)
  107. if response.Verification == nil {
  108. assert.FailNow(t, "no verification provided with response! %v", response)
  109. return
  110. }
  111. assert.True(t, response.Verification.Verified)
  112. if !response.Verification.Verified {
  113. t.FailNow()
  114. return
  115. }
  116. assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
  117. }))
  118. })
  119. setting.Repository.Signing.CRUDActions = []string{"parentsigned"}
  120. t.Run("Unsigned-Initial-CRUD-ParentSigned", func(t *testing.T) {
  121. defer tests.PrintCurrentTest(t)()
  122. testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  123. t.Run("CreateCRUDFile-Always-ParentSigned", crudActionCreateFile(
  124. t, testCtx, user, "always", "always-parentsigned", "signed-always-parentsigned.txt", func(t *testing.T, response api.FileResponse) {
  125. assert.NotNil(t, response.Verification)
  126. if response.Verification == nil {
  127. assert.FailNow(t, "no verification provided with response! %v", response)
  128. return
  129. }
  130. assert.True(t, response.Verification.Verified)
  131. if !response.Verification.Verified {
  132. t.FailNow()
  133. return
  134. }
  135. assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
  136. }))
  137. })
  138. setting.Repository.Signing.InitialCommit = []string{"always"}
  139. t.Run("AlwaysSign-Initial", func(t *testing.T) {
  140. defer tests.PrintCurrentTest(t)()
  141. testCtx := NewAPITestContext(t, username, "initial-always", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  142. t.Run("CreateRepository", doAPICreateRepository(testCtx, false))
  143. t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
  144. assert.NotNil(t, branch.Commit)
  145. if branch.Commit == nil {
  146. assert.FailNow(t, "no commit provided with branch! %v", branch)
  147. return
  148. }
  149. assert.NotNil(t, branch.Commit.Verification)
  150. if branch.Commit.Verification == nil {
  151. assert.FailNow(t, "no verification provided with branch commit! %v", branch.Commit)
  152. return
  153. }
  154. assert.True(t, branch.Commit.Verification.Verified)
  155. if !branch.Commit.Verification.Verified {
  156. t.FailNow()
  157. return
  158. }
  159. assert.Equal(t, "gitea@fake.local", branch.Commit.Verification.Signer.Email)
  160. }))
  161. })
  162. setting.Repository.Signing.CRUDActions = []string{"never"}
  163. t.Run("AlwaysSign-Initial-CRUD-Never", func(t *testing.T) {
  164. defer tests.PrintCurrentTest(t)()
  165. testCtx := NewAPITestContext(t, username, "initial-always-never", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  166. t.Run("CreateRepository", doAPICreateRepository(testCtx, false))
  167. t.Run("CreateCRUDFile-Never", crudActionCreateFile(
  168. t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) {
  169. assert.False(t, response.Verification.Verified)
  170. }))
  171. })
  172. setting.Repository.Signing.CRUDActions = []string{"parentsigned"}
  173. t.Run("AlwaysSign-Initial-CRUD-ParentSigned-On-Always", func(t *testing.T) {
  174. defer tests.PrintCurrentTest(t)()
  175. testCtx := NewAPITestContext(t, username, "initial-always-parent", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  176. t.Run("CreateRepository", doAPICreateRepository(testCtx, false))
  177. t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile(
  178. t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) {
  179. assert.True(t, response.Verification.Verified)
  180. if !response.Verification.Verified {
  181. t.FailNow()
  182. return
  183. }
  184. assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
  185. }))
  186. })
  187. setting.Repository.Signing.CRUDActions = []string{"always"}
  188. t.Run("AlwaysSign-Initial-CRUD-Always", func(t *testing.T) {
  189. defer tests.PrintCurrentTest(t)()
  190. testCtx := NewAPITestContext(t, username, "initial-always-always", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  191. t.Run("CreateRepository", doAPICreateRepository(testCtx, false))
  192. t.Run("CreateCRUDFile-Always", crudActionCreateFile(
  193. t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) {
  194. assert.True(t, response.Verification.Verified)
  195. if !response.Verification.Verified {
  196. t.FailNow()
  197. return
  198. }
  199. assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
  200. }))
  201. })
  202. setting.Repository.Signing.Merges = []string{"commitssigned"}
  203. t.Run("UnsignedMerging", func(t *testing.T) {
  204. defer tests.PrintCurrentTest(t)()
  205. testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  206. t.Run("CreatePullRequest", func(t *testing.T) {
  207. pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "never2")(t)
  208. assert.NoError(t, err)
  209. t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index))
  210. })
  211. t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
  212. assert.NotNil(t, branch.Commit)
  213. assert.NotNil(t, branch.Commit.Verification)
  214. assert.False(t, branch.Commit.Verification.Verified)
  215. assert.Empty(t, branch.Commit.Verification.Signature)
  216. }))
  217. })
  218. setting.Repository.Signing.Merges = []string{"basesigned"}
  219. t.Run("BaseSignedMerging", func(t *testing.T) {
  220. defer tests.PrintCurrentTest(t)()
  221. testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  222. t.Run("CreatePullRequest", func(t *testing.T) {
  223. pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "parentsigned2")(t)
  224. assert.NoError(t, err)
  225. t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index))
  226. })
  227. t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
  228. assert.NotNil(t, branch.Commit)
  229. assert.NotNil(t, branch.Commit.Verification)
  230. assert.False(t, branch.Commit.Verification.Verified)
  231. assert.Empty(t, branch.Commit.Verification.Signature)
  232. }))
  233. })
  234. setting.Repository.Signing.Merges = []string{"commitssigned"}
  235. t.Run("CommitsSignedMerging", func(t *testing.T) {
  236. defer tests.PrintCurrentTest(t)()
  237. testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  238. t.Run("CreatePullRequest", func(t *testing.T) {
  239. pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "always-parentsigned")(t)
  240. assert.NoError(t, err)
  241. t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index))
  242. })
  243. t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
  244. assert.NotNil(t, branch.Commit)
  245. assert.NotNil(t, branch.Commit.Verification)
  246. assert.True(t, branch.Commit.Verification.Verified)
  247. }))
  248. })
  249. })
  250. }
  251. func crudActionCreateFile(t *testing.T, ctx APITestContext, user *user_model.User, from, to, path string, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) {
  252. return doAPICreateFile(ctx, path, &api.CreateFileOptions{
  253. FileOptions: api.FileOptions{
  254. BranchName: from,
  255. NewBranchName: to,
  256. Message: fmt.Sprintf("from:%s to:%s path:%s", from, to, path),
  257. Author: api.Identity{
  258. Name: user.FullName,
  259. Email: user.Email,
  260. },
  261. Committer: api.Identity{
  262. Name: user.FullName,
  263. Email: user.Email,
  264. },
  265. },
  266. ContentBase64: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("This is new text for %s", path))),
  267. }, callback...)
  268. }
  269. func importTestingKey(tmpDir, name, email string) (*openpgp.Entity, error) {
  270. if _, _, err := process.GetManager().Exec("gpg --import tests/integration/private-testing.key", "gpg", "--import", "tests/integration/private-testing.key"); err != nil {
  271. return nil, err
  272. }
  273. keyringFile, err := os.Open("tests/integration/private-testing.key")
  274. if err != nil {
  275. return nil, err
  276. }
  277. defer keyringFile.Close()
  278. block, err := armor.Decode(keyringFile)
  279. if err != nil {
  280. return nil, err
  281. }
  282. keyring, err := openpgp.ReadKeyRing(block.Body)
  283. if err != nil {
  284. return nil, fmt.Errorf("Keyring access failed: '%w'", err)
  285. }
  286. // There should only be one entity in this file.
  287. return keyring[0], nil
  288. }