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.

216 lines
5.3 KiB

Sign merges, CRUD, Wiki and Repository initialisation with gpg key (#7631) This PR fixes #7598 by providing a configurable way of signing commits across the Gitea instance. Per repository configurability and import/generation of trusted secure keys is not provided by this PR - from a security PoV that's probably impossible to do properly. Similarly web-signing, that is asking the user to sign something, is not implemented - this could be done at a later stage however. ## Features - [x] If commit.gpgsign is set in .gitconfig sign commits and files created through repofiles. (merges should already have been signed.) - [x] Verify commits signed with the default gpg as valid - [x] Signer, Committer and Author can all be different - [x] Allow signer to be arbitrarily different - We still require the key to have an activated email on Gitea. A more complete implementation would be to use a keyserver and mark external-or-unactivated with an "unknown" trust level icon. - [x] Add a signing-key.gpg endpoint to get the default gpg pub key if available - Rather than add a fake web-flow user I've added this as an endpoint on /api/v1/signing-key.gpg - [x] Try to match the default key with a user on gitea - this is done at verification time - [x] Make things configurable? - app.ini configuration done - [x] when checking commits are signed need to check if they're actually verifiable too - [x] Add documentation I have decided that adjusting the docker to create a default gpg key is not the correct thing to do and therefore have not implemented this.
5 years ago
Sign merges, CRUD, Wiki and Repository initialisation with gpg key (#7631) This PR fixes #7598 by providing a configurable way of signing commits across the Gitea instance. Per repository configurability and import/generation of trusted secure keys is not provided by this PR - from a security PoV that's probably impossible to do properly. Similarly web-signing, that is asking the user to sign something, is not implemented - this could be done at a later stage however. ## Features - [x] If commit.gpgsign is set in .gitconfig sign commits and files created through repofiles. (merges should already have been signed.) - [x] Verify commits signed with the default gpg as valid - [x] Signer, Committer and Author can all be different - [x] Allow signer to be arbitrarily different - We still require the key to have an activated email on Gitea. A more complete implementation would be to use a keyserver and mark external-or-unactivated with an "unknown" trust level icon. - [x] Add a signing-key.gpg endpoint to get the default gpg pub key if available - Rather than add a fake web-flow user I've added this as an endpoint on /api/v1/signing-key.gpg - [x] Try to match the default key with a user on gitea - this is done at verification time - [x] Make things configurable? - app.ini configuration done - [x] when checking commits are signed need to check if they're actually verifiable too - [x] Add documentation I have decided that adjusting the docker to create a default gpg key is not the correct thing to do and therefore have not implemented this.
5 years ago
Sign merges, CRUD, Wiki and Repository initialisation with gpg key (#7631) This PR fixes #7598 by providing a configurable way of signing commits across the Gitea instance. Per repository configurability and import/generation of trusted secure keys is not provided by this PR - from a security PoV that's probably impossible to do properly. Similarly web-signing, that is asking the user to sign something, is not implemented - this could be done at a later stage however. ## Features - [x] If commit.gpgsign is set in .gitconfig sign commits and files created through repofiles. (merges should already have been signed.) - [x] Verify commits signed with the default gpg as valid - [x] Signer, Committer and Author can all be different - [x] Allow signer to be arbitrarily different - We still require the key to have an activated email on Gitea. A more complete implementation would be to use a keyserver and mark external-or-unactivated with an "unknown" trust level icon. - [x] Add a signing-key.gpg endpoint to get the default gpg pub key if available - Rather than add a fake web-flow user I've added this as an endpoint on /api/v1/signing-key.gpg - [x] Try to match the default key with a user on gitea - this is done at verification time - [x] Make things configurable? - app.ini configuration done - [x] when checking commits are signed need to check if they're actually verifiable too - [x] Add documentation I have decided that adjusting the docker to create a default gpg key is not the correct thing to do and therefore have not implemented this.
5 years ago
Sign merges, CRUD, Wiki and Repository initialisation with gpg key (#7631) This PR fixes #7598 by providing a configurable way of signing commits across the Gitea instance. Per repository configurability and import/generation of trusted secure keys is not provided by this PR - from a security PoV that's probably impossible to do properly. Similarly web-signing, that is asking the user to sign something, is not implemented - this could be done at a later stage however. ## Features - [x] If commit.gpgsign is set in .gitconfig sign commits and files created through repofiles. (merges should already have been signed.) - [x] Verify commits signed with the default gpg as valid - [x] Signer, Committer and Author can all be different - [x] Allow signer to be arbitrarily different - We still require the key to have an activated email on Gitea. A more complete implementation would be to use a keyserver and mark external-or-unactivated with an "unknown" trust level icon. - [x] Add a signing-key.gpg endpoint to get the default gpg pub key if available - Rather than add a fake web-flow user I've added this as an endpoint on /api/v1/signing-key.gpg - [x] Try to match the default key with a user on gitea - this is done at verification time - [x] Make things configurable? - app.ini configuration done - [x] when checking commits are signed need to check if they're actually verifiable too - [x] Add documentation I have decided that adjusting the docker to create a default gpg key is not the correct thing to do and therefore have not implemented this.
5 years ago
Sign merges, CRUD, Wiki and Repository initialisation with gpg key (#7631) This PR fixes #7598 by providing a configurable way of signing commits across the Gitea instance. Per repository configurability and import/generation of trusted secure keys is not provided by this PR - from a security PoV that's probably impossible to do properly. Similarly web-signing, that is asking the user to sign something, is not implemented - this could be done at a later stage however. ## Features - [x] If commit.gpgsign is set in .gitconfig sign commits and files created through repofiles. (merges should already have been signed.) - [x] Verify commits signed with the default gpg as valid - [x] Signer, Committer and Author can all be different - [x] Allow signer to be arbitrarily different - We still require the key to have an activated email on Gitea. A more complete implementation would be to use a keyserver and mark external-or-unactivated with an "unknown" trust level icon. - [x] Add a signing-key.gpg endpoint to get the default gpg pub key if available - Rather than add a fake web-flow user I've added this as an endpoint on /api/v1/signing-key.gpg - [x] Try to match the default key with a user on gitea - this is done at verification time - [x] Make things configurable? - app.ini configuration done - [x] when checking commits are signed need to check if they're actually verifiable too - [x] Add documentation I have decided that adjusting the docker to create a default gpg key is not the correct thing to do and therefore have not implemented this.
5 years ago
  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 models
  5. import (
  6. "strings"
  7. "code.gitea.io/gitea/modules/git"
  8. "code.gitea.io/gitea/modules/log"
  9. "code.gitea.io/gitea/modules/process"
  10. "code.gitea.io/gitea/modules/setting"
  11. )
  12. type signingMode string
  13. const (
  14. never signingMode = "never"
  15. always signingMode = "always"
  16. pubkey signingMode = "pubkey"
  17. twofa signingMode = "twofa"
  18. parentSigned signingMode = "parentsigned"
  19. baseSigned signingMode = "basesigned"
  20. headSigned signingMode = "headsigned"
  21. commitsSigned signingMode = "commitssigned"
  22. approved signingMode = "approved"
  23. )
  24. func signingModeFromStrings(modeStrings []string) []signingMode {
  25. returnable := make([]signingMode, 0, len(modeStrings))
  26. for _, mode := range modeStrings {
  27. signMode := signingMode(strings.ToLower(mode))
  28. switch signMode {
  29. case never:
  30. return []signingMode{never}
  31. case always:
  32. return []signingMode{always}
  33. case pubkey:
  34. fallthrough
  35. case twofa:
  36. fallthrough
  37. case parentSigned:
  38. fallthrough
  39. case baseSigned:
  40. fallthrough
  41. case headSigned:
  42. fallthrough
  43. case approved:
  44. fallthrough
  45. case commitsSigned:
  46. returnable = append(returnable, signMode)
  47. }
  48. }
  49. if len(returnable) == 0 {
  50. return []signingMode{never}
  51. }
  52. return returnable
  53. }
  54. func signingKey(repoPath string) string {
  55. if setting.Repository.Signing.SigningKey == "none" {
  56. return ""
  57. }
  58. if setting.Repository.Signing.SigningKey == "default" || setting.Repository.Signing.SigningKey == "" {
  59. // Can ignore the error here as it means that commit.gpgsign is not set
  60. value, _ := git.NewCommand("config", "--get", "commit.gpgsign").RunInDir(repoPath)
  61. sign, valid := git.ParseBool(strings.TrimSpace(value))
  62. if !sign || !valid {
  63. return ""
  64. }
  65. signingKey, _ := git.NewCommand("config", "--get", "user.signingkey").RunInDir(repoPath)
  66. return strings.TrimSpace(signingKey)
  67. }
  68. return setting.Repository.Signing.SigningKey
  69. }
  70. // PublicSigningKey gets the public signing key within a provided repository directory
  71. func PublicSigningKey(repoPath string) (string, error) {
  72. signingKey := signingKey(repoPath)
  73. if signingKey == "" {
  74. return "", nil
  75. }
  76. content, stderr, err := process.GetManager().ExecDir(-1, repoPath,
  77. "gpg --export -a", "gpg", "--export", "-a", signingKey)
  78. if err != nil {
  79. log.Error("Unable to get default signing key in %s: %s, %s, %v", repoPath, signingKey, stderr, err)
  80. return "", err
  81. }
  82. return content, nil
  83. }
  84. // SignInitialCommit determines if we should sign the initial commit to this repository
  85. func SignInitialCommit(repoPath string, u *User) (bool, string) {
  86. rules := signingModeFromStrings(setting.Repository.Signing.InitialCommit)
  87. signingKey := signingKey(repoPath)
  88. if signingKey == "" {
  89. return false, ""
  90. }
  91. for _, rule := range rules {
  92. switch rule {
  93. case never:
  94. return false, ""
  95. case always:
  96. break
  97. case pubkey:
  98. keys, err := ListGPGKeys(u.ID)
  99. if err != nil || len(keys) == 0 {
  100. return false, ""
  101. }
  102. case twofa:
  103. twofa, err := GetTwoFactorByUID(u.ID)
  104. if err != nil || twofa == nil {
  105. return false, ""
  106. }
  107. }
  108. }
  109. return true, signingKey
  110. }
  111. // SignWikiCommit determines if we should sign the commits to this repository wiki
  112. func (repo *Repository) SignWikiCommit(u *User) (bool, string) {
  113. rules := signingModeFromStrings(setting.Repository.Signing.Wiki)
  114. signingKey := signingKey(repo.WikiPath())
  115. if signingKey == "" {
  116. return false, ""
  117. }
  118. for _, rule := range rules {
  119. switch rule {
  120. case never:
  121. return false, ""
  122. case always:
  123. break
  124. case pubkey:
  125. keys, err := ListGPGKeys(u.ID)
  126. if err != nil || len(keys) == 0 {
  127. return false, ""
  128. }
  129. case twofa:
  130. twofa, err := GetTwoFactorByUID(u.ID)
  131. if err != nil || twofa == nil {
  132. return false, ""
  133. }
  134. case parentSigned:
  135. gitRepo, err := git.OpenRepository(repo.WikiPath())
  136. if err != nil {
  137. return false, ""
  138. }
  139. defer gitRepo.Close()
  140. commit, err := gitRepo.GetCommit("HEAD")
  141. if err != nil {
  142. return false, ""
  143. }
  144. if commit.Signature == nil {
  145. return false, ""
  146. }
  147. verification := ParseCommitWithSignature(commit)
  148. if !verification.Verified {
  149. return false, ""
  150. }
  151. }
  152. }
  153. return true, signingKey
  154. }
  155. // SignCRUDAction determines if we should sign a CRUD commit to this repository
  156. func (repo *Repository) SignCRUDAction(u *User, tmpBasePath, parentCommit string) (bool, string) {
  157. rules := signingModeFromStrings(setting.Repository.Signing.CRUDActions)
  158. signingKey := signingKey(repo.RepoPath())
  159. if signingKey == "" {
  160. return false, ""
  161. }
  162. for _, rule := range rules {
  163. switch rule {
  164. case never:
  165. return false, ""
  166. case always:
  167. break
  168. case pubkey:
  169. keys, err := ListGPGKeys(u.ID)
  170. if err != nil || len(keys) == 0 {
  171. return false, ""
  172. }
  173. case twofa:
  174. twofa, err := GetTwoFactorByUID(u.ID)
  175. if err != nil || twofa == nil {
  176. return false, ""
  177. }
  178. case parentSigned:
  179. gitRepo, err := git.OpenRepository(tmpBasePath)
  180. if err != nil {
  181. return false, ""
  182. }
  183. defer gitRepo.Close()
  184. commit, err := gitRepo.GetCommit(parentCommit)
  185. if err != nil {
  186. return false, ""
  187. }
  188. if commit.Signature == nil {
  189. return false, ""
  190. }
  191. verification := ParseCommitWithSignature(commit)
  192. if !verification.Verified {
  193. return false, ""
  194. }
  195. }
  196. }
  197. return true, signingKey
  198. }