package voiceprovider import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/hex" "errors" "strings" "time" ) const ( HeaderCallbackTimestamp = "X-Provider-Timestamp" HeaderCallbackSignature = "X-Provider-Signature" ) var ErrInvalidCallbackSignature = errors.New("invalid provider callback signature") func SignCallback(secret []byte, timestamp string, body []byte) string { digest := sha256.Sum256(body) canonical := timestamp + "\n" + hex.EncodeToString(digest[:]) mac := hmac.New(sha256.New, secret) _, _ = mac.Write([]byte(canonical)) return base64.RawURLEncoding.EncodeToString(mac.Sum(nil)) } func VerifyCallback(secret []byte, timestamp, signature string, body []byte, now time.Time, maxSkew time.Duration) error { if len(secret) < 32 || strings.TrimSpace(timestamp) == "" || strings.TrimSpace(signature) == "" || maxSkew <= 0 { return ErrInvalidCallbackSignature } parsed, err := time.Parse(time.RFC3339, timestamp) if err != nil || absoluteDuration(now.Sub(parsed)) > maxSkew { return ErrInvalidCallbackSignature } provided, err := base64.RawURLEncoding.DecodeString(signature) if err != nil { return ErrInvalidCallbackSignature } expected, _ := base64.RawURLEncoding.DecodeString(SignCallback(secret, timestamp, body)) if !hmac.Equal(provided, expected) { return ErrInvalidCallbackSignature } return nil } func absoluteDuration(value time.Duration) time.Duration { if value < 0 { return -value } return value }