feat: 接入 CoinGlass 清算热力图隔离链路
Some checks failed
ci / build + vet + guard + race (push) Has been cancelled
Some checks failed
ci / build + vet + guard + race (push) Has been cancelled
This commit is contained in:
128
internal/repo/webapi/coinglass/crypto_test.go
Normal file
128
internal/repo/webapi/coinglass/crypto_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package coinglass
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/aes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// helper:把 plaintext 用 V2 通用管线反向加密,产出可喂给 decryptBlob 的 base64 ciphertext。
|
||||
// 形态:plaintext → '"plaintext"' → gzip → pkcs7 pad → AES-ECB → base64
|
||||
func encryptForTest(t *testing.T, key []byte, plaintext string) string {
|
||||
t.Helper()
|
||||
|
||||
// CoinGlass 服务器端会在 plaintext 外加引号(decryptBlob 反向 strip);
|
||||
// 测试也照样加引号 → 验证 strip 逻辑。
|
||||
quoted := []byte(`"` + plaintext + `"`)
|
||||
|
||||
var buf bytes.Buffer
|
||||
gz := gzip.NewWriter(&buf)
|
||||
_, err := gz.Write(quoted)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, gz.Close())
|
||||
|
||||
padded := pkcs7Pad(buf.Bytes(), aes.BlockSize)
|
||||
enc, err := aesECBEncrypt(key, padded)
|
||||
require.NoError(t, err)
|
||||
return base64.StdEncoding.EncodeToString(enc)
|
||||
}
|
||||
|
||||
func TestDecryptBlob_RoundTrip(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
key []byte
|
||||
text string
|
||||
}{
|
||||
{"16-byte key (AES-128)", []byte("0123456789abcdef"), `{"hello":"world"}`},
|
||||
{"32-byte key (AES-256)", []byte("1f68efd73f8d4921acc0dead41dd39bc"), `{"foo":42,"bar":"baz"}`},
|
||||
{"empty json object", []byte("0123456789abcdef"), `{}`},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ct := encryptForTest(t, tc.key, tc.text)
|
||||
got, err := decryptBlob(ct, tc.key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.text, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptBlob_BadCiphertext(t *testing.T) {
|
||||
key := []byte("0123456789abcdef")
|
||||
|
||||
_, err := decryptBlob("!!!not_base64!!!", key)
|
||||
require.Error(t, err)
|
||||
require.ErrorIs(t, err, ErrDecryptFail)
|
||||
|
||||
short := base64.StdEncoding.EncodeToString([]byte("toosmall"))
|
||||
_, err = decryptBlob(short, key)
|
||||
require.ErrorIs(t, err, ErrDecryptFail)
|
||||
}
|
||||
|
||||
func TestPKCS7_PadUnpadRoundTrip(t *testing.T) {
|
||||
cases := [][]byte{
|
||||
[]byte(""),
|
||||
[]byte("a"),
|
||||
[]byte("0123456789abcde"), // 15 → pad 1
|
||||
[]byte("0123456789abcdef"), // 16 → pad 16 (full block)
|
||||
[]byte("0123456789abcdef0"),
|
||||
}
|
||||
for _, in := range cases {
|
||||
padded := pkcs7Pad(in, 16)
|
||||
require.Zero(t, len(padded)%16)
|
||||
out, err := pkcs7Unpad(padded, 16)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, in, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPKCS7_UnpadRejectsBadInput(t *testing.T) {
|
||||
bad := []byte("0123456789abcdef") // pad byte = 'f' (0x66 = 102 > 16)
|
||||
_, err := pkcs7Unpad(bad, 16)
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = pkcs7Unpad([]byte{}, 16)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestUserKeyFromSeed_TruncatesTo16(t *testing.T) {
|
||||
seed := "863f08689c97435b" // v=77 constant from bundle
|
||||
got := userKeyFromSeed(seed)
|
||||
require.Len(t, got, 16)
|
||||
// base64("863f08689c97435b") = "ODYzZjA4Njg5Yzk3NDM1Yg==" → first 16 = "ODYzZjA4Njg5Yzk3"
|
||||
require.Equal(t, "ODYzZjA4Njg5Yzk3", string(got))
|
||||
}
|
||||
|
||||
func TestBodyKeyFromToken_RawASCIIFirst16(t *testing.T) {
|
||||
token := "dfa89d5d00cf4000extra_chars"
|
||||
got := bodyKeyFromToken(token)
|
||||
require.Len(t, got, 16)
|
||||
require.Equal(t, "dfa89d5d00cf4000", string(got))
|
||||
}
|
||||
|
||||
func TestDecryptUniversal_TwoStage(t *testing.T) {
|
||||
seed := "863f08689c97435b" // v=77 bundle constant
|
||||
userKey := userKeyFromSeed(seed)
|
||||
token := "dfa89d5d00cf4000" // exactly 16 chars (matches cg.md L79 example)
|
||||
bodyKey := []byte(token)
|
||||
|
||||
plaintext := `{"success":true,"code":"0","msg":"","data":{"priceMin":110000.0}}`
|
||||
|
||||
userHeaderB64 := encryptForTest(t, userKey, token)
|
||||
bodyDataB64 := encryptForTest(t, bodyKey, plaintext)
|
||||
|
||||
got, err := DecryptUniversal(seed, userHeaderB64, bodyDataB64)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, plaintext, string(got))
|
||||
}
|
||||
|
||||
func TestDecryptUniversal_PropagatesDecryptFail(t *testing.T) {
|
||||
_, err := DecryptUniversal("seed", "!!!", "!!!")
|
||||
require.Error(t, err)
|
||||
require.True(t, errors.Is(err, ErrDecryptFail))
|
||||
}
|
||||
Reference in New Issue
Block a user