gitea源码

auth.go 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package chef
  4. import (
  5. "context"
  6. "crypto"
  7. "crypto/rsa"
  8. "crypto/sha1"
  9. "crypto/sha256"
  10. "crypto/x509"
  11. "encoding/base64"
  12. "encoding/pem"
  13. "errors"
  14. "fmt"
  15. "hash"
  16. "math/big"
  17. "net/http"
  18. "path"
  19. "regexp"
  20. "slices"
  21. "strconv"
  22. "strings"
  23. "time"
  24. user_model "code.gitea.io/gitea/models/user"
  25. chef_module "code.gitea.io/gitea/modules/packages/chef"
  26. "code.gitea.io/gitea/modules/util"
  27. "code.gitea.io/gitea/services/auth"
  28. )
  29. const (
  30. maxTimeDifference = 10 * time.Minute
  31. )
  32. var (
  33. algorithmPattern = regexp.MustCompile(`algorithm=(\w+)`)
  34. versionPattern = regexp.MustCompile(`version=(\d+\.\d+)`)
  35. authorizationPattern = regexp.MustCompile(`\AX-Ops-Authorization-(\d+)`)
  36. _ auth.Method = &Auth{}
  37. )
  38. // Documentation:
  39. // https://docs.chef.io/server/api_chef_server/#required-headers
  40. // https://github.com/chef-boneyard/chef-rfc/blob/master/rfc065-sign-v1.3.md
  41. // https://github.com/chef/mixlib-authentication/blob/bc8adbef833d4be23dc78cb23e6fe44b51ebc34f/lib/mixlib/authentication/signedheaderauth.rb
  42. type Auth struct{}
  43. func (a *Auth) Name() string {
  44. return "chef"
  45. }
  46. // Verify extracts the user from the signed request
  47. // If the request is signed with the user private key the user is verified.
  48. func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataStore, sess auth.SessionStore) (*user_model.User, error) {
  49. u, err := getUserFromRequest(req)
  50. if err != nil {
  51. return nil, err
  52. }
  53. if u == nil {
  54. return nil, nil
  55. }
  56. pub, err := getUserPublicKey(req.Context(), u)
  57. if err != nil {
  58. return nil, err
  59. }
  60. if err := verifyTimestamp(req); err != nil {
  61. return nil, err
  62. }
  63. version, err := getSignVersion(req)
  64. if err != nil {
  65. return nil, err
  66. }
  67. if err := verifySignedHeaders(req, version, pub.(*rsa.PublicKey)); err != nil {
  68. return nil, err
  69. }
  70. return u, nil
  71. }
  72. func getUserFromRequest(req *http.Request) (*user_model.User, error) {
  73. username := req.Header.Get("X-Ops-Userid")
  74. if username == "" {
  75. return nil, nil
  76. }
  77. return user_model.GetUserByName(req.Context(), username)
  78. }
  79. func getUserPublicKey(ctx context.Context, u *user_model.User) (crypto.PublicKey, error) {
  80. pubKey, err := user_model.GetSetting(ctx, u.ID, chef_module.SettingPublicPem)
  81. if err != nil {
  82. return nil, err
  83. }
  84. pubPem, _ := pem.Decode([]byte(pubKey))
  85. return x509.ParsePKIXPublicKey(pubPem.Bytes)
  86. }
  87. func verifyTimestamp(req *http.Request) error {
  88. hdr := req.Header.Get("X-Ops-Timestamp")
  89. if hdr == "" {
  90. return util.NewInvalidArgumentErrorf("X-Ops-Timestamp header missing")
  91. }
  92. ts, err := time.Parse(time.RFC3339, hdr)
  93. if err != nil {
  94. return err
  95. }
  96. diff := time.Now().UTC().Sub(ts)
  97. if diff < 0 {
  98. diff = -diff
  99. }
  100. if diff > maxTimeDifference {
  101. return errors.New("time difference")
  102. }
  103. return nil
  104. }
  105. func getSignVersion(req *http.Request) (string, error) {
  106. hdr := req.Header.Get("X-Ops-Sign")
  107. if hdr == "" {
  108. return "", util.NewInvalidArgumentErrorf("X-Ops-Sign header missing")
  109. }
  110. m := versionPattern.FindStringSubmatch(hdr)
  111. if len(m) != 2 {
  112. return "", util.NewInvalidArgumentErrorf("invalid X-Ops-Sign header")
  113. }
  114. switch m[1] {
  115. case "1.0", "1.1", "1.2", "1.3":
  116. default:
  117. return "", util.NewInvalidArgumentErrorf("unsupported version")
  118. }
  119. version := m[1]
  120. m = algorithmPattern.FindStringSubmatch(hdr)
  121. if len(m) == 2 && m[1] != "sha1" && !(m[1] == "sha256" && version == "1.3") {
  122. return "", util.NewInvalidArgumentErrorf("unsupported algorithm")
  123. }
  124. return version, nil
  125. }
  126. func verifySignedHeaders(req *http.Request, version string, pub *rsa.PublicKey) error {
  127. authorizationData, err := getAuthorizationData(req)
  128. if err != nil {
  129. return err
  130. }
  131. checkData := buildCheckData(req, version)
  132. switch version {
  133. case "1.3":
  134. return verifyDataNew(authorizationData, checkData, pub, crypto.SHA256)
  135. case "1.2":
  136. return verifyDataNew(authorizationData, checkData, pub, crypto.SHA1)
  137. default:
  138. return verifyDataOld(authorizationData, checkData, pub)
  139. }
  140. }
  141. func getAuthorizationData(req *http.Request) ([]byte, error) {
  142. valueList := make(map[int]string)
  143. for k, vs := range req.Header {
  144. if m := authorizationPattern.FindStringSubmatch(k); m != nil {
  145. index, _ := strconv.Atoi(m[1])
  146. var v string
  147. if len(vs) == 0 {
  148. v = ""
  149. } else {
  150. v = vs[0]
  151. }
  152. valueList[index] = v
  153. }
  154. }
  155. tmp := make([]string, len(valueList))
  156. for k, v := range valueList {
  157. if k > len(tmp) {
  158. return nil, errors.New("invalid X-Ops-Authorization headers")
  159. }
  160. tmp[k-1] = v
  161. }
  162. return base64.StdEncoding.DecodeString(strings.Join(tmp, ""))
  163. }
  164. func buildCheckData(req *http.Request, version string) []byte {
  165. username := req.Header.Get("X-Ops-Userid")
  166. if version != "1.0" && version != "1.3" {
  167. sum := sha1.Sum([]byte(username))
  168. username = base64.StdEncoding.EncodeToString(sum[:])
  169. }
  170. var data string
  171. if version == "1.3" {
  172. data = fmt.Sprintf(
  173. "Method:%s\nPath:%s\nX-Ops-Content-Hash:%s\nX-Ops-Sign:version=%s\nX-Ops-Timestamp:%s\nX-Ops-UserId:%s\nX-Ops-Server-API-Version:%s",
  174. req.Method,
  175. path.Clean(req.URL.Path),
  176. req.Header.Get("X-Ops-Content-Hash"),
  177. version,
  178. req.Header.Get("X-Ops-Timestamp"),
  179. username,
  180. req.Header.Get("X-Ops-Server-Api-Version"),
  181. )
  182. } else {
  183. sum := sha1.Sum([]byte(path.Clean(req.URL.Path)))
  184. data = fmt.Sprintf(
  185. "Method:%s\nHashed Path:%s\nX-Ops-Content-Hash:%s\nX-Ops-Timestamp:%s\nX-Ops-UserId:%s",
  186. req.Method,
  187. base64.StdEncoding.EncodeToString(sum[:]),
  188. req.Header.Get("X-Ops-Content-Hash"),
  189. req.Header.Get("X-Ops-Timestamp"),
  190. username,
  191. )
  192. }
  193. return []byte(data)
  194. }
  195. func verifyDataNew(signature, data []byte, pub *rsa.PublicKey, algo crypto.Hash) error {
  196. var h hash.Hash
  197. if algo == crypto.SHA256 {
  198. h = sha256.New()
  199. } else {
  200. h = sha1.New()
  201. }
  202. if _, err := h.Write(data); err != nil {
  203. return err
  204. }
  205. return rsa.VerifyPKCS1v15(pub, algo, h.Sum(nil), signature)
  206. }
  207. func verifyDataOld(signature, data []byte, pub *rsa.PublicKey) error {
  208. c := new(big.Int)
  209. m := new(big.Int)
  210. m.SetBytes(signature)
  211. e := big.NewInt(int64(pub.E))
  212. c.Exp(m, e, pub.N)
  213. out := c.Bytes()
  214. skip := 0
  215. for i := 2; i < len(out); i++ {
  216. if i+1 >= len(out) {
  217. break
  218. }
  219. if out[i] == 0xFF && out[i+1] == 0 {
  220. skip = i + 2
  221. break
  222. }
  223. }
  224. if !slices.Equal(out[skip:], data) {
  225. return errors.New("could not verify signature")
  226. }
  227. return nil
  228. }