feat(infra/domains): 域名池 + CDN 前置 + 签名端点分发 (tsk_NU9JuUweHWMt)
- domains.md: 四组域名隔离登记 + 冷备池 ≥5 + 启用流程(不含身份信息) - cdn/terraform: Cloudflare 配置即代码(WAF/bot/速率限制/代理DNS/回源鉴权注入)+ 30min 重放 Runbook - server/internal/originauth: 回源鉴权中间件,非 CDN 网段或鉴权头不符一律 403,支持双值轮换 - tools/endpoint-signer: 离线 Ed25519 签名 CLI(端点 + 公告文档,单调版本防回滚,key_id 双公钥轮换) - tools/publish-mirrors: ≥3 镜像发布 + hash 一致性校验 + 故障转移取回 - CLIENT-CONTRACT.md: schema/验签/防回滚/合并/兜底链/channel 客户端契约 - 出站独立出口要求写入部署文档;私钥/token/身份信息一律不入库 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func mustKey(t *testing.T) (ed25519.PublicKey, ed25519.PrivateKey) {
|
||||
t.Helper()
|
||||
pub, priv, err := GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKey: %v", err)
|
||||
}
|
||||
return pub, priv
|
||||
}
|
||||
|
||||
func TestSignVerifyRoundTrip(t *testing.T) {
|
||||
pub, priv := mustKey(t)
|
||||
env := Envelope{
|
||||
Version: 1,
|
||||
IssuedAt: "2026-06-13T00:00:00Z",
|
||||
KeyID: "k1",
|
||||
Payload: json.RawMessage(`{"api_domains":["a.example.com"]}`),
|
||||
}
|
||||
if err := Sign(priv, &env); err != nil {
|
||||
t.Fatalf("Sign: %v", err)
|
||||
}
|
||||
if env.Sig == "" {
|
||||
t.Fatal("signature not set")
|
||||
}
|
||||
if err := Verify(env, KeyRing{"k1": pub}); err != nil {
|
||||
t.Fatalf("Verify: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyRejectsTamperedPayload(t *testing.T) {
|
||||
pub, priv := mustKey(t)
|
||||
env := Envelope{Version: 1, IssuedAt: "t", KeyID: "k1", Payload: json.RawMessage(`{"x":1}`)}
|
||||
if err := Sign(priv, &env); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
env.Payload = json.RawMessage(`{"x":2}`) // tamper after signing
|
||||
if err := Verify(env, KeyRing{"k1": pub}); !errors.Is(err, ErrBadSignature) {
|
||||
t.Fatalf("want ErrBadSignature, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyRejectsVersionTamper(t *testing.T) {
|
||||
pub, priv := mustKey(t)
|
||||
env := Envelope{Version: 5, IssuedAt: "t", KeyID: "k1", Payload: json.RawMessage(`{"x":1}`)}
|
||||
if err := Sign(priv, &env); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
env.Version = 99 // attacker tries to inflate version
|
||||
if err := Verify(env, KeyRing{"k1": pub}); !errors.Is(err, ErrBadSignature) {
|
||||
t.Fatalf("want ErrBadSignature, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyUnknownKeyID(t *testing.T) {
|
||||
pub, priv := mustKey(t)
|
||||
env := Envelope{Version: 1, IssuedAt: "t", KeyID: "k1", Payload: json.RawMessage(`{}`)}
|
||||
if err := Sign(priv, &env); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := Verify(env, KeyRing{"other": pub}); !errors.Is(err, ErrUnknownKeyID) {
|
||||
t.Fatalf("want ErrUnknownKeyID, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyRotationDoublePublicKey(t *testing.T) {
|
||||
oldPub, _ := mustKey(t)
|
||||
newPub, newPriv := mustKey(t)
|
||||
// Document signed with the NEW key, key_id "v2".
|
||||
env := Envelope{Version: 1, IssuedAt: "t", KeyID: "v2", Payload: json.RawMessage(`{}`)}
|
||||
if err := Sign(newPriv, &env); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// During the rotation window the client holds BOTH public keys.
|
||||
ring := KeyRing{"v1": oldPub, "v2": newPub}
|
||||
if err := Verify(env, ring); err != nil {
|
||||
t.Fatalf("rotation window verify failed: %v", err)
|
||||
}
|
||||
// A client that only has the old key must reject it.
|
||||
if err := Verify(env, KeyRing{"v1": oldPub}); !errors.Is(err, ErrUnknownKeyID) {
|
||||
t.Fatalf("want ErrUnknownKeyID for old-only ring, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalizeFieldOrderInvariant(t *testing.T) {
|
||||
_, priv := mustKey(t)
|
||||
// Same logical payload, different key order -> identical signature.
|
||||
envA := Envelope{Version: 1, IssuedAt: "t", KeyID: "k1", Payload: json.RawMessage(`{"a":1,"b":2}`)}
|
||||
envB := Envelope{Version: 1, IssuedAt: "t", KeyID: "k1", Payload: json.RawMessage(`{"b":2,"a":1}`)}
|
||||
if err := Sign(priv, &envA); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := Sign(priv, &envB); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if envA.Sig != envB.Sig {
|
||||
t.Fatalf("canonicalization not order-invariant:\n A=%s\n B=%s", envA.Sig, envB.Sig)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalizePreservesIntegers(t *testing.T) {
|
||||
out, err := Canonicalize([]byte(`{"v":12345678901234567}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(out) != `{"v":12345678901234567}` {
|
||||
t.Fatalf("integer not preserved: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseKeyRing(t *testing.T) {
|
||||
pub, _ := mustKey(t)
|
||||
ring, err := ParseKeyRing([]string{"k1=" + EncodePublic(pub)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := ring["k1"]; !ok {
|
||||
t.Fatal("k1 missing from ring")
|
||||
}
|
||||
if _, err := ParseKeyRing([]string{"bad"}); err == nil {
|
||||
t.Fatal("want error for malformed spec")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodePrivateRoundTrip(t *testing.T) {
|
||||
_, priv := mustKey(t)
|
||||
got, err := DecodePrivate(EncodePrivate(priv))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !got.Equal(priv) {
|
||||
t.Fatal("private key round-trip mismatch")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user