hmac.go 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. package middleware
  2. import (
  3. "bytes"
  4. "crypto/hmac"
  5. "crypto/sha256"
  6. "encoding/hex"
  7. "io"
  8. "net/http"
  9. "strconv"
  10. "time"
  11. )
  12. func HMACAuth(secret string) func(http.Handler) http.Handler {
  13. return func(next http.Handler) http.Handler {
  14. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  15. ts := r.Header.Get("X-Timestamp")
  16. sig := r.Header.Get("X-Signature")
  17. if ts == "" || sig == "" {
  18. http.Error(w, "Missing HMAC headers", http.StatusUnauthorized)
  19. return
  20. }
  21. unix, err := strconv.ParseInt(ts, 10, 64)
  22. if err != nil {
  23. http.Error(w, "Invalid timestamp", http.StatusUnauthorized)
  24. return
  25. }
  26. now := time.Now().Unix()
  27. const skew = int64(300)
  28. if unix < now-skew || unix > now+skew {
  29. http.Error(w, "Timestamp out of allowed range", http.StatusUnauthorized)
  30. return
  31. }
  32. var bodyBytes []byte
  33. if r.Body != nil {
  34. bodyBytes, _ = io.ReadAll(r.Body)
  35. }
  36. r.Body = io.NopCloser(bytes.NewReader(bodyBytes))
  37. msg := r.Method + "\n" + r.URL.Path + "\n" + ts + "\n" + string(bodyBytes)
  38. mac := hmac.New(sha256.New, []byte(secret))
  39. mac.Write([]byte(msg))
  40. expected := mac.Sum(nil)
  41. expectedHex := hex.EncodeToString(expected)
  42. provided, err := hex.DecodeString(sig)
  43. if err != nil {
  44. http.Error(w, "Invalid signature encoding", http.StatusUnauthorized)
  45. return
  46. }
  47. if !hmac.Equal(expected, provided) && sig != expectedHex {
  48. http.Error(w, "Invalid signature", http.StatusUnauthorized)
  49. return
  50. }
  51. next.ServeHTTP(w, r)
  52. })
  53. }
  54. }