auth.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. package webhooks
  2. import (
  3. "crypto/hmac"
  4. "crypto/sha1"
  5. "crypto/sha256"
  6. "crypto/subtle"
  7. "encoding/hex"
  8. "net/http"
  9. "strings"
  10. "github.com/OliveTin/OliveTin/internal/config"
  11. log "github.com/sirupsen/logrus"
  12. )
  13. type AuthVerifier struct {
  14. config config.WebhookConfig
  15. }
  16. func NewAuthVerifier(cfg config.WebhookConfig) *AuthVerifier {
  17. return &AuthVerifier{config: cfg}
  18. }
  19. func (v *AuthVerifier) Verify(r *http.Request, payload []byte) bool {
  20. verifier := v.getVerifier()
  21. if verifier == nil {
  22. return v.handleUnknownAuthType()
  23. }
  24. return verifier(r, payload)
  25. }
  26. type authVerifierFunc func(*http.Request, []byte) bool
  27. func (v *AuthVerifier) getVerifier() authVerifierFunc {
  28. if v.config.AuthType == "" || v.config.AuthType == "none" {
  29. return func(_ *http.Request, _ []byte) bool {
  30. return true
  31. }
  32. }
  33. verifierMap := v.buildVerifierMap()
  34. if verifier, ok := verifierMap[v.config.AuthType]; ok {
  35. return verifier
  36. }
  37. return nil
  38. }
  39. func (v *AuthVerifier) buildVerifierMap() map[string]authVerifierFunc {
  40. return map[string]authVerifierFunc{
  41. "hmac-sha256": v.verifyHMAC256,
  42. "hmac-sha1": v.verifyHMAC1,
  43. "bearer": func(r *http.Request, _ []byte) bool {
  44. return v.verifyBearer(r)
  45. },
  46. "basic": func(r *http.Request, _ []byte) bool {
  47. return v.verifyBasic(r)
  48. },
  49. }
  50. }
  51. func (v *AuthVerifier) handleUnknownAuthType() bool {
  52. log.WithFields(log.Fields{
  53. "authType": v.config.AuthType,
  54. }).Warnf("Unknown auth type, rejecting")
  55. return false
  56. }
  57. func (v *AuthVerifier) verifyHMAC256(r *http.Request, payload []byte) bool {
  58. if v.config.Secret == "" {
  59. log.Warnf("HMAC-SHA256 auth requires secret")
  60. return false
  61. }
  62. headerName := v.config.AuthHeader
  63. if headerName == "" {
  64. headerName = "X-Webhook-Signature"
  65. }
  66. signature := r.Header.Get(headerName)
  67. if signature == "" {
  68. log.Debugf("Missing signature header: %s", headerName)
  69. return false
  70. }
  71. expectedSig := strings.TrimPrefix(signature, "sha256=")
  72. mac := hmac.New(sha256.New, []byte(v.config.Secret))
  73. mac.Write(payload)
  74. computedSig := hex.EncodeToString(mac.Sum(nil))
  75. return hmac.Equal([]byte(expectedSig), []byte(computedSig))
  76. }
  77. func (v *AuthVerifier) verifyHMAC1(r *http.Request, payload []byte) bool {
  78. if v.config.Secret == "" {
  79. log.Warnf("HMAC-SHA1 auth requires secret")
  80. return false
  81. }
  82. headerName := v.config.AuthHeader
  83. if headerName == "" {
  84. headerName = "X-Webhook-Signature"
  85. }
  86. signature := r.Header.Get(headerName)
  87. if signature == "" {
  88. log.Debugf("Missing signature header: %s", headerName)
  89. return false
  90. }
  91. expectedSig := strings.TrimPrefix(signature, "sha1=")
  92. mac := hmac.New(sha1.New, []byte(v.config.Secret))
  93. mac.Write(payload)
  94. computedSig := hex.EncodeToString(mac.Sum(nil))
  95. return hmac.Equal([]byte(expectedSig), []byte(computedSig))
  96. }
  97. func (v *AuthVerifier) verifyBearer(r *http.Request) bool {
  98. if v.config.Secret == "" {
  99. log.Warnf("Bearer auth requires secret")
  100. return false
  101. }
  102. authHeader := r.Header.Get("Authorization")
  103. if !strings.HasPrefix(authHeader, "Bearer ") {
  104. log.Debugf("Missing or invalid Bearer token")
  105. return false
  106. }
  107. token := strings.TrimPrefix(authHeader, "Bearer ")
  108. tokenBytes := []byte(token)
  109. secretBytes := []byte(v.config.Secret)
  110. return len(tokenBytes) == len(secretBytes) && subtle.ConstantTimeCompare(tokenBytes, secretBytes) == 1
  111. }
  112. func (v *AuthVerifier) verifyBasic(r *http.Request) bool {
  113. if v.config.Secret == "" {
  114. log.Warnf("Basic auth requires secret")
  115. return false
  116. }
  117. username, password, ok := r.BasicAuth()
  118. if !ok {
  119. log.Debugf("Missing Basic auth header")
  120. return false
  121. }
  122. return v.verifyBasicCredentials(username, password)
  123. }
  124. func (v *AuthVerifier) verifyBasicCredentials(username, password string) bool {
  125. parts := strings.SplitN(v.config.Secret, ":", 2)
  126. if len(parts) == 2 {
  127. return v.verifyBasicWithUsername(username, password, parts[0], parts[1])
  128. }
  129. return v.verifyBasicPasswordOnly(password)
  130. }
  131. func (v *AuthVerifier) verifyBasicWithUsername(username, password, expectedUsername, expectedPassword string) bool {
  132. usernameMatch := subtle.ConstantTimeCompare([]byte(username), []byte(expectedUsername))
  133. passwordMatch := subtle.ConstantTimeCompare([]byte(password), []byte(expectedPassword))
  134. return usernameMatch == 1 && passwordMatch == 1
  135. }
  136. func (v *AuthVerifier) verifyBasicPasswordOnly(password string) bool {
  137. return subtle.ConstantTimeCompare([]byte(password), []byte(v.config.Secret)) == 1
  138. }