package agentd import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "fmt" "math/big" "os" "time" ) // ensureSelfSignedCert 保证 hysteria2 入站用的自签 TLS 证书存在(方案①)。 // // 为什么自签:Hy2 跑在 QUIC 上,QUIC 强制 TLS 1.3 → 服务端必须出示证书。它不像 // REALITY 借真实大站证书,得自备一张。两端都自有,客户端用 tls.insecure 收自签即可 // (真正的服务端鉴权靠 per-user 派生的 hy2 密码)。 // // 幂等:两个文件都在则直接返回;否则生成一张 ECDSA P-256、10 年有效的自签证书, // cert 0644 / key 0600 写盘。sni 写入 SAN(客户端 insecure 时仅作 ClientHello 用)。 func ensureSelfSignedCert(certPath, keyPath, sni string) error { if fileExists(certPath) && fileExists(keyPath) { return nil } key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return fmt.Errorf("agentd: hy2 cert keygen: %w", err) } serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { return fmt.Errorf("agentd: hy2 cert serial: %w", err) } tmpl := x509.Certificate{ SerialNumber: serial, Subject: pkix.Name{CommonName: "pangolin-hy2"}, NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().AddDate(10, 0, 0), KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } if sni != "" { tmpl.DNSNames = []string{sni} } der, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &key.PublicKey, key) if err != nil { return fmt.Errorf("agentd: hy2 cert create: %w", err) } certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}) if err := os.WriteFile(certPath, certPEM, 0o644); err != nil { return fmt.Errorf("agentd: write hy2 cert: %w", err) } keyDER, err := x509.MarshalPKCS8PrivateKey(key) if err != nil { return fmt.Errorf("agentd: marshal hy2 key: %w", err) } keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) if err := os.WriteFile(keyPath, keyPEM, 0o600); err != nil { return fmt.Errorf("agentd: write hy2 key: %w", err) } return nil } func fileExists(p string) bool { info, err := os.Stat(p) return err == nil && !info.IsDir() }