106 lines
3.1 KiB
Go
106 lines
3.1 KiB
Go
package signature
|
|
|
|
import (
|
|
"context"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// mockNonceStore 内存实现,用于测试
|
|
type mockNonceStore struct {
|
|
seen map[string]bool
|
|
}
|
|
|
|
func newMockStore() *mockNonceStore {
|
|
return &mockNonceStore{seen: map[string]bool{}}
|
|
}
|
|
|
|
func (m *mockNonceStore) SetIfNotExists(_ context.Context, appId, nonce string, _ int64) (bool, error) {
|
|
key := appId + ":" + nonce
|
|
if m.seen[key] {
|
|
return true, nil
|
|
}
|
|
m.seen[key] = true
|
|
return false, nil
|
|
}
|
|
|
|
func makeSignature(secret, stringToSign string) string {
|
|
mac := hmac.New(sha256.New, []byte(secret))
|
|
mac.Write([]byte(stringToSign))
|
|
return hex.EncodeToString(mac.Sum(nil))
|
|
}
|
|
|
|
func TestValidate_Success(t *testing.T) {
|
|
conf := SignatureConf{
|
|
AppSecrets: map[string]string{"web-client": "uB4G,XxL2{7b"},
|
|
ValidWindowSeconds: 300,
|
|
}
|
|
v := NewValidator(conf, newMockStore())
|
|
|
|
ts := strconv.FormatInt(time.Now().Unix(), 10)
|
|
nonce := fmt.Sprintf("%x", time.Now().UnixNano())
|
|
sts := BuildStringToSign("POST", "/api/v1/order", "", []byte(`{"plan_id":1}`), "web-client", ts, nonce)
|
|
sig := makeSignature("uB4G,XxL2{7b", sts)
|
|
|
|
if err := v.Validate(context.Background(), "web-client", ts, nonce, sig, sts); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestValidate_Expired(t *testing.T) {
|
|
conf := SignatureConf{
|
|
AppSecrets: map[string]string{"web-client": "uB4G,XxL2{7b"},
|
|
ValidWindowSeconds: 300,
|
|
}
|
|
v := NewValidator(conf, newMockStore())
|
|
|
|
ts := strconv.FormatInt(time.Now().Unix()-400, 10) // 已过期 400s
|
|
nonce := "abc"
|
|
sts := BuildStringToSign("GET", "/api/v1/user/info", "", nil, "web-client", ts, nonce)
|
|
sig := makeSignature("uB4G,XxL2{7b", sts)
|
|
|
|
if err := v.Validate(context.Background(), "web-client", ts, nonce, sig, sts); err != ErrSignatureExpired {
|
|
t.Fatalf("expected ErrSignatureExpired, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidate_Replay(t *testing.T) {
|
|
conf := SignatureConf{
|
|
AppSecrets: map[string]string{"web-client": "uB4G,XxL2{7b"},
|
|
ValidWindowSeconds: 300,
|
|
}
|
|
store := newMockStore()
|
|
v := NewValidator(conf, store)
|
|
|
|
ts := strconv.FormatInt(time.Now().Unix(), 10)
|
|
nonce := "same-nonce-replay"
|
|
sts := BuildStringToSign("GET", "/api/v1/user/info", "", nil, "web-client", ts, nonce)
|
|
sig := makeSignature("uB4G,XxL2{7b", sts)
|
|
|
|
_ = v.Validate(context.Background(), "web-client", ts, nonce, sig, sts)
|
|
if err := v.Validate(context.Background(), "web-client", ts, nonce, sig, sts); err != ErrSignatureReplay {
|
|
t.Fatalf("expected ErrSignatureReplay, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidate_InvalidSignature(t *testing.T) {
|
|
conf := SignatureConf{
|
|
AppSecrets: map[string]string{"web-client": "uB4G,XxL2{7b"},
|
|
ValidWindowSeconds: 300,
|
|
}
|
|
v := NewValidator(conf, newMockStore())
|
|
|
|
ts := strconv.FormatInt(time.Now().Unix(), 10)
|
|
nonce := "nonce-invalid-sig"
|
|
sts := BuildStringToSign("POST", "/api/v1/order", "", []byte(`{"plan_id":1}`), "web-client", ts, nonce)
|
|
|
|
if err := v.Validate(context.Background(), "web-client", ts, nonce, "badsignature", sts); err != ErrSignatureInvalid {
|
|
t.Fatalf("expected ErrSignatureInvalid, got %v", err)
|
|
}
|
|
}
|