xsgzFetchRSA 此前只判断 DK/M/E 是否非空。若 /api/sec_js 响应被截断 或格式变化,正则可能匹配到残缺值,用它加密出的密文服务端必然解不开, 最终表现为「参数解码错误,刷新页面后尝试」,难以定位真因。 新增形态校验,提前拦住并把错误指向真实原因: - DK 盐长度 < 16 → 报「DK 盐长度异常(len=N),sec_js 响应可能不完整」 - 模数 hex 长度 < 64 → 报「模数长度异常」 - 指数非 010001/10001 → 报「指数形态异常」 阈值取保守下限(真实响应对应 32 / 258 / "010001"), 已用线上真实 /api/sec_js 验证不会误判。 测试覆盖正常与四类残缺情形。
This commit is contained in:
@@ -909,6 +909,19 @@ func xsgzFetchRSA(client *resty.Client, base string) (*xsgzRSA, error) {
|
||||
if r.DK == "" || r.M == "" || r.E == "" {
|
||||
return nil, fmt.Errorf("解析公钥参数失败")
|
||||
}
|
||||
// 形态校验:/api/sec_js 每次返回都带新盐,若响应被截断或格式变化,
|
||||
// 正则会匹配到残缺值,加密出的密文服务端必然解不开(表现为
|
||||
// 「参数解码错误,刷新页面后尝试」)。这里提前拦住,报错更贴近真因,
|
||||
// 也避免把无效密文发出去。
|
||||
if len(r.DK) < 16 {
|
||||
return nil, fmt.Errorf("DK 盐长度异常(len=%d),sec_js 响应可能不完整", len(r.DK))
|
||||
}
|
||||
if len(r.M) < 64 {
|
||||
return nil, fmt.Errorf("模数长度异常(hex len=%d),sec_js 响应可能不完整", len(r.M))
|
||||
}
|
||||
if r.E != "010001" && r.E != "10001" {
|
||||
return nil, fmt.Errorf("指数形态异常(%q),sec_js 响应可能不完整", r.E)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package cpdaily
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
// xsgzFetchRSA 的形态校验:sec_js 响应不完整时必须提前报错,
|
||||
// 而不是拿残缺密钥加密后让服务端回「参数解码错误」。
|
||||
func TestXsgzFetchRSAValidation(t *testing.T) {
|
||||
// 构造一个正常的 sec_js 片段(DK 32 字符、M 258 hex、E 010001)
|
||||
goodDK := strings.Repeat("a", 32)
|
||||
goodM := "00" + strings.Repeat("b", 256)
|
||||
mk := func(dk, m, e string) string {
|
||||
var sb strings.Builder
|
||||
if dk != "" {
|
||||
sb.WriteString(`function _DK(){return "` + dk + `"}` + "\n")
|
||||
}
|
||||
if m != "" {
|
||||
sb.WriteString(`function _M(){return "` + m + `"}` + "\n")
|
||||
}
|
||||
if e != "" {
|
||||
sb.WriteString(`function _E(){return "` + e + `"}` + "\n")
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
body string
|
||||
wantErr string
|
||||
}{
|
||||
{"正常", mk(goodDK, goodM, "010001"), ""},
|
||||
{"DK 过短", mk("abc", goodM, "010001"), "DK 盐长度异常"},
|
||||
{"M 过短", mk(goodDK, "00bb", "010001"), "模数长度异常"},
|
||||
{"E 异常", mk(goodDK, goodM, "010003"), "指数形态异常"},
|
||||
{"缺 E", mk(goodDK, goodM, ""), "解析公钥参数失败"},
|
||||
{"空响应", "", "解析公钥参数失败"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte(c.body))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
_, err := xsgzFetchRSA(resty.New(), srv.URL)
|
||||
if c.wantErr == "" {
|
||||
if err != nil {
|
||||
t.Fatalf("应成功,实际报错: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("应报错但未报错")
|
||||
}
|
||||
if !strings.Contains(err.Error(), c.wantErr) {
|
||||
t.Fatalf("错误信息应含 %q,实际: %v", c.wantErr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user