mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-08-22 14:38:34 +00:00
VLESS protocol: Add lightweight Post-Quantum ML-KEM-768-based PFS 1-RTT / anti-replay 0-RTT AEAD encryption
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3163335040
This commit is contained in:
parent
0cceea75da
commit
f61c14e9c6
@ -79,20 +79,18 @@ type CommandSwitchAccount struct {
|
||||
}
|
||||
|
||||
var (
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
|
||||
// Keep in sync with crypto/tls/cipher_suites.go.
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3
|
||||
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
|
||||
// Keep in sync with crypto/aes/cipher_s390x.go.
|
||||
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR &&
|
||||
(cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
|
||||
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH
|
||||
hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le"
|
||||
|
||||
hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 ||
|
||||
runtime.GOARCH == "arm64" && hasGCMAsmARM64 ||
|
||||
runtime.GOARCH == "s390x" && hasGCMAsmS390X
|
||||
HasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64
|
||||
)
|
||||
|
||||
func (sc *SecurityConfig) GetSecurityType() SecurityType {
|
||||
if sc == nil || sc.Type == SecurityType_AUTO {
|
||||
if hasAESGCMHardwareSupport {
|
||||
if HasAESGCMHardwareSupport {
|
||||
return SecurityType_AES128_GCM
|
||||
}
|
||||
return SecurityType_CHACHA20_POLY1305
|
||||
|
@ -1,6 +1,7 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@ -55,12 +56,16 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
|
||||
account.Id = u.String()
|
||||
|
||||
switch account.Flow {
|
||||
case "", vless.XRV:
|
||||
case "":
|
||||
case vless.XRV:
|
||||
if c.Decryption != "none" {
|
||||
return nil, errors.New(`VLESS clients: "decryption" doesn't support "flow" yet`)
|
||||
}
|
||||
default:
|
||||
return nil, errors.New(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`)
|
||||
}
|
||||
|
||||
if account.Encryption != "" {
|
||||
if len(account.Encryption) > 0 {
|
||||
return nil, errors.New(`VLESS clients: "encryption" should not in inbound settings`)
|
||||
}
|
||||
|
||||
@ -68,10 +73,34 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
|
||||
config.Clients[idx] = user
|
||||
}
|
||||
|
||||
if c.Decryption != "none" {
|
||||
if !func() bool {
|
||||
s := strings.Split(c.Decryption, "-mlkem768seed-")
|
||||
if len(s) != 2 {
|
||||
return false
|
||||
}
|
||||
if s[0] != "1rtt" {
|
||||
t := strings.TrimSuffix(s[0], "min")
|
||||
if t == s[0] {
|
||||
return false
|
||||
}
|
||||
i, err := strconv.Atoi(t)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
config.Minutes = uint32(i)
|
||||
}
|
||||
b, err := base64.RawURLEncoding.DecodeString(s[1])
|
||||
if len(b) != 64 || err != nil {
|
||||
return false
|
||||
}
|
||||
config.Decryption = s[1]
|
||||
return true
|
||||
}() && c.Decryption != "none" {
|
||||
if c.Decryption == "" {
|
||||
return nil, errors.New(`VLESS settings: please add/set "decryption":"none" to every settings`)
|
||||
}
|
||||
config.Decryption = c.Decryption
|
||||
return nil, errors.New(`VLESS settings: unsupported "decryption": ` + c.Decryption)
|
||||
}
|
||||
|
||||
for _, fb := range c.Fallbacks {
|
||||
var i uint16
|
||||
@ -143,16 +172,16 @@ type VLessOutboundConfig struct {
|
||||
func (c *VLessOutboundConfig) Build() (proto.Message, error) {
|
||||
config := new(outbound.Config)
|
||||
|
||||
if len(c.Vnext) == 0 {
|
||||
return nil, errors.New(`VLESS settings: "vnext" is empty`)
|
||||
if len(c.Vnext) != 1 {
|
||||
return nil, errors.New(`VLESS settings: "vnext" should have one and only one member`)
|
||||
}
|
||||
config.Vnext = make([]*protocol.ServerEndpoint, len(c.Vnext))
|
||||
for idx, rec := range c.Vnext {
|
||||
if rec.Address == nil {
|
||||
return nil, errors.New(`VLESS vnext: "address" is not set`)
|
||||
}
|
||||
if len(rec.Users) == 0 {
|
||||
return nil, errors.New(`VLESS vnext: "users" is empty`)
|
||||
if len(rec.Users) != 1 {
|
||||
return nil, errors.New(`VLESS vnext: "users" should have one and only one member`)
|
||||
}
|
||||
spec := &protocol.ServerEndpoint{
|
||||
Address: rec.Address.Build(),
|
||||
@ -176,14 +205,43 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
|
||||
account.Id = u.String()
|
||||
|
||||
switch account.Flow {
|
||||
case "", vless.XRV, vless.XRV + "-udp443":
|
||||
case "":
|
||||
case vless.XRV, vless.XRV + "-udp443":
|
||||
if account.Encryption != "none" {
|
||||
return nil, errors.New(`VLESS users: "encryption" doesn't support "flow" yet`)
|
||||
}
|
||||
default:
|
||||
return nil, errors.New(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`)
|
||||
}
|
||||
|
||||
if account.Encryption != "none" {
|
||||
if !func() bool {
|
||||
s := strings.Split(account.Encryption, "-mlkem768client-")
|
||||
if len(s) != 2 {
|
||||
return false
|
||||
}
|
||||
if s[0] != "1rtt" {
|
||||
t := strings.TrimSuffix(s[0], "min")
|
||||
if t == s[0] {
|
||||
return false
|
||||
}
|
||||
i, err := strconv.Atoi(t)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
account.Minutes = uint32(i)
|
||||
}
|
||||
b, err := base64.RawURLEncoding.DecodeString(s[1])
|
||||
if len(b) != 1184 || err != nil {
|
||||
return false
|
||||
}
|
||||
account.Encryption = s[1]
|
||||
return true
|
||||
}() && account.Encryption != "none" {
|
||||
if account.Encryption == "" {
|
||||
return nil, errors.New(`VLESS users: please add/set "encryption":"none" for every user`)
|
||||
}
|
||||
return nil, errors.New(`VLESS users: unsupported "encryption": ` + account.Encryption)
|
||||
}
|
||||
|
||||
user.Account = serial.ToTypedMessage(account)
|
||||
spec.User[idx] = user
|
||||
|
@ -17,5 +17,6 @@ func init() {
|
||||
cmdX25519,
|
||||
cmdWG,
|
||||
cmdMLDSA65,
|
||||
cmdMLKEM768,
|
||||
)
|
||||
}
|
||||
|
@ -11,9 +11,9 @@ import (
|
||||
|
||||
var cmdMLDSA65 = &base.Command{
|
||||
UsageLine: `{{.Exec}} mldsa65 [-i "seed (base64.RawURLEncoding)"]`,
|
||||
Short: `Generate key pair for ML-DSA-65 post-quantum signature`,
|
||||
Short: `Generate key pair for ML-DSA-65 post-quantum signature (REALITY)`,
|
||||
Long: `
|
||||
Generate key pair for ML-DSA-65 post-quantum signature.
|
||||
Generate key pair for ML-DSA-65 post-quantum signature (REALITY).
|
||||
|
||||
Random: {{.Exec}} mldsa65
|
||||
|
||||
@ -25,12 +25,16 @@ func init() {
|
||||
cmdMLDSA65.Run = executeMLDSA65 // break init loop
|
||||
}
|
||||
|
||||
var input_seed = cmdMLDSA65.Flag.String("i", "", "")
|
||||
var input_mldsa65 = cmdMLDSA65.Flag.String("i", "", "")
|
||||
|
||||
func executeMLDSA65(cmd *base.Command, args []string) {
|
||||
var seed [32]byte
|
||||
if len(*input_seed) > 0 {
|
||||
s, _ := base64.RawURLEncoding.DecodeString(*input_seed)
|
||||
if len(*input_mldsa65) > 0 {
|
||||
s, _ := base64.RawURLEncoding.DecodeString(*input_mldsa65)
|
||||
if len(s) != 32 {
|
||||
fmt.Println("Invalid length of ML-DSA-65 seed.")
|
||||
return
|
||||
}
|
||||
seed = [32]byte(s)
|
||||
} else {
|
||||
rand.Read(seed[:])
|
||||
|
47
main/commands/all/mlkem768.go
Normal file
47
main/commands/all/mlkem768.go
Normal file
@ -0,0 +1,47 @@
|
||||
package all
|
||||
|
||||
import (
|
||||
"crypto/mlkem"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/xtls/xray-core/main/commands/base"
|
||||
)
|
||||
|
||||
var cmdMLKEM768 = &base.Command{
|
||||
UsageLine: `{{.Exec}} mlkem768 [-i "seed (base64.RawURLEncoding)"]`,
|
||||
Short: `Generate key pair for ML-KEM-768 post-quantum key exchange (VLESS)`,
|
||||
Long: `
|
||||
Generate key pair for ML-KEM-768 post-quantum key exchange (VLESS).
|
||||
|
||||
Random: {{.Exec}} mlkem768
|
||||
|
||||
From seed: {{.Exec}} mlkem768 -i "seed (base64.RawURLEncoding)"
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdMLKEM768.Run = executeMLKEM768 // break init loop
|
||||
}
|
||||
|
||||
var input_mlkem768 = cmdMLKEM768.Flag.String("i", "", "")
|
||||
|
||||
func executeMLKEM768(cmd *base.Command, args []string) {
|
||||
var seed [64]byte
|
||||
if len(*input_mlkem768) > 0 {
|
||||
s, _ := base64.RawURLEncoding.DecodeString(*input_mlkem768)
|
||||
if len(s) != 64 {
|
||||
fmt.Println("Invalid length of ML-KEM-768 seed.")
|
||||
return
|
||||
}
|
||||
seed = [64]byte(s)
|
||||
} else {
|
||||
rand.Read(seed[:])
|
||||
}
|
||||
key, _ := mlkem.NewDecapsulationKey768(seed[:])
|
||||
pub := key.EncapsulationKey()
|
||||
fmt.Printf("Seed: %v\nClient: %v",
|
||||
base64.RawURLEncoding.EncodeToString(seed[:]),
|
||||
base64.RawURLEncoding.EncodeToString(pub.Bytes()))
|
||||
}
|
@ -9,9 +9,9 @@ import (
|
||||
|
||||
var cmdUUID = &base.Command{
|
||||
UsageLine: `{{.Exec}} uuid [-i "example"]`,
|
||||
Short: `Generate UUIDv4 or UUIDv5`,
|
||||
Short: `Generate UUIDv4 or UUIDv5 (VLESS)`,
|
||||
Long: `
|
||||
Generate UUIDv4 or UUIDv5.
|
||||
Generate UUIDv4 or UUIDv5 (VLESS).
|
||||
|
||||
UUIDv4 (random): {{.Exec}} uuid
|
||||
|
||||
|
@ -6,9 +6,9 @@ import (
|
||||
|
||||
var cmdWG = &base.Command{
|
||||
UsageLine: `{{.Exec}} wg [-i "private key (base64.StdEncoding)"]`,
|
||||
Short: `Generate key pair for wireguard key exchange`,
|
||||
Short: `Generate key pair for X25519 key exchange (WireGuard)`,
|
||||
Long: `
|
||||
Generate key pair for wireguard key exchange.
|
||||
Generate key pair for X25519 key exchange (WireGuard).
|
||||
|
||||
Random: {{.Exec}} wg
|
||||
|
||||
|
@ -6,9 +6,9 @@ import (
|
||||
|
||||
var cmdX25519 = &base.Command{
|
||||
UsageLine: `{{.Exec}} x25519 [-i "private key (base64.RawURLEncoding)"] [--std-encoding]`,
|
||||
Short: `Generate key pair for x25519 key exchange`,
|
||||
Short: `Generate key pair for X25519 key exchange (REALITY)`,
|
||||
Long: `
|
||||
Generate key pair for x25519 key exchange.
|
||||
Generate key pair for X25519 key exchange (REALITY).
|
||||
|
||||
Random: {{.Exec}} x25519
|
||||
|
||||
|
@ -18,6 +18,7 @@ func (a *Account) AsAccount() (protocol.Account, error) {
|
||||
ID: protocol.NewID(id),
|
||||
Flow: a.Flow, // needs parser here?
|
||||
Encryption: a.Encryption, // needs parser here?
|
||||
Minutes: a.Minutes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -27,8 +28,9 @@ type MemoryAccount struct {
|
||||
ID *protocol.ID
|
||||
// Flow of the account. May be "xtls-rprx-vision".
|
||||
Flow string
|
||||
// Encryption of the account. Used for client connections, and only accepts "none" for now.
|
||||
|
||||
Encryption string
|
||||
Minutes uint32
|
||||
}
|
||||
|
||||
// Equals implements protocol.Account.Equals().
|
||||
@ -45,5 +47,6 @@ func (a *MemoryAccount) ToProto() proto.Message {
|
||||
Id: a.ID.String(),
|
||||
Flow: a.Flow,
|
||||
Encryption: a.Encryption,
|
||||
Minutes: a.Minutes,
|
||||
}
|
||||
}
|
||||
|
@ -29,8 +29,8 @@ type Account struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
// Flow settings. May be "xtls-rprx-vision".
|
||||
Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"`
|
||||
// Encryption settings. Only applies to client side, and only accepts "none" for now.
|
||||
Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"`
|
||||
Minutes uint32 `protobuf:"varint,4,opt,name=minutes,proto3" json:"minutes,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Account) Reset() {
|
||||
@ -84,23 +84,32 @@ func (x *Account) GetEncryption() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Account) GetMinutes() uint32 {
|
||||
if x != nil {
|
||||
return x.Minutes
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_proxy_vless_account_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_proxy_vless_account_proto_rawDesc = []byte{
|
||||
0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x63,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61,
|
||||
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x4d, 0x0a,
|
||||
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x67, 0x0a,
|
||||
0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x77,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, 0x0a,
|
||||
0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x52, 0x0a, 0x14,
|
||||
0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76,
|
||||
0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
|
||||
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x10,
|
||||
0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73,
|
||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07,
|
||||
0x6d, 0x69, 0x6e, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x6d,
|
||||
0x69, 0x6e, 0x75, 0x74, 0x65, 0x73, 0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72,
|
||||
0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x50, 0x01,
|
||||
0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c,
|
||||
0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78,
|
||||
0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50,
|
||||
0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -11,6 +11,7 @@ message Account {
|
||||
string id = 1;
|
||||
// Flow settings. May be "xtls-rprx-vision".
|
||||
string flow = 2;
|
||||
// Encryption settings. Only applies to client side, and only accepts "none" for now.
|
||||
|
||||
string encryption = 3;
|
||||
uint32 minutes = 4;
|
||||
}
|
||||
|
228
proxy/vless/encryption/client.go
Normal file
228
proxy/vless/encryption/client.go
Normal file
@ -0,0 +1,228 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"crypto/mlkem"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
var ClientCipher byte
|
||||
|
||||
func init() {
|
||||
if !protocol.HasAESGCMHardwareSupport {
|
||||
ClientCipher = 1
|
||||
}
|
||||
}
|
||||
|
||||
type ClientInstance struct {
|
||||
sync.RWMutex
|
||||
eKeyNfs *mlkem.EncapsulationKey768
|
||||
minutes time.Duration
|
||||
expire time.Time
|
||||
baseKey []byte
|
||||
reuse []byte
|
||||
}
|
||||
|
||||
type ClientConn struct {
|
||||
net.Conn
|
||||
instance *ClientInstance
|
||||
baseKey []byte
|
||||
reuse []byte
|
||||
random []byte
|
||||
aead cipher.AEAD
|
||||
nonce []byte
|
||||
peerAead cipher.AEAD
|
||||
peerNonce []byte
|
||||
peerCache []byte
|
||||
}
|
||||
|
||||
func (i *ClientInstance) Init(eKeyNfsData []byte, minutes time.Duration) (err error) {
|
||||
i.eKeyNfs, err = mlkem.NewEncapsulationKey768(eKeyNfsData)
|
||||
i.minutes = minutes
|
||||
return
|
||||
}
|
||||
|
||||
func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
|
||||
if i.eKeyNfs == nil {
|
||||
return nil, errors.New("uninitialized")
|
||||
}
|
||||
c := &ClientConn{Conn: conn}
|
||||
|
||||
if i.minutes > 0 {
|
||||
i.RLock()
|
||||
if time.Now().Before(i.expire) {
|
||||
c.instance = i
|
||||
c.baseKey = i.baseKey
|
||||
c.reuse = i.reuse
|
||||
i.RUnlock()
|
||||
return c, nil
|
||||
}
|
||||
i.RUnlock()
|
||||
}
|
||||
|
||||
nfsKey, encapsulatedNfsKey := i.eKeyNfs.Encapsulate()
|
||||
seed := make([]byte, 64)
|
||||
rand.Read(seed)
|
||||
dKeyPfs, _ := mlkem.NewDecapsulationKey768(seed)
|
||||
eKeyPfs := dKeyPfs.EncapsulationKey().Bytes()
|
||||
padding := crypto.RandBetween(100, 1000)
|
||||
|
||||
clientHello := make([]byte, 1088+1184+1+5+padding)
|
||||
copy(clientHello, encapsulatedNfsKey)
|
||||
copy(clientHello[1088:], eKeyPfs)
|
||||
clientHello[2272] = ClientCipher
|
||||
encodeHeader(clientHello[2273:], int(padding))
|
||||
|
||||
if _, err := c.Conn.Write(clientHello); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// we can send more padding if needed
|
||||
|
||||
peerServerHello := make([]byte, 1088+21)
|
||||
if _, err := io.ReadFull(c.Conn, peerServerHello); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encapsulatedPfsKey := peerServerHello[:1088]
|
||||
c.reuse = peerServerHello[1088:]
|
||||
|
||||
pfsKey, err := dKeyPfs.Decapsulate(encapsulatedPfsKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.baseKey = append(nfsKey, pfsKey...)
|
||||
|
||||
authKey := make([]byte, 32)
|
||||
hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfs).Read(authKey)
|
||||
nonce := make([]byte, 12)
|
||||
VLESS, _ := newAead(ClientCipher, authKey).Open(nil, nonce, c.reuse, encapsulatedPfsKey)
|
||||
if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more message
|
||||
return nil, errors.New("invalid server").AtError()
|
||||
}
|
||||
|
||||
if i.minutes > 0 {
|
||||
i.Lock()
|
||||
i.expire = time.Now().Add(i.minutes)
|
||||
i.baseKey = c.baseKey
|
||||
i.reuse = c.reuse
|
||||
i.Unlock()
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *ClientConn) Write(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
var data []byte
|
||||
if c.aead == nil {
|
||||
c.random = make([]byte, 32)
|
||||
rand.Read(c.random)
|
||||
key := make([]byte, 32)
|
||||
hkdf.New(sha256.New, c.baseKey, c.random, c.reuse).Read(key)
|
||||
c.aead = newAead(ClientCipher, key)
|
||||
c.nonce = make([]byte, 12)
|
||||
|
||||
data = make([]byte, 21+32+5+len(b)+16)
|
||||
copy(data, c.reuse)
|
||||
copy(data[21:], c.random)
|
||||
encodeHeader(data[53:], len(b)+16)
|
||||
c.aead.Seal(data[:58], c.nonce, b, data[53:58])
|
||||
} else {
|
||||
data = make([]byte, 5+len(b)+16)
|
||||
encodeHeader(data, len(b)+16)
|
||||
c.aead.Seal(data[:5], c.nonce, b, data[:5])
|
||||
}
|
||||
increaseNonce(c.nonce)
|
||||
if _, err := c.Conn.Write(data); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
peerHeader := make([]byte, 5)
|
||||
if c.peerAead == nil {
|
||||
if c.instance == nil {
|
||||
for {
|
||||
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
peerPadding, _ := decodeHeader(peerHeader)
|
||||
if peerPadding == 0 {
|
||||
break
|
||||
}
|
||||
if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
peerRandom := make([]byte, 32)
|
||||
copy(peerRandom, peerHeader)
|
||||
if _, err := io.ReadFull(c.Conn, peerRandom[5:]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if c.random == nil {
|
||||
return 0, errors.New("can not Read() first")
|
||||
}
|
||||
peerKey := make([]byte, 32)
|
||||
hkdf.New(sha256.New, c.baseKey, peerRandom, c.random).Read(peerKey)
|
||||
c.peerAead = newAead(ClientCipher, peerKey)
|
||||
c.peerNonce = make([]byte, 12)
|
||||
}
|
||||
if len(c.peerCache) != 0 {
|
||||
n := copy(b, c.peerCache)
|
||||
c.peerCache = c.peerCache[n:]
|
||||
return n, nil
|
||||
}
|
||||
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
peerLength, err := decodeHeader(peerHeader) // 17~17000
|
||||
if err != nil {
|
||||
if c.instance != nil {
|
||||
c.instance.Lock()
|
||||
if bytes.Equal(c.reuse, c.instance.reuse) {
|
||||
c.instance.expire = time.Now() // expired
|
||||
}
|
||||
c.instance.Unlock()
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
peerData := make([]byte, peerLength)
|
||||
if _, err := io.ReadFull(c.Conn, peerData); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dst := peerData[:peerLength-16]
|
||||
if len(dst) <= len(b) {
|
||||
dst = b[:len(dst)] // max=8192 is recommended for peer
|
||||
}
|
||||
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
|
||||
increaseNonce(c.peerNonce)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(dst) > len(b) {
|
||||
c.peerCache = dst[copy(b, dst):]
|
||||
dst = b // for len(dst)
|
||||
}
|
||||
return len(dst), nil
|
||||
}
|
55
proxy/vless/encryption/common.go
Normal file
55
proxy/vless/encryption/common.go
Normal file
@ -0,0 +1,55 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"strconv"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
func encodeHeader(b []byte, l int) {
|
||||
b[0] = 23
|
||||
b[1] = 3
|
||||
b[2] = 3
|
||||
b[3] = byte(l >> 8)
|
||||
b[4] = byte(l)
|
||||
}
|
||||
|
||||
func decodeHeader(b []byte) (int, error) {
|
||||
if b[0] == 23 && b[1] == 3 && b[2] == 3 {
|
||||
l := int(b[3])<<8 | int(b[4])
|
||||
if l < 17 || l > 17000 { // TODO
|
||||
return 0, errors.New("invalid length in record's header: " + strconv.Itoa(l))
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
return 0, errors.New("invalid record's header")
|
||||
}
|
||||
|
||||
func newAead(c byte, k []byte) cipher.AEAD {
|
||||
switch c {
|
||||
case 0:
|
||||
if block, err := aes.NewCipher(k); err == nil {
|
||||
aead, _ := cipher.NewGCM(block)
|
||||
return aead
|
||||
}
|
||||
case 1:
|
||||
aead, _ := chacha20poly1305.New(k)
|
||||
return aead
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func increaseNonce(nonce []byte) {
|
||||
for i := range 12 {
|
||||
nonce[11-i]++
|
||||
if nonce[11-i] != 0 {
|
||||
break
|
||||
}
|
||||
if i == 11 {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
251
proxy/vless/encryption/server.go
Normal file
251
proxy/vless/encryption/server.go
Normal file
@ -0,0 +1,251 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"crypto/mlkem"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
type ServerSession struct {
|
||||
expire time.Time
|
||||
cipher byte
|
||||
baseKey []byte
|
||||
randoms sync.Map
|
||||
}
|
||||
|
||||
type ServerInstance struct {
|
||||
sync.RWMutex
|
||||
dKeyNfs *mlkem.DecapsulationKey768
|
||||
minutes time.Duration
|
||||
sessions map[[21]byte]*ServerSession
|
||||
}
|
||||
|
||||
type ServerConn struct {
|
||||
net.Conn
|
||||
cipher byte
|
||||
baseKey []byte
|
||||
reuse []byte
|
||||
peerRandom []byte
|
||||
peerAead cipher.AEAD
|
||||
peerNonce []byte
|
||||
peerCache []byte
|
||||
aead cipher.AEAD
|
||||
nonce []byte
|
||||
}
|
||||
|
||||
func (i *ServerInstance) Init(dKeyNfsData []byte, minutes time.Duration) (err error) {
|
||||
i.dKeyNfs, err = mlkem.NewDecapsulationKey768(dKeyNfsData)
|
||||
if minutes > 0 {
|
||||
i.minutes = minutes
|
||||
i.sessions = make(map[[21]byte]*ServerSession)
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(time.Minute)
|
||||
now := time.Now()
|
||||
i.Lock()
|
||||
for index, session := range i.sessions {
|
||||
if now.After(session.expire) {
|
||||
delete(i.sessions, index)
|
||||
}
|
||||
}
|
||||
i.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) {
|
||||
if i.dKeyNfs == nil {
|
||||
return nil, errors.New("uninitialized")
|
||||
}
|
||||
c := &ServerConn{Conn: conn}
|
||||
|
||||
peerReuseHello := make([]byte, 21+32)
|
||||
if _, err := io.ReadFull(c.Conn, peerReuseHello); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if i.minutes > 0 {
|
||||
i.RLock()
|
||||
s := i.sessions[[21]byte(peerReuseHello)]
|
||||
i.RUnlock()
|
||||
if s != nil {
|
||||
if _, replay := s.randoms.LoadOrStore([32]byte(peerReuseHello[21:]), true); !replay {
|
||||
c.cipher = s.cipher
|
||||
c.baseKey = s.baseKey
|
||||
c.reuse = peerReuseHello[:21]
|
||||
c.peerRandom = peerReuseHello[21:]
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
peerHeader := make([]byte, 5)
|
||||
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if l, _ := decodeHeader(peerHeader); l != 0 {
|
||||
c.Conn.Write(make([]byte, crypto.RandBetween(100, 1000))) // make client do new handshake
|
||||
return nil, errors.New("invalid reuse")
|
||||
}
|
||||
|
||||
peerClientHello := make([]byte, 1088+1184+1)
|
||||
copy(peerClientHello, peerReuseHello)
|
||||
copy(peerClientHello[53:], peerHeader)
|
||||
if _, err := io.ReadFull(c.Conn, peerClientHello[58:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encapsulatedNfsKey := peerClientHello[:1088]
|
||||
eKeyPfsData := peerClientHello[1088:2272]
|
||||
c.cipher = peerClientHello[2272]
|
||||
if c.cipher != 0 && c.cipher != 1 {
|
||||
return nil, errors.New("invalid cipher")
|
||||
}
|
||||
|
||||
nfsKey, err := i.dKeyNfs.Decapsulate(encapsulatedNfsKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eKeyPfs, err := mlkem.NewEncapsulationKey768(eKeyPfsData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pfsKey, encapsulatedPfsKey := eKeyPfs.Encapsulate()
|
||||
c.baseKey = append(nfsKey, pfsKey...)
|
||||
|
||||
authKey := make([]byte, 32)
|
||||
hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfsData).Read(authKey)
|
||||
nonce := make([]byte, 12)
|
||||
c.reuse = newAead(c.cipher, authKey).Seal(nil, nonce, []byte("VLESS"), encapsulatedPfsKey)
|
||||
|
||||
padding := crypto.RandBetween(100, 1000)
|
||||
|
||||
serverHello := make([]byte, 1088+21+5+padding)
|
||||
copy(serverHello, encapsulatedPfsKey)
|
||||
copy(serverHello[1088:], c.reuse)
|
||||
encodeHeader(serverHello[1109:], int(padding))
|
||||
|
||||
if _, err := c.Conn.Write(serverHello); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if i.minutes > 0 {
|
||||
i.Lock()
|
||||
i.sessions[[21]byte(c.reuse)] = &ServerSession{
|
||||
expire: time.Now().Add(i.minutes),
|
||||
cipher: c.cipher,
|
||||
baseKey: c.baseKey,
|
||||
}
|
||||
i.Unlock()
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *ServerConn) Read(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
peerHeader := make([]byte, 5)
|
||||
if c.peerAead == nil {
|
||||
if c.peerRandom == nil {
|
||||
for {
|
||||
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
peerPadding, _ := decodeHeader(peerHeader)
|
||||
if peerPadding == 0 {
|
||||
break
|
||||
}
|
||||
if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
peerIndex := make([]byte, 21)
|
||||
copy(peerIndex, peerHeader)
|
||||
if _, err := io.ReadFull(c.Conn, peerIndex[5:]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !bytes.Equal(peerIndex, c.reuse) {
|
||||
return 0, errors.New("naughty boy")
|
||||
}
|
||||
c.peerRandom = make([]byte, 32)
|
||||
if _, err := io.ReadFull(c.Conn, c.peerRandom); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
peerKey := make([]byte, 32)
|
||||
hkdf.New(sha256.New, c.baseKey, c.peerRandom, c.reuse).Read(peerKey)
|
||||
c.peerAead = newAead(c.cipher, peerKey)
|
||||
c.peerNonce = make([]byte, 12)
|
||||
}
|
||||
if len(c.peerCache) != 0 {
|
||||
n := copy(b, c.peerCache)
|
||||
c.peerCache = c.peerCache[n:]
|
||||
return n, nil
|
||||
}
|
||||
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
peerLength, err := decodeHeader(peerHeader) // 17~17000
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
peerData := make([]byte, peerLength)
|
||||
if _, err := io.ReadFull(c.Conn, peerData); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dst := peerData[:peerLength-16]
|
||||
if len(dst) <= len(b) {
|
||||
dst = b[:len(dst)] // max=8192 is recommended for peer
|
||||
}
|
||||
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
|
||||
increaseNonce(c.peerNonce)
|
||||
if err != nil {
|
||||
return 0, errors.New("error")
|
||||
}
|
||||
if len(dst) > len(b) {
|
||||
c.peerCache = dst[copy(b, dst):]
|
||||
dst = b // for len(dst)
|
||||
}
|
||||
return len(dst), nil
|
||||
}
|
||||
|
||||
func (c *ServerConn) Write(b []byte) (int, error) { // after first Read()
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
var data []byte
|
||||
if c.aead == nil {
|
||||
if c.peerRandom == nil {
|
||||
return 0, errors.New("can not Write() first")
|
||||
}
|
||||
data = make([]byte, 32+5+len(b)+16)
|
||||
rand.Read(data[:32])
|
||||
key := make([]byte, 32)
|
||||
hkdf.New(sha256.New, c.baseKey, data[:32], c.peerRandom).Read(key)
|
||||
c.aead = newAead(c.cipher, key)
|
||||
c.nonce = make([]byte, 12)
|
||||
encodeHeader(data[32:], len(b)+16)
|
||||
c.aead.Seal(data[:37], c.nonce, b, data[32:37])
|
||||
} else {
|
||||
data = make([]byte, 5+len(b)+16)
|
||||
encodeHeader(data, len(b)+16)
|
||||
c.aead.Seal(data[:5], c.nonce, b, data[:5])
|
||||
}
|
||||
increaseNonce(c.nonce)
|
||||
if _, err := c.Conn.Write(data); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
@ -112,10 +112,9 @@ type Config struct {
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"`
|
||||
// Decryption settings. Only applies to server side, and only accepts "none"
|
||||
// for now.
|
||||
Decryption string `protobuf:"bytes,2,opt,name=decryption,proto3" json:"decryption,omitempty"`
|
||||
Fallbacks []*Fallback `protobuf:"bytes,3,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"`
|
||||
Fallbacks []*Fallback `protobuf:"bytes,2,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"`
|
||||
Decryption string `protobuf:"bytes,3,opt,name=decryption,proto3" json:"decryption,omitempty"`
|
||||
Minutes uint32 `protobuf:"varint,4,opt,name=minutes,proto3" json:"minutes,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Config) Reset() {
|
||||
@ -155,6 +154,13 @@ func (x *Config) GetClients() []*protocol.User {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetFallbacks() []*Fallback {
|
||||
if x != nil {
|
||||
return x.Fallbacks
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetDecryption() string {
|
||||
if x != nil {
|
||||
return x.Decryption
|
||||
@ -162,11 +168,11 @@ func (x *Config) GetDecryption() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetFallbacks() []*Fallback {
|
||||
func (x *Config) GetMinutes() uint32 {
|
||||
if x != nil {
|
||||
return x.Fallbacks
|
||||
return x.Minutes
|
||||
}
|
||||
return nil
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_proxy_vless_inbound_config_proto protoreflect.FileDescriptor
|
||||
@ -185,25 +191,26 @@ var file_proxy_vless_inbound_config_proto_rawDesc = []byte{
|
||||
0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65,
|
||||
0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xa0, 0x01,
|
||||
0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xba, 0x01,
|
||||
0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65,
|
||||
0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
|
||||
0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1e,
|
||||
0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40,
|
||||
0x0a, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
|
||||
0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x40,
|
||||
0x0a, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x22, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76,
|
||||
0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2e, 0x46, 0x61, 0x6c,
|
||||
0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73,
|
||||
0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
|
||||
0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78,
|
||||
0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72,
|
||||
0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e,
|
||||
0x64, 0xaa, 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56,
|
||||
0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x12, 0x18, 0x0a, 0x07, 0x6d, 0x69, 0x6e, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||
0x0d, 0x52, 0x07, 0x6d, 0x69, 0x6e, 0x75, 0x74, 0x65, 0x73, 0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f,
|
||||
0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65,
|
||||
0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69,
|
||||
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72,
|
||||
0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c,
|
||||
0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x18, 0x58, 0x72,
|
||||
0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49,
|
||||
0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -19,8 +19,8 @@ message Fallback {
|
||||
|
||||
message Config {
|
||||
repeated xray.common.protocol.User clients = 1;
|
||||
// Decryption settings. Only applies to server side, and only accepts "none"
|
||||
// for now.
|
||||
string decryption = 2;
|
||||
repeated Fallback fallbacks = 3;
|
||||
repeated Fallback fallbacks = 2;
|
||||
|
||||
string decryption = 3;
|
||||
uint32 minutes = 4;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
gotls "crypto/tls"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@ -29,6 +30,7 @@ import (
|
||||
"github.com/xtls/xray-core/proxy"
|
||||
"github.com/xtls/xray-core/proxy/vless"
|
||||
"github.com/xtls/xray-core/proxy/vless/encoding"
|
||||
"github.com/xtls/xray-core/proxy/vless/encryption"
|
||||
"github.com/xtls/xray-core/transport/internet/reality"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
"github.com/xtls/xray-core/transport/internet/tls"
|
||||
@ -67,6 +69,7 @@ type Handler struct {
|
||||
policyManager policy.Manager
|
||||
validator vless.Validator
|
||||
dns dns.Client
|
||||
decryption *encryption.ServerInstance
|
||||
fallbacks map[string]map[string]map[string]*Fallback // or nil
|
||||
// regexps map[string]*regexp.Regexp // or nil
|
||||
}
|
||||
@ -81,6 +84,14 @@ func New(ctx context.Context, config *Config, dc dns.Client, validator vless.Val
|
||||
validator: validator,
|
||||
}
|
||||
|
||||
d, _ := base64.RawURLEncoding.DecodeString(config.Decryption)
|
||||
if len(d) == 64 {
|
||||
handler.decryption = &encryption.ServerInstance{}
|
||||
if err := handler.decryption.Init(d, time.Duration(config.Minutes)*time.Minute); err != nil {
|
||||
return nil, errors.New("failed to use mlkem768seed").Base(err).AtError()
|
||||
}
|
||||
}
|
||||
|
||||
if config.Fallbacks != nil {
|
||||
handler.fallbacks = make(map[string]map[string]map[string]*Fallback)
|
||||
// handler.regexps = make(map[string]*regexp.Regexp)
|
||||
@ -204,6 +215,14 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
||||
return errors.New("unable to set read deadline").Base(err).AtWarning()
|
||||
}
|
||||
|
||||
if h.decryption != nil {
|
||||
var err error
|
||||
connection, err = h.decryption.Handshake(connection)
|
||||
if err != nil {
|
||||
return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo()
|
||||
}
|
||||
}
|
||||
|
||||
first := buf.FromBytes(make([]byte, buf.Size))
|
||||
first.Clear()
|
||||
firstLen, errR := first.ReadFrom(connection)
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
gotls "crypto/tls"
|
||||
"encoding/base64"
|
||||
"reflect"
|
||||
"time"
|
||||
"unsafe"
|
||||
@ -24,6 +25,7 @@ import (
|
||||
"github.com/xtls/xray-core/proxy"
|
||||
"github.com/xtls/xray-core/proxy/vless"
|
||||
"github.com/xtls/xray-core/proxy/vless/encoding"
|
||||
"github.com/xtls/xray-core/proxy/vless/encryption"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/reality"
|
||||
@ -43,6 +45,7 @@ type Handler struct {
|
||||
serverPicker protocol.ServerPicker
|
||||
policyManager policy.Manager
|
||||
cone bool
|
||||
encryption *encryption.ClientInstance
|
||||
}
|
||||
|
||||
// New creates a new VLess outbound handler.
|
||||
@ -64,6 +67,15 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
|
||||
cone: ctx.Value("cone").(bool),
|
||||
}
|
||||
|
||||
a := handler.serverPicker.PickServer().PickUser().Account.(*vless.MemoryAccount)
|
||||
e, _ := base64.RawURLEncoding.DecodeString(a.Encryption)
|
||||
if len(e) == 1184 {
|
||||
handler.encryption = &encryption.ClientInstance{}
|
||||
if err := handler.encryption.Init(e, time.Duration(a.Minutes)*time.Minute); err != nil {
|
||||
return nil, errors.New("failed to use mlkem768client").Base(err).AtError()
|
||||
}
|
||||
}
|
||||
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
@ -98,6 +110,14 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
target := ob.Target
|
||||
errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination().NetAddr())
|
||||
|
||||
if h.encryption != nil {
|
||||
var err error
|
||||
conn, err = h.encryption.Handshake(conn)
|
||||
if err != nil {
|
||||
return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo()
|
||||
}
|
||||
}
|
||||
|
||||
command := protocol.RequestCommandTCP
|
||||
if target.Network == net.Network_UDP {
|
||||
command = protocol.RequestCommandUDP
|
||||
|
Loading…
Reference in New Issue
Block a user