修复: Reality x25519 密钥改面板内置生成(crypto/ecdh), 不再 exec xray 二进制; 编码与 xray x25519 输出一致(base64url raw 32B), 附曲线自验单测
This commit is contained in:
@@ -6,8 +6,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ecdh"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
@@ -2305,17 +2308,21 @@ func parseXrayKeyPairOutput(output string) (string, string, error) {
|
|||||||
return strings.TrimSpace(first[1]), strings.TrimSpace(second[1]), nil
|
return strings.TrimSpace(first[1]), strings.TrimSpace(second[1]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetNewX25519Cert() (any, error) {
|
// generateX25519KeyPair returns a fresh X25519 key pair in the exact format
|
||||||
// Run the command
|
// `xray x25519` prints (base64 RawURLEncoding of the raw 32-byte keys), but
|
||||||
cmd := exec.CommandContext(context.Background(), xray.GetBinaryPath(), "x25519")
|
// generated in-process: a control-plane-only panel has no xray binary to shell
|
||||||
var out bytes.Buffer
|
// out to, and shelling out made Reality inbound creation fail there.
|
||||||
cmd.Stdout = &out
|
func generateX25519KeyPair() (privateKey string, publicKey string, err error) {
|
||||||
err := cmd.Run()
|
priv, err := ecdh.X25519().GenerateKey(rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", "", err
|
||||||
|
}
|
||||||
|
return base64.RawURLEncoding.EncodeToString(priv.Bytes()),
|
||||||
|
base64.RawURLEncoding.EncodeToString(priv.PublicKey().Bytes()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
privateKey, publicKey, err := parseXrayKeyPairOutput(out.String())
|
func (s *ServerService) GetNewX25519Cert() (any, error) {
|
||||||
|
privateKey, publicKey, err := generateX25519KeyPair()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/ecdh"
|
||||||
|
"encoding/base64"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The panel generates Reality keys in-process now (no xray binary on a
|
||||||
|
// control-plane-only master). These tests pin the wire format to what
|
||||||
|
// `xray x25519` prints — base64 RawURLEncoding of the raw 32-byte keys —
|
||||||
|
// and prove the pair is a genuine X25519 pair by re-deriving the public
|
||||||
|
// key from the private key and by an ECDH agreement check.
|
||||||
|
func TestGenerateX25519KeyPair_MatchesXrayFormatAndCurve(t *testing.T) {
|
||||||
|
privB64, pubB64, err := generateX25519KeyPair()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("generateX25519KeyPair: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, k := range map[string]string{"private": privB64, "public": pubB64} {
|
||||||
|
if len(k) != 43 || strings.ContainsAny(k, "+/=") {
|
||||||
|
t.Fatalf("%s key %q is not 43-char raw base64url", name, k)
|
||||||
|
}
|
||||||
|
raw, err := base64.RawURLEncoding.DecodeString(k)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s key does not decode as raw base64url: %v", name, err)
|
||||||
|
}
|
||||||
|
if len(raw) != 32 {
|
||||||
|
t.Fatalf("%s key decodes to %d bytes, want 32", name, len(raw))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
privRaw, _ := base64.RawURLEncoding.DecodeString(privB64)
|
||||||
|
pubRaw, _ := base64.RawURLEncoding.DecodeString(pubB64)
|
||||||
|
|
||||||
|
priv, err := ecdh.X25519().NewPrivateKey(privRaw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewPrivateKey: %v", err)
|
||||||
|
}
|
||||||
|
if got := priv.PublicKey().Bytes(); !bytes.Equal(got, pubRaw) {
|
||||||
|
t.Fatalf("public key does not correspond to private key:\n got %x\nwant %x", got, pubRaw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateX25519KeyPair_ECDHagreement(t *testing.T) {
|
||||||
|
privAB64, pubAB64, err := generateX25519KeyPair()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
privBB64, pubBB64, err := generateX25519KeyPair()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if privAB64 == privBB64 || pubAB64 == pubBB64 {
|
||||||
|
t.Fatal("two generations produced identical keys")
|
||||||
|
}
|
||||||
|
|
||||||
|
decode := func(s string) []byte {
|
||||||
|
raw, err := base64.RawURLEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
privA, _ := ecdh.X25519().NewPrivateKey(decode(privAB64))
|
||||||
|
privB, _ := ecdh.X25519().NewPrivateKey(decode(privBB64))
|
||||||
|
pubA, _ := ecdh.X25519().NewPublicKey(decode(pubAB64))
|
||||||
|
pubB, _ := ecdh.X25519().NewPublicKey(decode(pubBB64))
|
||||||
|
|
||||||
|
secretAB, err := privA.ECDH(pubB)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
secretBA, err := privB.ECDH(pubA)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(secretAB, secretBA) {
|
||||||
|
t.Fatal("ECDH shared secrets differ")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user