package security import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "log/slog" "strings" ) // VerifyHMACSHA256 checks a hex HMAC-SHA256 signature over the raw body. // // If no secret is configured we skip verification (dev convenience) but log a // warning, so it is never silently insecure where a secret IS set. func VerifyHMACSHA256(secret string, body []byte, signature string) bool { if secret == "" { slog.Warn("signature secret not configured — skipping verification") return true } if signature == "" { return false } signature = strings.TrimSpace(signature) // Some providers prefix the digest, e.g. "sha256=abc...". if strings.HasPrefix(strings.ToLower(signature), "sha256=") { signature = signature[len("sha256="):] } mac := hmac.New(sha256.New, []byte(secret)) mac.Write(body) expected := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(expected), []byte(signature)) }