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 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package integrations
  5. import (
  6. "encoding/base64"
  7. "fmt"
  8. "io/ioutil"
  9. "net/url"
  10. "os"
  11. "path/filepath"
  12. "testing"
  13. "code.gitea.io/gitea/models"
  14. "code.gitea.io/gitea/modules/process"
  15. "code.gitea.io/gitea/modules/setting"
  16. api "code.gitea.io/gitea/modules/structs"
  17. "github.com/stretchr/testify/assert"
  18. "golang.org/x/crypto/openpgp"
  19. "golang.org/x/crypto/openpgp/armor"
  20. )
  21. func TestGPGGit(t *testing.T) {
  22. prepareTestEnv(t)
  23. username := "user2"
  24. // OK Set a new GPG home
  25. tmpDir, err := ioutil.TempDir("", "temp-gpg")
  26. assert.NoError(t, err)
  27. defer os.RemoveAll(tmpDir)
  28. err = os.Chmod(tmpDir, 0700)
  29. assert.NoError(t, err)
  30. oldGNUPGHome := os.Getenv("GNUPGHOME")
  31. err = os.Setenv("GNUPGHOME", tmpDir)
  32. assert.NoError(t, err)
  33. defer os.Setenv("GNUPGHOME", oldGNUPGHome)
  34. // Need to create a root key
  35. rootKeyPair, err := createGPGKey(tmpDir, "gitea", "gitea@fake.local")
  36. assert.NoError(t, err)
  37. rootKeyID := rootKeyPair.PrimaryKey.KeyIdShortString()
  38. oldKeyID := setting.Repository.Signing.SigningKey
  39. oldName := setting.Repository.Signing.SigningName
  40. oldEmail := setting.Repository.Signing.SigningEmail
  41. defer func() {
  42. setting.Repository.Signing.SigningKey = oldKeyID
  43. setting.Repository.Signing.SigningName = oldName
  44. setting.Repository.Signing.SigningEmail = oldEmail
  45. }()
  46. setting.Repository.Signing.SigningKey = rootKeyID
  47. setting.Repository.Signing.SigningName = "gitea"
  48. setting.Repository.Signing.SigningEmail = "gitea@fake.local"
  49. user := models.AssertExistsAndLoadBean(t, &models.User{Name: username}).(*models.User)
  50. setting.Repository.Signing.InitialCommit = []string{"never"}
  51. setting.Repository.Signing.CRUDActions = []string{"never"}
  52. baseAPITestContext := NewAPITestContext(t, username, "repo1")
  53. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  54. u.Path = baseAPITestContext.GitPath()
  55. t.Run("Unsigned-Initial", func(t *testing.T) {
  56. defer PrintCurrentTest(t)()
  57. testCtx := NewAPITestContext(t, username, "initial-unsigned")
  58. t.Run("CreateRepository", doAPICreateRepository(testCtx, false))
  59. t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
  60. assert.NotNil(t, branch.Commit)
  61. assert.NotNil(t, branch.Commit.Verification)
  62. assert.False(t, branch.Commit.Verification.Verified)
  63. assert.Empty(t, branch.Commit.Verification.Signature)
  64. }))
  65. t.Run("CreateCRUDFile-Never", crudActionCreateFile(
  66. t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) {
  67. assert.False(t, response.Verification.Verified)
  68. }))
  69. t.Run("CreateCRUDFile-Never", crudActionCreateFile(
  70. t, testCtx, user, "never", "never2", "unsigned-never2.txt", func(t *testing.T, response api.FileResponse) {
  71. assert.False(t, response.Verification.Verified)
  72. }))
  73. })
  74. }, false)
  75. setting.Repository.Signing.CRUDActions = []string{"parentsigned"}
  76. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  77. u.Path = baseAPITestContext.GitPath()
  78. t.Run("Unsigned-Initial-CRUD-ParentSigned", func(t *testing.T) {
  79. defer PrintCurrentTest(t)()
  80. testCtx := NewAPITestContext(t, username, "initial-unsigned")
  81. t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile(
  82. t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) {
  83. assert.False(t, response.Verification.Verified)
  84. }))
  85. t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile(
  86. t, testCtx, user, "parentsigned", "parentsigned2", "signed-parent2.txt", func(t *testing.T, response api.FileResponse) {
  87. assert.False(t, response.Verification.Verified)
  88. }))
  89. })
  90. }, false)
  91. setting.Repository.Signing.CRUDActions = []string{"never"}
  92. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  93. u.Path = baseAPITestContext.GitPath()
  94. t.Run("Unsigned-Initial-CRUD-Never", func(t *testing.T) {
  95. defer PrintCurrentTest(t)()
  96. testCtx := NewAPITestContext(t, username, "initial-unsigned")
  97. t.Run("CreateCRUDFile-Never", crudActionCreateFile(
  98. t, testCtx, user, "parentsigned", "parentsigned-never", "unsigned-never2.txt", func(t *testing.T, response api.FileResponse) {
  99. assert.False(t, response.Verification.Verified)
  100. }))
  101. })
  102. }, false)
  103. setting.Repository.Signing.CRUDActions = []string{"always"}
  104. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  105. u.Path = baseAPITestContext.GitPath()
  106. t.Run("Unsigned-Initial-CRUD-Always", func(t *testing.T) {
  107. defer PrintCurrentTest(t)()
  108. testCtx := NewAPITestContext(t, username, "initial-unsigned")
  109. t.Run("CreateCRUDFile-Always", crudActionCreateFile(
  110. t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) {
  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. t.Run("CreateCRUDFile-ParentSigned-always", crudActionCreateFile(
  119. t, testCtx, user, "parentsigned", "parentsigned-always", "signed-parent2.txt", func(t *testing.T, response api.FileResponse) {
  120. assert.True(t, response.Verification.Verified)
  121. if !response.Verification.Verified {
  122. t.FailNow()
  123. return
  124. }
  125. assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
  126. }))
  127. })
  128. }, false)
  129. setting.Repository.Signing.CRUDActions = []string{"parentsigned"}
  130. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  131. u.Path = baseAPITestContext.GitPath()
  132. t.Run("Unsigned-Initial-CRUD-ParentSigned", func(t *testing.T) {
  133. defer PrintCurrentTest(t)()
  134. testCtx := NewAPITestContext(t, username, "initial-unsigned")
  135. t.Run("CreateCRUDFile-Always-ParentSigned", crudActionCreateFile(
  136. t, testCtx, user, "always", "always-parentsigned", "signed-always-parentsigned.txt", func(t *testing.T, response api.FileResponse) {
  137. assert.True(t, response.Verification.Verified)
  138. if !response.Verification.Verified {
  139. t.FailNow()
  140. return
  141. }
  142. assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
  143. }))
  144. })
  145. }, false)
  146. setting.Repository.Signing.InitialCommit = []string{"always"}
  147. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  148. u.Path = baseAPITestContext.GitPath()
  149. t.Run("AlwaysSign-Initial", func(t *testing.T) {
  150. defer PrintCurrentTest(t)()
  151. testCtx := NewAPITestContext(t, username, "initial-always")
  152. t.Run("CreateRepository", doAPICreateRepository(testCtx, false))
  153. t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
  154. assert.NotNil(t, branch.Commit)
  155. assert.NotNil(t, branch.Commit.Verification)
  156. assert.True(t, branch.Commit.Verification.Verified)
  157. if !branch.Commit.Verification.Verified {
  158. t.FailNow()
  159. return
  160. }
  161. assert.Equal(t, "gitea@fake.local", branch.Commit.Verification.Signer.Email)
  162. }))
  163. })
  164. }, false)
  165. setting.Repository.Signing.CRUDActions = []string{"never"}
  166. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  167. u.Path = baseAPITestContext.GitPath()
  168. t.Run("AlwaysSign-Initial-CRUD-Never", func(t *testing.T) {
  169. defer PrintCurrentTest(t)()
  170. testCtx := NewAPITestContext(t, username, "initial-always-never")
  171. t.Run("CreateRepository", doAPICreateRepository(testCtx, false))
  172. t.Run("CreateCRUDFile-Never", crudActionCreateFile(
  173. t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) {
  174. assert.False(t, response.Verification.Verified)
  175. }))
  176. })
  177. }, false)
  178. setting.Repository.Signing.CRUDActions = []string{"parentsigned"}
  179. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  180. u.Path = baseAPITestContext.GitPath()
  181. t.Run("AlwaysSign-Initial-CRUD-ParentSigned-On-Always", func(t *testing.T) {
  182. defer PrintCurrentTest(t)()
  183. testCtx := NewAPITestContext(t, username, "initial-always-parent")
  184. t.Run("CreateRepository", doAPICreateRepository(testCtx, false))
  185. t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile(
  186. t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) {
  187. assert.True(t, response.Verification.Verified)
  188. if !response.Verification.Verified {
  189. t.FailNow()
  190. return
  191. }
  192. assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
  193. }))
  194. })
  195. }, false)
  196. setting.Repository.Signing.CRUDActions = []string{"always"}
  197. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  198. u.Path = baseAPITestContext.GitPath()
  199. t.Run("AlwaysSign-Initial-CRUD-Always", func(t *testing.T) {
  200. defer PrintCurrentTest(t)()
  201. testCtx := NewAPITestContext(t, username, "initial-always-always")
  202. t.Run("CreateRepository", doAPICreateRepository(testCtx, false))
  203. t.Run("CreateCRUDFile-Always", crudActionCreateFile(
  204. t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) {
  205. assert.True(t, response.Verification.Verified)
  206. if !response.Verification.Verified {
  207. t.FailNow()
  208. return
  209. }
  210. assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email)
  211. }))
  212. })
  213. }, false)
  214. var pr api.PullRequest
  215. setting.Repository.Signing.Merges = []string{"commitssigned"}
  216. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  217. u.Path = baseAPITestContext.GitPath()
  218. t.Run("UnsignedMerging", func(t *testing.T) {
  219. defer PrintCurrentTest(t)()
  220. testCtx := NewAPITestContext(t, username, "initial-unsigned")
  221. var err error
  222. t.Run("CreatePullRequest", func(t *testing.T) {
  223. pr, err = doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "never2")(t)
  224. assert.NoError(t, err)
  225. })
  226. t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index))
  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. }, false)
  235. setting.Repository.Signing.Merges = []string{"basesigned"}
  236. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  237. u.Path = baseAPITestContext.GitPath()
  238. t.Run("BaseSignedMerging", func(t *testing.T) {
  239. defer PrintCurrentTest(t)()
  240. testCtx := NewAPITestContext(t, username, "initial-unsigned")
  241. var err error
  242. t.Run("CreatePullRequest", func(t *testing.T) {
  243. pr, err = doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "parentsigned2")(t)
  244. assert.NoError(t, err)
  245. })
  246. t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index))
  247. t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
  248. assert.NotNil(t, branch.Commit)
  249. assert.NotNil(t, branch.Commit.Verification)
  250. assert.False(t, branch.Commit.Verification.Verified)
  251. assert.Empty(t, branch.Commit.Verification.Signature)
  252. }))
  253. })
  254. }, false)
  255. setting.Repository.Signing.Merges = []string{"commitssigned"}
  256. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  257. u.Path = baseAPITestContext.GitPath()
  258. t.Run("CommitsSignedMerging", func(t *testing.T) {
  259. defer PrintCurrentTest(t)()
  260. testCtx := NewAPITestContext(t, username, "initial-unsigned")
  261. var err error
  262. t.Run("CreatePullRequest", func(t *testing.T) {
  263. pr, err = doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "always-parentsigned")(t)
  264. assert.NoError(t, err)
  265. })
  266. t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index))
  267. t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) {
  268. assert.NotNil(t, branch.Commit)
  269. assert.NotNil(t, branch.Commit.Verification)
  270. assert.True(t, branch.Commit.Verification.Verified)
  271. }))
  272. })
  273. }, false)
  274. }
  275. func crudActionCreateFile(t *testing.T, ctx APITestContext, user *models.User, from, to, path string, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) {
  276. return doAPICreateFile(ctx, path, &api.CreateFileOptions{
  277. FileOptions: api.FileOptions{
  278. BranchName: from,
  279. NewBranchName: to,
  280. Message: fmt.Sprintf("from:%s to:%s path:%s", from, to, path),
  281. Author: api.Identity{
  282. Name: user.FullName,
  283. Email: user.Email,
  284. },
  285. Committer: api.Identity{
  286. Name: user.FullName,
  287. Email: user.Email,
  288. },
  289. },
  290. Content: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("This is new text for %s", path))),
  291. }, callback...)
  292. }
  293. func createGPGKey(tmpDir, name, email string) (*openpgp.Entity, error) {
  294. keyPair, err := openpgp.NewEntity(name, "test", email, nil)
  295. if err != nil {
  296. return nil, err
  297. }
  298. for _, id := range keyPair.Identities {
  299. err := id.SelfSignature.SignUserId(id.UserId.Id, keyPair.PrimaryKey, keyPair.PrivateKey, nil)
  300. if err != nil {
  301. return nil, err
  302. }
  303. }
  304. keyFile := filepath.Join(tmpDir, "temporary.key")
  305. keyWriter, err := os.Create(keyFile)
  306. if err != nil {
  307. return nil, err
  308. }
  309. defer keyWriter.Close()
  310. defer os.Remove(keyFile)
  311. w, err := armor.Encode(keyWriter, openpgp.PrivateKeyType, nil)
  312. if err != nil {
  313. return nil, err
  314. }
  315. defer w.Close()
  316. keyPair.SerializePrivate(w, nil)
  317. if err := w.Close(); err != nil {
  318. return nil, err
  319. }
  320. if err := keyWriter.Close(); err != nil {
  321. return nil, err
  322. }
  323. if _, _, err := process.GetManager().Exec("gpg --import temporary.key", "gpg", "--import", keyFile); err != nil {
  324. return nil, err
  325. }
  326. return keyPair, nil
  327. }