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,76 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GenerateKey creates a fresh Ed25519 keypair for offline use.
|
||||
func GenerateKey() (ed25519.PublicKey, ed25519.PrivateKey, error) {
|
||||
return ed25519.GenerateKey(rand.Reader)
|
||||
}
|
||||
|
||||
// EncodePrivate / EncodePublic render keys as base64 (std) for storage. The
|
||||
// private encoding is meant to be written to an OFFLINE medium only.
|
||||
func EncodePrivate(priv ed25519.PrivateKey) string {
|
||||
return base64.StdEncoding.EncodeToString(priv)
|
||||
}
|
||||
|
||||
func EncodePublic(pub ed25519.PublicKey) string {
|
||||
return base64.StdEncoding.EncodeToString(pub)
|
||||
}
|
||||
|
||||
// DecodePrivate parses a base64-encoded Ed25519 private key.
|
||||
func DecodePrivate(s string) (ed25519.PrivateKey, error) {
|
||||
b, err := base64.StdEncoding.DecodeString(strings.TrimSpace(s))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sign: private key not valid base64: %w", err)
|
||||
}
|
||||
if len(b) != ed25519.PrivateKeySize {
|
||||
return nil, fmt.Errorf("sign: private key wrong size: got %d want %d", len(b), ed25519.PrivateKeySize)
|
||||
}
|
||||
return ed25519.PrivateKey(b), nil
|
||||
}
|
||||
|
||||
// DecodePublic parses a base64-encoded Ed25519 public key.
|
||||
func DecodePublic(s string) (ed25519.PublicKey, error) {
|
||||
b, err := base64.StdEncoding.DecodeString(strings.TrimSpace(s))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sign: public key not valid base64: %w", err)
|
||||
}
|
||||
if len(b) != ed25519.PublicKeySize {
|
||||
return nil, fmt.Errorf("sign: public key wrong size: got %d want %d", len(b), ed25519.PublicKeySize)
|
||||
}
|
||||
return ed25519.PublicKey(b), nil
|
||||
}
|
||||
|
||||
// ParseKeyRing builds a KeyRing from "keyid=base64pub" specs. Multiple specs
|
||||
// (comma- or repeat-supplied) enable a rotation window where either key
|
||||
// validates a document.
|
||||
func ParseKeyRing(specs []string) (KeyRing, error) {
|
||||
ring := KeyRing{}
|
||||
for _, spec := range specs {
|
||||
for _, part := range strings.Split(spec, ",") {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
id, b64, ok := strings.Cut(part, "=")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("sign: key ring spec %q must be keyid=base64pubkey", part)
|
||||
}
|
||||
pub, err := DecodePublic(b64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ring[strings.TrimSpace(id)] = pub
|
||||
}
|
||||
}
|
||||
if len(ring) == 0 {
|
||||
return nil, fmt.Errorf("sign: empty key ring")
|
||||
}
|
||||
return ring, nil
|
||||
}
|
||||
Reference in New Issue
Block a user