package signature import ( "context" "crypto/hmac" "crypto/sha256" "encoding/hex" "strconv" "time" ) type Validator struct { conf SignatureConf nonceStore NonceStore } func NewValidator(conf SignatureConf, store NonceStore) *Validator { return &Validator{ conf: conf, nonceStore: store, } } func (v *Validator) windowSeconds() int64 { if v.conf.ValidWindowSeconds <= 0 { return 300 } return v.conf.ValidWindowSeconds } func (v *Validator) Validate(ctx context.Context, appId, timestamp, nonce, signature, stringToSign string) error { secret, ok := v.conf.AppSecrets[appId] if !ok { return ErrSignatureMissing } ts, err := strconv.ParseInt(timestamp, 10, 64) if err != nil { return ErrSignatureExpired } diff := time.Now().Unix() - ts if diff < 0 { diff = -diff } if diff > v.windowSeconds() { return ErrSignatureExpired } replayed, err := v.nonceStore.SetIfNotExists(ctx, appId, nonce, v.windowSeconds()+60) if err == nil && replayed { return ErrSignatureReplay } mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(stringToSign)) expected := hex.EncodeToString(mac.Sum(nil)) if !hmac.Equal([]byte(expected), []byte(signature)) { return ErrSignatureInvalid } return nil }