mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-08-23 01:56:48 +08:00
Compare commits
21 Commits
5129c1e4ff
...
vless
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b33555cc0a | ||
![]() |
38cc306c95 | ||
![]() |
373558ed7a | ||
![]() |
84835bec7d | ||
![]() |
49580705f6 | ||
![]() |
d1fb485212 | ||
![]() |
bfe4820f2f | ||
![]() |
2807ee432a | ||
![]() |
7f778a4e2f | ||
![]() |
09cc92c61d | ||
![]() |
0fd7691d6b | ||
![]() |
1720be168f | ||
![]() |
3c20bddfcf | ||
![]() |
23d7aad461 | ||
![]() |
5c61142048 | ||
![]() |
ec1cc35188 | ||
![]() |
7ffb555fc8 | ||
![]() |
2e6a88307c | ||
![]() |
3e19bf9233 | ||
![]() |
fc137d2612 | ||
![]() |
f61c14e9c6 |
@@ -13,8 +13,6 @@ const (
|
|||||||
Size = 8192
|
Size = 8192
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrBufferFull = errors.New("buffer is full")
|
|
||||||
|
|
||||||
var zero = [Size * 10]byte{0}
|
var zero = [Size * 10]byte{0}
|
||||||
|
|
||||||
var pool = bytespool.GetPool(Size)
|
var pool = bytespool.GetPool(Size)
|
||||||
@@ -260,16 +258,13 @@ func (b *Buffer) IsFull() bool {
|
|||||||
func (b *Buffer) Write(data []byte) (int, error) {
|
func (b *Buffer) Write(data []byte) (int, error) {
|
||||||
nBytes := copy(b.v[b.end:], data)
|
nBytes := copy(b.v[b.end:], data)
|
||||||
b.end += int32(nBytes)
|
b.end += int32(nBytes)
|
||||||
if nBytes < len(data) {
|
|
||||||
return nBytes, ErrBufferFull
|
|
||||||
}
|
|
||||||
return nBytes, nil
|
return nBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteByte writes a single byte into the buffer.
|
// WriteByte writes a single byte into the buffer.
|
||||||
func (b *Buffer) WriteByte(v byte) error {
|
func (b *Buffer) WriteByte(v byte) error {
|
||||||
if b.IsFull() {
|
if b.IsFull() {
|
||||||
return ErrBufferFull
|
return errors.New("buffer full")
|
||||||
}
|
}
|
||||||
b.v[b.end] = v
|
b.v[b.end] = v
|
||||||
b.end++
|
b.end++
|
||||||
|
@@ -79,20 +79,18 @@ type CommandSwitchAccount struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
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
|
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
|
||||||
// Keep in sync with crypto/aes/cipher_s390x.go.
|
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH
|
||||||
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR &&
|
hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le"
|
||||||
(cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
|
|
||||||
|
|
||||||
hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 ||
|
HasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64
|
||||||
runtime.GOARCH == "arm64" && hasGCMAsmARM64 ||
|
|
||||||
runtime.GOARCH == "s390x" && hasGCMAsmS390X
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (sc *SecurityConfig) GetSecurityType() SecurityType {
|
func (sc *SecurityConfig) GetSecurityType() SecurityType {
|
||||||
if sc == nil || sc.Type == SecurityType_AUTO {
|
if sc == nil || sc.Type == SecurityType_AUTO {
|
||||||
if hasAESGCMHardwareSupport {
|
if HasAESGCMHardwareSupport {
|
||||||
return SecurityType_AES128_GCM
|
return SecurityType_AES128_GCM
|
||||||
}
|
}
|
||||||
return SecurityType_CHACHA20_POLY1305
|
return SecurityType_CHACHA20_POLY1305
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -68,10 +69,46 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
|
|||||||
config.Clients[idx] = user
|
config.Clients[idx] = user
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Decryption != "none" {
|
config.Decryption = c.Decryption
|
||||||
|
if !func() bool {
|
||||||
|
s := strings.Split(config.Decryption, ".")
|
||||||
|
if len(s) != 5 || s[2] != "mlkem768Seed" {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
switch s[1] {
|
||||||
|
case "native":
|
||||||
|
case "divide":
|
||||||
|
config.XorMode = 1
|
||||||
|
case "random":
|
||||||
|
config.XorMode = 2
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if b, _ := base64.RawURLEncoding.DecodeString(s[3]); len(b) != 32 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if b, _ := base64.RawURLEncoding.DecodeString(s[4]); len(b) != 64 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
config.Decryption = s[4] + "." + s[3]
|
||||||
|
return true
|
||||||
|
}() && config.Decryption != "none" {
|
||||||
|
if config.Decryption == "" {
|
||||||
return nil, errors.New(`VLESS settings: please add/set "decryption":"none" to every settings`)
|
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": ` + config.Decryption)
|
||||||
|
}
|
||||||
|
|
||||||
for _, fb := range c.Fallbacks {
|
for _, fb := range c.Fallbacks {
|
||||||
var i uint16
|
var i uint16
|
||||||
@@ -143,16 +180,16 @@ type VLessOutboundConfig struct {
|
|||||||
func (c *VLessOutboundConfig) Build() (proto.Message, error) {
|
func (c *VLessOutboundConfig) Build() (proto.Message, error) {
|
||||||
config := new(outbound.Config)
|
config := new(outbound.Config)
|
||||||
|
|
||||||
if len(c.Vnext) == 0 {
|
if len(c.Vnext) != 1 {
|
||||||
return nil, errors.New(`VLESS settings: "vnext" is empty`)
|
return nil, errors.New(`VLESS settings: "vnext" should have one and only one member`)
|
||||||
}
|
}
|
||||||
config.Vnext = make([]*protocol.ServerEndpoint, len(c.Vnext))
|
config.Vnext = make([]*protocol.ServerEndpoint, len(c.Vnext))
|
||||||
for idx, rec := range c.Vnext {
|
for idx, rec := range c.Vnext {
|
||||||
if rec.Address == nil {
|
if rec.Address == nil {
|
||||||
return nil, errors.New(`VLESS vnext: "address" is not set`)
|
return nil, errors.New(`VLESS vnext: "address" is not set`)
|
||||||
}
|
}
|
||||||
if len(rec.Users) == 0 {
|
if len(rec.Users) != 1 {
|
||||||
return nil, errors.New(`VLESS vnext: "users" is empty`)
|
return nil, errors.New(`VLESS vnext: "users" should have one and only one member`)
|
||||||
}
|
}
|
||||||
spec := &protocol.ServerEndpoint{
|
spec := &protocol.ServerEndpoint{
|
||||||
Address: rec.Address.Build(),
|
Address: rec.Address.Build(),
|
||||||
@@ -181,9 +218,45 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
|
|||||||
return nil, errors.New(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`)
|
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, ".")
|
||||||
|
if len(s) != 5 || s[2] != "mlkem768Client" {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
switch s[1] {
|
||||||
|
case "native":
|
||||||
|
case "divide":
|
||||||
|
account.XorMode = 1
|
||||||
|
case "random":
|
||||||
|
account.XorMode = 2
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if b, _ := base64.RawURLEncoding.DecodeString(s[3]); len(b) != 32 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if b, _ := base64.RawURLEncoding.DecodeString(s[4]); len(b) != 1184 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
account.Encryption = s[4] + "." + s[3]
|
||||||
|
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: please add/set "encryption":"none" for every user`)
|
||||||
}
|
}
|
||||||
|
return nil, errors.New(`VLESS users: unsupported "encryption": ` + account.Encryption)
|
||||||
|
}
|
||||||
|
|
||||||
user.Account = serial.ToTypedMessage(account)
|
user.Account = serial.ToTypedMessage(account)
|
||||||
spec.User[idx] = user
|
spec.User[idx] = user
|
||||||
|
@@ -17,5 +17,6 @@ func init() {
|
|||||||
cmdX25519,
|
cmdX25519,
|
||||||
cmdWG,
|
cmdWG,
|
||||||
cmdMLDSA65,
|
cmdMLDSA65,
|
||||||
|
cmdMLKEM768,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -1,17 +1,13 @@
|
|||||||
package all
|
package all
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdh"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"golang.org/x/crypto/curve25519"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Curve25519Genkey(StdEncoding bool, input_base64 string) {
|
func Curve25519Genkey(StdEncoding bool, input_base64 string) {
|
||||||
var output string
|
|
||||||
var err error
|
|
||||||
var privateKey, publicKey []byte
|
|
||||||
var encoding *base64.Encoding
|
var encoding *base64.Encoding
|
||||||
if *input_stdEncoding || StdEncoding {
|
if *input_stdEncoding || StdEncoding {
|
||||||
encoding = base64.StdEncoding
|
encoding = base64.StdEncoding
|
||||||
@@ -19,40 +15,32 @@ func Curve25519Genkey(StdEncoding bool, input_base64 string) {
|
|||||||
encoding = base64.RawURLEncoding
|
encoding = base64.RawURLEncoding
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var privateKey []byte
|
||||||
if len(input_base64) > 0 {
|
if len(input_base64) > 0 {
|
||||||
privateKey, err = encoding.DecodeString(input_base64)
|
privateKey, _ = encoding.DecodeString(input_base64)
|
||||||
if err != nil {
|
if len(privateKey) != 32 {
|
||||||
output = err.Error()
|
fmt.Println("Invalid length of X25519 private key.")
|
||||||
goto out
|
return
|
||||||
}
|
|
||||||
if len(privateKey) != curve25519.ScalarSize {
|
|
||||||
output = "Invalid length of private key."
|
|
||||||
goto out
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if privateKey == nil {
|
if privateKey == nil {
|
||||||
privateKey = make([]byte, curve25519.ScalarSize)
|
privateKey = make([]byte, 32)
|
||||||
if _, err = rand.Read(privateKey); err != nil {
|
rand.Read(privateKey)
|
||||||
output = err.Error()
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modify random bytes using algorithm described at:
|
// Modify random bytes using algorithm described at:
|
||||||
// https://cr.yp.to/ecdh.html.
|
// https://cr.yp.to/ecdh.html
|
||||||
|
// (Just to make sure printing the real private key)
|
||||||
privateKey[0] &= 248
|
privateKey[0] &= 248
|
||||||
privateKey[31] &= 127
|
privateKey[31] &= 127
|
||||||
privateKey[31] |= 64
|
privateKey[31] |= 64
|
||||||
|
|
||||||
if publicKey, err = curve25519.X25519(privateKey, curve25519.Basepoint); err != nil {
|
key, err := ecdh.X25519().NewPrivateKey(privateKey)
|
||||||
output = err.Error()
|
if err != nil {
|
||||||
goto out
|
fmt.Println(err.Error())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
fmt.Printf("PrivateKey: %v\nPassword: %v",
|
||||||
output = fmt.Sprintf("Private key: %v\nPublic key: %v",
|
|
||||||
encoding.EncodeToString(privateKey),
|
encoding.EncodeToString(privateKey),
|
||||||
encoding.EncodeToString(publicKey))
|
encoding.EncodeToString(key.PublicKey().Bytes()))
|
||||||
out:
|
|
||||||
fmt.Println(output)
|
|
||||||
}
|
}
|
||||||
|
@@ -11,9 +11,9 @@ import (
|
|||||||
|
|
||||||
var cmdMLDSA65 = &base.Command{
|
var cmdMLDSA65 = &base.Command{
|
||||||
UsageLine: `{{.Exec}} mldsa65 [-i "seed (base64.RawURLEncoding)"]`,
|
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: `
|
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
|
Random: {{.Exec}} mldsa65
|
||||||
|
|
||||||
@@ -25,12 +25,16 @@ func init() {
|
|||||||
cmdMLDSA65.Run = executeMLDSA65 // break init loop
|
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) {
|
func executeMLDSA65(cmd *base.Command, args []string) {
|
||||||
var seed [32]byte
|
var seed [32]byte
|
||||||
if len(*input_seed) > 0 {
|
if len(*input_mldsa65) > 0 {
|
||||||
s, _ := base64.RawURLEncoding.DecodeString(*input_seed)
|
s, _ := base64.RawURLEncoding.DecodeString(*input_mldsa65)
|
||||||
|
if len(s) != 32 {
|
||||||
|
fmt.Println("Invalid length of ML-DSA-65 seed.")
|
||||||
|
return
|
||||||
|
}
|
||||||
seed = [32]byte(s)
|
seed = [32]byte(s)
|
||||||
} else {
|
} else {
|
||||||
rand.Read(seed[:])
|
rand.Read(seed[:])
|
||||||
|
50
main/commands/all/mlkem768.go
Normal file
50
main/commands/all/mlkem768.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package all
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/mlkem"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha3"
|
||||||
|
"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[:])
|
||||||
|
client := key.EncapsulationKey().Bytes()
|
||||||
|
hash32 := sha3.Sum256(client)
|
||||||
|
fmt.Printf("Seed: %v\nClient: %v\nHash11: %v",
|
||||||
|
base64.RawURLEncoding.EncodeToString(seed[:]),
|
||||||
|
base64.RawURLEncoding.EncodeToString(client),
|
||||||
|
base64.RawURLEncoding.EncodeToString(hash32[:11]))
|
||||||
|
}
|
@@ -9,9 +9,9 @@ import (
|
|||||||
|
|
||||||
var cmdUUID = &base.Command{
|
var cmdUUID = &base.Command{
|
||||||
UsageLine: `{{.Exec}} uuid [-i "example"]`,
|
UsageLine: `{{.Exec}} uuid [-i "example"]`,
|
||||||
Short: `Generate UUIDv4 or UUIDv5`,
|
Short: `Generate UUIDv4 or UUIDv5 (VLESS)`,
|
||||||
Long: `
|
Long: `
|
||||||
Generate UUIDv4 or UUIDv5.
|
Generate UUIDv4 or UUIDv5 (VLESS).
|
||||||
|
|
||||||
UUIDv4 (random): {{.Exec}} uuid
|
UUIDv4 (random): {{.Exec}} uuid
|
||||||
|
|
||||||
|
@@ -6,9 +6,9 @@ import (
|
|||||||
|
|
||||||
var cmdWG = &base.Command{
|
var cmdWG = &base.Command{
|
||||||
UsageLine: `{{.Exec}} wg [-i "private key (base64.StdEncoding)"]`,
|
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: `
|
Long: `
|
||||||
Generate key pair for wireguard key exchange.
|
Generate key pair for X25519 key exchange (WireGuard).
|
||||||
|
|
||||||
Random: {{.Exec}} wg
|
Random: {{.Exec}} wg
|
||||||
|
|
||||||
|
@@ -6,9 +6,9 @@ import (
|
|||||||
|
|
||||||
var cmdX25519 = &base.Command{
|
var cmdX25519 = &base.Command{
|
||||||
UsageLine: `{{.Exec}} x25519 [-i "private key (base64.RawURLEncoding)"] [--std-encoding]`,
|
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 (VLESS, REALITY)`,
|
||||||
Long: `
|
Long: `
|
||||||
Generate key pair for x25519 key exchange.
|
Generate key pair for X25519 key exchange (VLESS, REALITY).
|
||||||
|
|
||||||
Random: {{.Exec}} x25519
|
Random: {{.Exec}} x25519
|
||||||
|
|
||||||
|
@@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/signal"
|
"github.com/xtls/xray-core/common/signal"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
"github.com/xtls/xray-core/features/stats"
|
"github.com/xtls/xray-core/features/stats"
|
||||||
|
"github.com/xtls/xray-core/proxy/vless/encryption"
|
||||||
"github.com/xtls/xray-core/transport"
|
"github.com/xtls/xray-core/transport"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"github.com/xtls/xray-core/transport/internet/reality"
|
"github.com/xtls/xray-core/transport/internet/reality"
|
||||||
@@ -524,16 +525,31 @@ func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx conte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnwrapRawConn support unwrap stats, tls, utls, reality, proxyproto, uds-wrapper conn and get raw tcp/uds conn from it
|
// UnwrapRawConn support unwrap encryption, stats, tls, utls, reality, proxyproto, uds-wrapper conn and get raw tcp/uds conn from it
|
||||||
func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {
|
func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {
|
||||||
var readCounter, writerCounter stats.Counter
|
var readCounter, writerCounter stats.Counter
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
statConn, ok := conn.(*stat.CounterConnection)
|
isEncryption := false
|
||||||
if ok {
|
if clientConn, ok := conn.(*encryption.ClientConn); ok {
|
||||||
|
conn = clientConn.Conn
|
||||||
|
isEncryption = true
|
||||||
|
}
|
||||||
|
if serverConn, ok := conn.(*encryption.ServerConn); ok {
|
||||||
|
conn = serverConn.Conn
|
||||||
|
isEncryption = true
|
||||||
|
}
|
||||||
|
if xorConn, ok := conn.(*encryption.XorConn); ok {
|
||||||
|
if !xorConn.Divide {
|
||||||
|
return xorConn, nil, nil // full-random xorConn should not be penetrated
|
||||||
|
}
|
||||||
|
conn = xorConn.Conn
|
||||||
|
}
|
||||||
|
if statConn, ok := conn.(*stat.CounterConnection); ok {
|
||||||
conn = statConn.Connection
|
conn = statConn.Connection
|
||||||
readCounter = statConn.ReadCounter
|
readCounter = statConn.ReadCounter
|
||||||
writerCounter = statConn.WriteCounter
|
writerCounter = statConn.WriteCounter
|
||||||
}
|
}
|
||||||
|
if !isEncryption { // avoids double penetration
|
||||||
if xc, ok := conn.(*tls.Conn); ok {
|
if xc, ok := conn.(*tls.Conn); ok {
|
||||||
conn = xc.NetConn()
|
conn = xc.NetConn()
|
||||||
} else if utlsConn, ok := conn.(*tls.UConn); ok {
|
} else if utlsConn, ok := conn.(*tls.UConn); ok {
|
||||||
@@ -543,6 +559,7 @@ func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {
|
|||||||
} else if realityUConn, ok := conn.(*reality.UConn); ok {
|
} else if realityUConn, ok := conn.(*reality.UConn); ok {
|
||||||
conn = realityUConn.NetConn()
|
conn = realityUConn.NetConn()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if pc, ok := conn.(*proxyproto.Conn); ok {
|
if pc, ok := conn.(*proxyproto.Conn); ok {
|
||||||
conn = pc.Raw()
|
conn = pc.Raw()
|
||||||
// 8192 > 4096, there is no need to process pc's bufReader
|
// 8192 > 4096, there is no need to process pc's bufReader
|
||||||
@@ -632,9 +649,20 @@ func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readV(ctx context.Context, reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, readCounter stats.Counter) error {
|
func readV(ctx context.Context, reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, readCounter stats.Counter) error {
|
||||||
errors.LogInfo(ctx, "CopyRawConn readv")
|
errors.LogInfo(ctx, "CopyRawConn (maybe) readv")
|
||||||
if err := buf.Copy(reader, writer, buf.UpdateActivity(timer), buf.AddToStatCounter(readCounter)); err != nil {
|
if err := buf.Copy(reader, writer, buf.UpdateActivity(timer), buf.AddToStatCounter(readCounter)); err != nil {
|
||||||
return errors.New("failed to process response").Base(err)
|
return errors.New("failed to process response").Base(err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsRAWTransport(conn stat.Connection) bool {
|
||||||
|
iConn := conn
|
||||||
|
if statConn, ok := iConn.(*stat.CounterConnection); ok {
|
||||||
|
iConn = statConn.Connection
|
||||||
|
}
|
||||||
|
_, ok1 := iConn.(*proxyproto.Conn)
|
||||||
|
_, ok2 := iConn.(*net.TCPConn)
|
||||||
|
_, ok3 := iConn.(*internet.UnixConnWrapper)
|
||||||
|
return ok1 || ok2 || ok3
|
||||||
|
}
|
||||||
|
@@ -18,6 +18,8 @@ func (a *Account) AsAccount() (protocol.Account, error) {
|
|||||||
ID: protocol.NewID(id),
|
ID: protocol.NewID(id),
|
||||||
Flow: a.Flow, // needs parser here?
|
Flow: a.Flow, // needs parser here?
|
||||||
Encryption: a.Encryption, // needs parser here?
|
Encryption: a.Encryption, // needs parser here?
|
||||||
|
XorMode: a.XorMode,
|
||||||
|
Minutes: a.Minutes,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,8 +29,10 @@ type MemoryAccount struct {
|
|||||||
ID *protocol.ID
|
ID *protocol.ID
|
||||||
// Flow of the account. May be "xtls-rprx-vision".
|
// Flow of the account. May be "xtls-rprx-vision".
|
||||||
Flow string
|
Flow string
|
||||||
// Encryption of the account. Used for client connections, and only accepts "none" for now.
|
|
||||||
Encryption string
|
Encryption string
|
||||||
|
XorMode uint32
|
||||||
|
Minutes uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equals implements protocol.Account.Equals().
|
// Equals implements protocol.Account.Equals().
|
||||||
@@ -45,5 +49,7 @@ func (a *MemoryAccount) ToProto() proto.Message {
|
|||||||
Id: a.ID.String(),
|
Id: a.ID.String(),
|
||||||
Flow: a.Flow,
|
Flow: a.Flow,
|
||||||
Encryption: a.Encryption,
|
Encryption: a.Encryption,
|
||||||
|
XorMode: a.XorMode,
|
||||||
|
Minutes: a.Minutes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -29,8 +29,9 @@ type Account struct {
|
|||||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
// Flow settings. May be "xtls-rprx-vision".
|
// Flow settings. May be "xtls-rprx-vision".
|
||||||
Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"`
|
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"`
|
Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"`
|
||||||
|
XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"`
|
||||||
|
Minutes uint32 `protobuf:"varint,5,opt,name=minutes,proto3" json:"minutes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Account) Reset() {
|
func (x *Account) Reset() {
|
||||||
@@ -84,23 +85,40 @@ func (x *Account) GetEncryption() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Account) GetXorMode() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.XorMode
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
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 protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_proxy_vless_account_proto_rawDesc = []byte{
|
var file_proxy_vless_account_proto_rawDesc = []byte{
|
||||||
0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x63,
|
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,
|
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, 0x81, 0x01,
|
||||||
0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
|
0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x77,
|
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f,
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, 0x0a,
|
0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a,
|
||||||
0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||||
0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x52, 0x0a, 0x14,
|
0x09, 0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a,
|
||||||
0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76,
|
0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07,
|
||||||
0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x69, 0x6e, 0x75, 0x74,
|
||||||
0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
|
0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x6d, 0x69, 0x6e, 0x75, 0x74, 0x65,
|
||||||
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x10,
|
0x73, 0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72,
|
||||||
0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73,
|
0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74,
|
||||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
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 (
|
var (
|
||||||
|
@@ -11,6 +11,8 @@ message Account {
|
|||||||
string id = 1;
|
string id = 1;
|
||||||
// Flow settings. May be "xtls-rprx-vision".
|
// Flow settings. May be "xtls-rprx-vision".
|
||||||
string flow = 2;
|
string flow = 2;
|
||||||
// Encryption settings. Only applies to client side, and only accepts "none" for now.
|
|
||||||
string encryption = 3;
|
string encryption = 3;
|
||||||
|
uint32 xorMode = 4;
|
||||||
|
uint32 minutes = 5;
|
||||||
}
|
}
|
||||||
|
@@ -172,7 +172,7 @@ func DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader) (*A
|
|||||||
}
|
}
|
||||||
|
|
||||||
// XtlsRead filter and read xtls protocol
|
// XtlsRead filter and read xtls protocol
|
||||||
func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error {
|
func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, peerCache *[]byte, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error {
|
||||||
err := func() error {
|
err := func() error {
|
||||||
for {
|
for {
|
||||||
if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy {
|
if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy {
|
||||||
@@ -194,7 +194,12 @@ func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer,
|
|||||||
if !buffer.IsEmpty() {
|
if !buffer.IsEmpty() {
|
||||||
timer.Update()
|
timer.Update()
|
||||||
if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy {
|
if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy {
|
||||||
// XTLS Vision processes struct TLS Conn's input and rawInput
|
// XTLS Vision processes struct Encryption Conn's peerCache or TLS Conn's input and rawInput
|
||||||
|
if peerCache != nil {
|
||||||
|
if len(*peerCache) != 0 {
|
||||||
|
buffer = buf.MergeBytes(buffer, *peerCache)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if inputBuffer, err := buf.ReadFrom(input); err == nil {
|
if inputBuffer, err := buf.ReadFrom(input); err == nil {
|
||||||
if !inputBuffer.IsEmpty() {
|
if !inputBuffer.IsEmpty() {
|
||||||
buffer, _ = buf.MergeMulti(buffer, inputBuffer)
|
buffer, _ = buf.MergeMulti(buffer, inputBuffer)
|
||||||
@@ -206,6 +211,7 @@ func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if werr := writer.WriteMultiBuffer(buffer); werr != nil {
|
if werr := writer.WriteMultiBuffer(buffer); werr != nil {
|
||||||
return werr
|
return werr
|
||||||
}
|
}
|
||||||
|
266
proxy/vless/encryption/client.go
Normal file
266
proxy/vless/encryption/client.go
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
package encryption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/ecdh"
|
||||||
|
"crypto/mlkem"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha3"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/crypto"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ClientCipher byte
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if protocol.HasAESGCMHardwareSupport {
|
||||||
|
ClientCipher = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientInstance struct {
|
||||||
|
sync.RWMutex
|
||||||
|
nfsEKey *mlkem.EncapsulationKey768
|
||||||
|
hash11 [11]byte // no more capacity
|
||||||
|
xorMode uint32
|
||||||
|
xorPKey *ecdh.PublicKey
|
||||||
|
minutes time.Duration
|
||||||
|
expire time.Time
|
||||||
|
baseKey []byte
|
||||||
|
ticket []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientConn struct {
|
||||||
|
net.Conn
|
||||||
|
instance *ClientInstance
|
||||||
|
baseKey []byte
|
||||||
|
ticket []byte
|
||||||
|
random []byte
|
||||||
|
aead cipher.AEAD
|
||||||
|
nonce []byte
|
||||||
|
peerAEAD cipher.AEAD
|
||||||
|
peerNonce []byte
|
||||||
|
PeerCache []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ClientInstance) Init(nfsEKeyBytes, xorPKeyBytes []byte, xorMode, minutes uint32) (err error) {
|
||||||
|
if i.nfsEKey != nil {
|
||||||
|
err = errors.New("already initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if xorMode > 0 {
|
||||||
|
i.xorMode = xorMode
|
||||||
|
if i.xorPKey, err = ecdh.X25519().NewPublicKey(xorPKeyBytes); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hash32 := sha3.Sum256(nfsEKeyBytes)
|
||||||
|
copy(i.hash11[:], hash32[:])
|
||||||
|
}
|
||||||
|
i.minutes = time.Duration(minutes) * time.Minute
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ClientInstance) Handshake(conn net.Conn) (*ClientConn, error) {
|
||||||
|
if i.nfsEKey == nil {
|
||||||
|
return nil, errors.New("uninitialized")
|
||||||
|
}
|
||||||
|
if i.xorMode > 0 {
|
||||||
|
conn, _ = NewXorConn(conn, i.xorMode, i.xorPKey, nil)
|
||||||
|
}
|
||||||
|
c := &ClientConn{Conn: conn}
|
||||||
|
|
||||||
|
if i.minutes > 0 {
|
||||||
|
i.RLock()
|
||||||
|
if time.Now().Before(i.expire) {
|
||||||
|
c.instance = i
|
||||||
|
c.baseKey = i.baseKey
|
||||||
|
c.ticket = i.ticket
|
||||||
|
i.RUnlock()
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
i.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
pfsDKeySeed := make([]byte, 64)
|
||||||
|
rand.Read(pfsDKeySeed)
|
||||||
|
pfsDKey, _ := mlkem.NewDecapsulationKey768(pfsDKeySeed)
|
||||||
|
pfsEKeyBytes := pfsDKey.EncapsulationKey().Bytes()
|
||||||
|
nfsKey, encapsulatedNfsKey := i.nfsEKey.Encapsulate()
|
||||||
|
nfsAEAD := NewAEAD(ClientCipher, nfsKey, pfsEKeyBytes, encapsulatedNfsKey)
|
||||||
|
|
||||||
|
clientHello := make([]byte, 5+11+1+1184+1088+crypto.RandBetween(100, 1000))
|
||||||
|
EncodeHeader(clientHello, 1, 11+1+1184+1088)
|
||||||
|
copy(clientHello[5:], i.hash11[:])
|
||||||
|
clientHello[5+11] = ClientCipher
|
||||||
|
copy(clientHello[5+11+1:], pfsEKeyBytes)
|
||||||
|
copy(clientHello[5+11+1+1184:], encapsulatedNfsKey)
|
||||||
|
padding := clientHello[5+11+1+1184+1088:]
|
||||||
|
rand.Read(padding) // important
|
||||||
|
EncodeHeader(padding, 23, len(padding)-5)
|
||||||
|
nfsAEAD.Seal(padding[:5], clientHello[5:5+11+1], padding[5:len(padding)-16], padding[:5])
|
||||||
|
|
||||||
|
if _, err := c.Conn.Write(clientHello); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// client can send more NFS AEAD paddings / messages if needed
|
||||||
|
|
||||||
|
_, t, l, err := ReadAndDiscardPaddings(c.Conn, nil, nil) // allow paddings before server hello
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if t != 1 {
|
||||||
|
return nil, errors.New("unexpected type ", t, ", expect server hello")
|
||||||
|
}
|
||||||
|
peerServerHello := make([]byte, 1088+21)
|
||||||
|
if l != len(peerServerHello) {
|
||||||
|
return nil, errors.New("unexpected length ", l, " for server hello")
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerServerHello); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
encapsulatedPfsKey := peerServerHello[:1088]
|
||||||
|
c.ticket = append(i.hash11[:], peerServerHello[1088:]...)
|
||||||
|
|
||||||
|
pfsKey, err := pfsDKey.Decapsulate(encapsulatedPfsKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.baseKey = append(pfsKey, nfsKey...)
|
||||||
|
|
||||||
|
VLESS, _ := NewAEAD(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, append(i.hash11[:], ClientCipher), c.ticket[11:], pfsEKeyBytes)
|
||||||
|
if !bytes.Equal(VLESS, []byte("VLESS")) {
|
||||||
|
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.ticket = c.ticket
|
||||||
|
i.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) Write(b []byte) (int, error) {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
var data []byte
|
||||||
|
for n := 0; n < len(b); {
|
||||||
|
b := b[n:]
|
||||||
|
if len(b) > 8192 {
|
||||||
|
b = b[:8192] // for avoiding another copy() in server's Read()
|
||||||
|
}
|
||||||
|
n += len(b)
|
||||||
|
if c.aead == nil {
|
||||||
|
data = make([]byte, 5+32+32+5+len(b)+16)
|
||||||
|
EncodeHeader(data, 0, 32+32)
|
||||||
|
copy(data[5:], c.ticket)
|
||||||
|
c.random = make([]byte, 32)
|
||||||
|
rand.Read(c.random)
|
||||||
|
copy(data[5+32:], c.random)
|
||||||
|
EncodeHeader(data[5+32+32:], 23, len(b)+16)
|
||||||
|
c.aead = NewAEAD(ClientCipher, c.baseKey, c.random, c.ticket)
|
||||||
|
c.nonce = make([]byte, 12)
|
||||||
|
c.aead.Seal(data[:5+32+32+5], c.nonce, b, data[5+32+32:5+32+32+5])
|
||||||
|
} else {
|
||||||
|
data = make([]byte, 5+len(b)+16)
|
||||||
|
EncodeHeader(data, 23, len(b)+16)
|
||||||
|
c.aead.Seal(data[:5], c.nonce, b, data[:5])
|
||||||
|
if bytes.Equal(c.nonce, MaxNonce) {
|
||||||
|
c.aead = NewAEAD(ClientCipher, c.baseKey, data[5:], 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) {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if c.peerAEAD == nil {
|
||||||
|
_, t, l, err := ReadAndDiscardPaddings(c.Conn, nil, nil) // allow paddings before random hello
|
||||||
|
if err != nil {
|
||||||
|
if c.instance != nil && strings.HasPrefix(err.Error(), "invalid header: ") { // 0-RTT
|
||||||
|
c.instance.Lock()
|
||||||
|
if bytes.Equal(c.ticket, c.instance.ticket) {
|
||||||
|
c.instance.expire = time.Now() // expired
|
||||||
|
}
|
||||||
|
c.instance.Unlock()
|
||||||
|
return 0, errors.New("new handshake needed")
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if t != 0 {
|
||||||
|
return 0, errors.New("unexpected type ", t, ", expect random hello")
|
||||||
|
}
|
||||||
|
peerRandomHello := make([]byte, 32)
|
||||||
|
if l != len(peerRandomHello) {
|
||||||
|
return 0, errors.New("unexpected length ", l, " for random hello")
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerRandomHello); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if c.random == nil {
|
||||||
|
return 0, errors.New("empty c.random")
|
||||||
|
}
|
||||||
|
c.peerAEAD = NewAEAD(ClientCipher, c.baseKey, peerRandomHello, c.random)
|
||||||
|
c.peerNonce = make([]byte, 12)
|
||||||
|
}
|
||||||
|
if len(c.PeerCache) != 0 {
|
||||||
|
n := copy(b, c.PeerCache)
|
||||||
|
c.PeerCache = c.PeerCache[n:]
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
h, t, l, err := ReadAndDecodeHeader(c.Conn) // l: 17~17000
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if t != 23 {
|
||||||
|
return 0, errors.New("unexpected type ", t, ", expect encrypted data")
|
||||||
|
}
|
||||||
|
peerData := make([]byte, l)
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerData); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
dst := peerData[:l-16]
|
||||||
|
if len(dst) <= len(b) {
|
||||||
|
dst = b[:len(dst)] // avoids another copy()
|
||||||
|
}
|
||||||
|
var peerAEAD cipher.AEAD
|
||||||
|
if bytes.Equal(c.peerNonce, MaxNonce) {
|
||||||
|
peerAEAD = NewAEAD(ClientCipher, c.baseKey, peerData, h)
|
||||||
|
}
|
||||||
|
_, err = c.peerAEAD.Open(dst[:0], c.peerNonce, peerData, h)
|
||||||
|
if peerAEAD != nil {
|
||||||
|
c.peerAEAD = peerAEAD
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
100
proxy/vless/encryption/common.go
Normal file
100
proxy/vless/encryption/common.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package encryption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/hkdf"
|
||||||
|
"crypto/sha3"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
|
)
|
||||||
|
|
||||||
|
var MaxNonce = bytes.Repeat([]byte{255}, 12)
|
||||||
|
|
||||||
|
func EncodeHeader(h []byte, t byte, l int) {
|
||||||
|
switch t {
|
||||||
|
case 1:
|
||||||
|
h[0] = 1
|
||||||
|
h[1] = 1
|
||||||
|
h[2] = 1
|
||||||
|
case 0:
|
||||||
|
h[0] = 0
|
||||||
|
h[1] = 0
|
||||||
|
h[2] = 0
|
||||||
|
case 23:
|
||||||
|
h[0] = 23
|
||||||
|
h[1] = 3
|
||||||
|
h[2] = 3
|
||||||
|
}
|
||||||
|
h[3] = byte(l >> 8)
|
||||||
|
h[4] = byte(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeHeader(h []byte) (t byte, l int, err error) {
|
||||||
|
l = int(h[3])<<8 | int(h[4])
|
||||||
|
if h[0] == 23 && h[1] == 3 && h[2] == 3 {
|
||||||
|
t = 23
|
||||||
|
} else if h[0] == 0 && h[1] == 0 && h[2] == 0 {
|
||||||
|
t = 0
|
||||||
|
} else if h[0] == 1 && h[1] == 1 && h[2] == 1 {
|
||||||
|
t = 1
|
||||||
|
} else {
|
||||||
|
l = 0
|
||||||
|
}
|
||||||
|
if l < 17 || l > 17000 { // TODO: TLSv1.3 max length
|
||||||
|
err = errors.New("invalid header: ", fmt.Sprintf("%v", h[:5])) // DO NOT CHANGE: relied by client's Read()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadAndDecodeHeader(conn net.Conn) (h []byte, t byte, l int, err error) {
|
||||||
|
h = make([]byte, 5)
|
||||||
|
if _, err = io.ReadFull(conn, h); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t, l, err = DecodeHeader(h)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadAndDiscardPaddings(conn net.Conn, aead cipher.AEAD, nonce []byte) (h []byte, t byte, l int, err error) {
|
||||||
|
for {
|
||||||
|
if h, t, l, err = ReadAndDecodeHeader(conn); err != nil || t != 23 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
padding := make([]byte, l)
|
||||||
|
if _, err = io.ReadFull(conn, padding); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if aead != nil {
|
||||||
|
if _, err := aead.Open(nil, nonce, padding, h); err != nil {
|
||||||
|
return h, t, l, err
|
||||||
|
}
|
||||||
|
IncreaseNonce(nonce)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAEAD(c byte, secret, salt, info []byte) (aead cipher.AEAD) {
|
||||||
|
key, _ := hkdf.Key(sha3.New256, secret, salt, string(info), 32)
|
||||||
|
if c&1 == 1 {
|
||||||
|
block, _ := aes.NewCipher(key)
|
||||||
|
aead, _ = cipher.NewGCM(block)
|
||||||
|
} else {
|
||||||
|
aead, _ = chacha20poly1305.New(key)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func IncreaseNonce(nonce []byte) {
|
||||||
|
for i := range 12 {
|
||||||
|
nonce[11-i]++
|
||||||
|
if nonce[11-i] != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
311
proxy/vless/encryption/server.go
Normal file
311
proxy/vless/encryption/server.go
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
package encryption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/ecdh"
|
||||||
|
"crypto/mlkem"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha3"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/crypto"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerSession struct {
|
||||||
|
expire time.Time
|
||||||
|
cipher byte
|
||||||
|
baseKey []byte
|
||||||
|
randoms sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerInstance struct {
|
||||||
|
sync.RWMutex
|
||||||
|
nfsDKey *mlkem.DecapsulationKey768
|
||||||
|
hash11 [11]byte // no more capacity
|
||||||
|
xorMode uint32
|
||||||
|
xorSKey *ecdh.PrivateKey
|
||||||
|
minutes time.Duration
|
||||||
|
sessions map[[32]byte]*ServerSession
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerConn struct {
|
||||||
|
net.Conn
|
||||||
|
cipher byte
|
||||||
|
baseKey []byte
|
||||||
|
ticket []byte
|
||||||
|
peerRandom []byte
|
||||||
|
peerAEAD cipher.AEAD
|
||||||
|
peerNonce []byte
|
||||||
|
PeerCache []byte
|
||||||
|
aead cipher.AEAD
|
||||||
|
nonce []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ServerInstance) Init(nfsDKeySeed, xorSKeyBytes []byte, xorMode, minutes uint32) (err error) {
|
||||||
|
if i.nfsDKey != nil {
|
||||||
|
err = errors.New("already initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if i.nfsDKey, err = mlkem.NewDecapsulationKey768(nfsDKeySeed); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if xorMode > 0 {
|
||||||
|
i.xorMode = xorMode
|
||||||
|
if i.xorSKey, err = ecdh.X25519().NewPrivateKey(xorSKeyBytes); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hash32 := sha3.Sum256(i.nfsDKey.EncapsulationKey().Bytes())
|
||||||
|
copy(i.hash11[:], hash32[:])
|
||||||
|
}
|
||||||
|
if minutes > 0 {
|
||||||
|
i.minutes = time.Duration(minutes) * time.Minute
|
||||||
|
i.sessions = make(map[[32]byte]*ServerSession)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Minute)
|
||||||
|
i.Lock()
|
||||||
|
if i.closed {
|
||||||
|
i.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
for ticket, session := range i.sessions {
|
||||||
|
if now.After(session.expire) {
|
||||||
|
delete(i.sessions, ticket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ServerInstance) Close() (err error) {
|
||||||
|
i.Lock()
|
||||||
|
i.closed = true
|
||||||
|
i.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ServerInstance) Handshake(conn net.Conn) (*ServerConn, error) {
|
||||||
|
if i.nfsDKey == nil {
|
||||||
|
return nil, errors.New("uninitialized")
|
||||||
|
}
|
||||||
|
if i.xorMode > 0 {
|
||||||
|
var err error
|
||||||
|
if conn, err = NewXorConn(conn, i.xorMode, nil, i.xorSKey); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c := &ServerConn{Conn: conn}
|
||||||
|
|
||||||
|
_, t, l, err := ReadAndDiscardPaddings(c.Conn, nil, nil) // allow paddings before client/ticket hello
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if t == 0 {
|
||||||
|
if i.minutes == 0 {
|
||||||
|
return nil, errors.New("0-RTT is not allowed")
|
||||||
|
}
|
||||||
|
peerTicketHello := make([]byte, 32+32)
|
||||||
|
if l != len(peerTicketHello) {
|
||||||
|
return nil, errors.New("unexpected length ", l, " for ticket hello")
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerTicketHello); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !bytes.Equal(peerTicketHello[:11], i.hash11[:]) {
|
||||||
|
return nil, errors.New("unexpected hash11: ", fmt.Sprintf("%v", peerTicketHello[:11]))
|
||||||
|
}
|
||||||
|
i.RLock()
|
||||||
|
s := i.sessions[[32]byte(peerTicketHello)]
|
||||||
|
i.RUnlock()
|
||||||
|
if s == nil {
|
||||||
|
noises := make([]byte, crypto.RandBetween(100, 1000))
|
||||||
|
var err error
|
||||||
|
for err == nil {
|
||||||
|
rand.Read(noises)
|
||||||
|
_, _, err = DecodeHeader(noises)
|
||||||
|
}
|
||||||
|
c.Conn.Write(noises) // make client do new handshake
|
||||||
|
return nil, errors.New("expired ticket")
|
||||||
|
}
|
||||||
|
if _, replay := s.randoms.LoadOrStore([32]byte(peerTicketHello[32:]), true); replay {
|
||||||
|
return nil, errors.New("replay detected")
|
||||||
|
}
|
||||||
|
c.cipher = s.cipher
|
||||||
|
c.baseKey = s.baseKey
|
||||||
|
c.ticket = peerTicketHello[:32]
|
||||||
|
c.peerRandom = peerTicketHello[32:]
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
peerClientHello := make([]byte, 11+1+1184+1088)
|
||||||
|
if l != len(peerClientHello) {
|
||||||
|
return nil, errors.New("unexpected length ", l, " for client hello")
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerClientHello); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !bytes.Equal(peerClientHello[:11], i.hash11[:]) {
|
||||||
|
return nil, errors.New("unexpected hash11: ", fmt.Sprintf("%v", peerClientHello[:11]))
|
||||||
|
}
|
||||||
|
c.cipher = peerClientHello[11]
|
||||||
|
pfsEKeyBytes := peerClientHello[11+1 : 11+1+1184]
|
||||||
|
encapsulatedNfsKey := peerClientHello[11+1+1184:]
|
||||||
|
|
||||||
|
pfsEKey, err := mlkem.NewEncapsulationKey768(pfsEKeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nfsKey, err := i.nfsDKey.Decapsulate(encapsulatedNfsKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nfsAEAD := NewAEAD(c.cipher, nfsKey, pfsEKeyBytes, encapsulatedNfsKey)
|
||||||
|
nfsNonce := append([]byte{}, peerClientHello[:11+1]...)
|
||||||
|
pfsKey, encapsulatedPfsKey := pfsEKey.Encapsulate()
|
||||||
|
c.baseKey = append(pfsKey, nfsKey...)
|
||||||
|
pfsAEAD := NewAEAD(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey)
|
||||||
|
pfsNonce := append([]byte{}, peerClientHello[:11+1]...)
|
||||||
|
c.ticket = append(i.hash11[:], pfsAEAD.Seal(nil, pfsNonce, []byte("VLESS"), pfsEKeyBytes)...)
|
||||||
|
IncreaseNonce(pfsNonce)
|
||||||
|
|
||||||
|
serverHello := make([]byte, 5+1088+21+crypto.RandBetween(100, 1000))
|
||||||
|
EncodeHeader(serverHello, 1, 1088+21)
|
||||||
|
copy(serverHello[5:], encapsulatedPfsKey)
|
||||||
|
copy(serverHello[5+1088:], c.ticket[11:])
|
||||||
|
padding := serverHello[5+1088+21:]
|
||||||
|
rand.Read(padding) // important
|
||||||
|
EncodeHeader(padding, 23, len(padding)-5)
|
||||||
|
pfsAEAD.Seal(padding[:5], pfsNonce, padding[5:len(padding)-16], padding[:5])
|
||||||
|
|
||||||
|
if _, err := c.Conn.Write(serverHello); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// server can send more PFS AEAD paddings / messages if needed
|
||||||
|
|
||||||
|
_, t, l, err = ReadAndDiscardPaddings(c.Conn, nfsAEAD, nfsNonce) // allow paddings before ticket hello
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if t != 0 {
|
||||||
|
return nil, errors.New("unexpected type ", t, ", expect ticket hello")
|
||||||
|
}
|
||||||
|
peerTicketHello := make([]byte, 32+32)
|
||||||
|
if l != len(peerTicketHello) {
|
||||||
|
return nil, errors.New("unexpected length ", l, " for ticket hello")
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerTicketHello); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !bytes.Equal(peerTicketHello[:32], c.ticket) {
|
||||||
|
return nil, errors.New("naughty boy")
|
||||||
|
}
|
||||||
|
c.peerRandom = peerTicketHello[32:]
|
||||||
|
|
||||||
|
if i.minutes > 0 {
|
||||||
|
i.Lock()
|
||||||
|
s := &ServerSession{
|
||||||
|
expire: time.Now().Add(i.minutes),
|
||||||
|
cipher: c.cipher,
|
||||||
|
baseKey: c.baseKey,
|
||||||
|
}
|
||||||
|
s.randoms.Store([32]byte(c.peerRandom), true)
|
||||||
|
i.sessions[[32]byte(c.ticket)] = s
|
||||||
|
i.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerConn) Read(b []byte) (int, error) {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if c.peerAEAD == nil {
|
||||||
|
c.peerAEAD = NewAEAD(c.cipher, c.baseKey, c.peerRandom, c.ticket)
|
||||||
|
c.peerNonce = make([]byte, 12)
|
||||||
|
}
|
||||||
|
if len(c.PeerCache) != 0 {
|
||||||
|
n := copy(b, c.PeerCache)
|
||||||
|
c.PeerCache = c.PeerCache[n:]
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
h, t, l, err := ReadAndDecodeHeader(c.Conn) // l: 17~17000
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if t != 23 {
|
||||||
|
return 0, errors.New("unexpected type ", t, ", expect encrypted data")
|
||||||
|
}
|
||||||
|
peerData := make([]byte, l)
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerData); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
dst := peerData[:l-16]
|
||||||
|
if len(dst) <= len(b) {
|
||||||
|
dst = b[:len(dst)] // avoids another copy()
|
||||||
|
}
|
||||||
|
var peerAEAD cipher.AEAD
|
||||||
|
if bytes.Equal(c.peerNonce, MaxNonce) {
|
||||||
|
peerAEAD = NewAEAD(c.cipher, c.baseKey, peerData, h)
|
||||||
|
}
|
||||||
|
_, err = c.peerAEAD.Open(dst[:0], c.peerNonce, peerData, h)
|
||||||
|
if peerAEAD != nil {
|
||||||
|
c.peerAEAD = peerAEAD
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerConn) Write(b []byte) (int, error) {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
var data []byte
|
||||||
|
for n := 0; n < len(b); {
|
||||||
|
b := b[n:]
|
||||||
|
if len(b) > 8192 {
|
||||||
|
b = b[:8192] // for avoiding another copy() in client's Read()
|
||||||
|
}
|
||||||
|
n += len(b)
|
||||||
|
if c.aead == nil {
|
||||||
|
data = make([]byte, 5+32+5+len(b)+16)
|
||||||
|
EncodeHeader(data, 0, 32)
|
||||||
|
rand.Read(data[5 : 5+32])
|
||||||
|
EncodeHeader(data[5+32:], 23, len(b)+16)
|
||||||
|
c.aead = NewAEAD(c.cipher, c.baseKey, data[5:5+32], c.peerRandom)
|
||||||
|
c.nonce = make([]byte, 12)
|
||||||
|
c.aead.Seal(data[:5+32+5], c.nonce, b, data[5+32:5+32+5])
|
||||||
|
} else {
|
||||||
|
data = make([]byte, 5+len(b)+16)
|
||||||
|
EncodeHeader(data, 23, len(b)+16)
|
||||||
|
c.aead.Seal(data[:5], c.nonce, b, data[:5])
|
||||||
|
if bytes.Equal(c.nonce, MaxNonce) {
|
||||||
|
c.aead = NewAEAD(c.cipher, c.baseKey, data[5:], data[:5])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IncreaseNonce(c.nonce)
|
||||||
|
if _, err := c.Conn.Write(data); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
200
proxy/vless/encryption/xor.go
Normal file
200
proxy/vless/encryption/xor.go
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
package encryption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/ecdh"
|
||||||
|
"crypto/hkdf"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha3"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type XorConn struct {
|
||||||
|
net.Conn
|
||||||
|
Divide bool
|
||||||
|
|
||||||
|
head []byte
|
||||||
|
key []byte
|
||||||
|
ctr cipher.Stream
|
||||||
|
peerCtr cipher.Stream
|
||||||
|
isHeader bool
|
||||||
|
skipNext bool
|
||||||
|
|
||||||
|
out_after0 bool
|
||||||
|
out_header []byte
|
||||||
|
out_skip int
|
||||||
|
|
||||||
|
in_after0 bool
|
||||||
|
in_header []byte
|
||||||
|
in_skip int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCTR(key, iv []byte, isServer bool) cipher.Stream {
|
||||||
|
info := "CLIENT"
|
||||||
|
if isServer {
|
||||||
|
info = "SERVER" // avoids attackers sending traffic back to the client, though the encryption layer has its own protection
|
||||||
|
}
|
||||||
|
key, _ = hkdf.Key(sha3.New256, key, iv, info, 32) // avoids using pKey directly if attackers sent the basepoint, or whaterver they like
|
||||||
|
block, _ := aes.NewCipher(key)
|
||||||
|
return cipher.NewCTR(block, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewXorConn(conn net.Conn, mode uint32, pKey *ecdh.PublicKey, sKey *ecdh.PrivateKey) (*XorConn, error) {
|
||||||
|
if mode == 0 || (pKey == nil && sKey == nil) || (pKey != nil && sKey != nil) {
|
||||||
|
return nil, errors.New("invalid parameters")
|
||||||
|
}
|
||||||
|
c := &XorConn{
|
||||||
|
Conn: conn,
|
||||||
|
Divide: mode == 1,
|
||||||
|
isHeader: true,
|
||||||
|
out_header: make([]byte, 0, 5), // important
|
||||||
|
in_header: make([]byte, 0, 5), // important
|
||||||
|
}
|
||||||
|
if pKey != nil {
|
||||||
|
c.head = make([]byte, 16+32)
|
||||||
|
rand.Read(c.head)
|
||||||
|
eSKey, _ := ecdh.X25519().NewPrivateKey(c.head[16:])
|
||||||
|
NewCTR(pKey.Bytes(), c.head[:16], false).XORKeyStream(c.head[16:], eSKey.PublicKey().Bytes()) // make X25519 public key distinguishable from random bytes
|
||||||
|
c.key, _ = eSKey.ECDH(pKey)
|
||||||
|
c.ctr = NewCTR(c.key, c.head[:16], false)
|
||||||
|
}
|
||||||
|
if sKey != nil {
|
||||||
|
peerHead := make([]byte, 16+32)
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerHead); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
NewCTR(sKey.PublicKey().Bytes(), peerHead[:16], false).XORKeyStream(peerHead[16:], peerHead[16:]) // we don't use buggy elligator, because we have PSK :)
|
||||||
|
ePKey, err := ecdh.X25519().NewPublicKey(peerHead[16:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
key, err := sKey.ECDH(ePKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.peerCtr = NewCTR(key, peerHead[:16], false)
|
||||||
|
c.head = make([]byte, 16)
|
||||||
|
rand.Read(c.head) // make sure the server always replies random bytes even when received replays, though it is not important
|
||||||
|
c.ctr = NewCTR(key, c.head, true) // the same key links the upload & download, though the encryption layer has its own link
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
//chacha20.NewUnauthenticatedCipher()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *XorConn) Write(b []byte) (int, error) { // whole one/two records
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if !c.out_after0 {
|
||||||
|
t, l, _ := DecodeHeader(b)
|
||||||
|
if t == 23 { // single 23
|
||||||
|
l = 5
|
||||||
|
} else { // 1/0 + 23, or noises only
|
||||||
|
l += 10
|
||||||
|
if t == 0 {
|
||||||
|
c.out_after0 = true
|
||||||
|
if c.Divide {
|
||||||
|
l -= 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.ctr.XORKeyStream(b[:l], b[:l]) // caller MUST discard b
|
||||||
|
l = len(b)
|
||||||
|
if c.head != nil {
|
||||||
|
b = append(c.head, b...)
|
||||||
|
c.head = nil
|
||||||
|
}
|
||||||
|
if _, err := c.Conn.Write(b); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
if c.Divide {
|
||||||
|
return c.Conn.Write(b)
|
||||||
|
}
|
||||||
|
for p := b; ; { // for XTLS
|
||||||
|
if len(p) <= c.out_skip {
|
||||||
|
c.out_skip -= len(p)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
p = p[c.out_skip:]
|
||||||
|
c.out_skip = 0
|
||||||
|
need := 5 - len(c.out_header)
|
||||||
|
if len(p) < need {
|
||||||
|
c.out_header = append(c.out_header, p...)
|
||||||
|
c.ctr.XORKeyStream(p, p)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, c.out_skip, _ = DecodeHeader(append(c.out_header, p[:need]...))
|
||||||
|
c.out_header = c.out_header[:0]
|
||||||
|
c.ctr.XORKeyStream(p[:need], p[:need])
|
||||||
|
p = p[need:]
|
||||||
|
}
|
||||||
|
if _, err := c.Conn.Write(b); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes...
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if !c.in_after0 || !c.isHeader {
|
||||||
|
if c.peerCtr == nil { // for client
|
||||||
|
peerIv := make([]byte, 16)
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerIv); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
c.peerCtr = NewCTR(c.key, peerIv, true)
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(c.Conn, b); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if c.skipNext {
|
||||||
|
c.skipNext = false
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
c.peerCtr.XORKeyStream(b, b)
|
||||||
|
if c.isHeader { // always 5-bytes
|
||||||
|
if t, _, _ := DecodeHeader(b); t == 23 {
|
||||||
|
c.skipNext = true
|
||||||
|
} else {
|
||||||
|
c.isHeader = false
|
||||||
|
if t == 0 {
|
||||||
|
c.in_after0 = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.isHeader = true
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
if c.Divide {
|
||||||
|
return c.Conn.Read(b)
|
||||||
|
}
|
||||||
|
n, err := c.Conn.Read(b)
|
||||||
|
for p := b[:n]; ; { // for XTLS
|
||||||
|
if len(p) <= c.in_skip {
|
||||||
|
c.in_skip -= len(p)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
p = p[c.in_skip:]
|
||||||
|
c.in_skip = 0
|
||||||
|
need := 5 - len(c.in_header)
|
||||||
|
if len(p) < need {
|
||||||
|
c.peerCtr.XORKeyStream(p, p)
|
||||||
|
c.in_header = append(c.in_header, p...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
c.peerCtr.XORKeyStream(p[:need], p[:need])
|
||||||
|
_, c.in_skip, _ = DecodeHeader(append(c.in_header, p[:need]...))
|
||||||
|
c.in_header = c.in_header[:0]
|
||||||
|
p = p[need:]
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
@@ -112,10 +112,10 @@ type Config struct {
|
|||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"`
|
Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"`
|
||||||
// Decryption settings. Only applies to server side, and only accepts "none"
|
Fallbacks []*Fallback `protobuf:"bytes,2,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"`
|
||||||
// for now.
|
Decryption string `protobuf:"bytes,3,opt,name=decryption,proto3" json:"decryption,omitempty"`
|
||||||
Decryption string `protobuf:"bytes,2,opt,name=decryption,proto3" json:"decryption,omitempty"`
|
XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"`
|
||||||
Fallbacks []*Fallback `protobuf:"bytes,3,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"`
|
Minutes uint32 `protobuf:"varint,5,opt,name=minutes,proto3" json:"minutes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) Reset() {
|
func (x *Config) Reset() {
|
||||||
@@ -155,6 +155,13 @@ func (x *Config) GetClients() []*protocol.User {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetFallbacks() []*Fallback {
|
||||||
|
if x != nil {
|
||||||
|
return x.Fallbacks
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (x *Config) GetDecryption() string {
|
func (x *Config) GetDecryption() string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Decryption
|
return x.Decryption
|
||||||
@@ -162,11 +169,18 @@ func (x *Config) GetDecryption() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetFallbacks() []*Fallback {
|
func (x *Config) GetXorMode() uint32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Fallbacks
|
return x.XorMode
|
||||||
}
|
}
|
||||||
return nil
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetMinutes() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Minutes
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var File_proxy_vless_inbound_config_proto protoreflect.FileDescriptor
|
var File_proxy_vless_inbound_config_proto protoreflect.FileDescriptor
|
||||||
@@ -185,25 +199,28 @@ 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,
|
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,
|
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,
|
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, 0xd4, 0x01,
|
||||||
0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65,
|
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,
|
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, 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,
|
0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x40,
|
||||||
0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
|
0x0a, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
|
||||||
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,
|
|
||||||
0x0b, 0x32, 0x22, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76,
|
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, 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,
|
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,
|
0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03,
|
||||||
0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e,
|
||||||
0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78,
|
0x12, 0x18, 0x0a, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||||
0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72,
|
0x0d, 0x52, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x69,
|
||||||
0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e,
|
0x6e, 0x75, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x6d, 0x69, 0x6e,
|
||||||
0x64, 0xaa, 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56,
|
0x75, 0x74, 0x65, 0x73, 0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79,
|
||||||
0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72,
|
0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62,
|
||||||
0x6f, 0x74, 0x6f, 0x33,
|
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 (
|
var (
|
||||||
|
@@ -19,8 +19,9 @@ message Fallback {
|
|||||||
|
|
||||||
message Config {
|
message Config {
|
||||||
repeated xray.common.protocol.User clients = 1;
|
repeated xray.common.protocol.User clients = 1;
|
||||||
// Decryption settings. Only applies to server side, and only accepts "none"
|
repeated Fallback fallbacks = 2;
|
||||||
// for now.
|
|
||||||
string decryption = 2;
|
string decryption = 3;
|
||||||
repeated Fallback fallbacks = 3;
|
uint32 xorMode = 4;
|
||||||
|
uint32 minutes = 5;
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
gotls "crypto/tls"
|
gotls "crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -29,6 +30,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/proxy"
|
"github.com/xtls/xray-core/proxy"
|
||||||
"github.com/xtls/xray-core/proxy/vless"
|
"github.com/xtls/xray-core/proxy/vless"
|
||||||
"github.com/xtls/xray-core/proxy/vless/encoding"
|
"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/reality"
|
||||||
"github.com/xtls/xray-core/transport/internet/stat"
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
@@ -67,6 +69,7 @@ type Handler struct {
|
|||||||
policyManager policy.Manager
|
policyManager policy.Manager
|
||||||
validator vless.Validator
|
validator vless.Validator
|
||||||
dns dns.Client
|
dns dns.Client
|
||||||
|
decryption *encryption.ServerInstance
|
||||||
fallbacks map[string]map[string]map[string]*Fallback // or nil
|
fallbacks map[string]map[string]map[string]*Fallback // or nil
|
||||||
// regexps map[string]*regexp.Regexp // or nil
|
// regexps map[string]*regexp.Regexp // or nil
|
||||||
}
|
}
|
||||||
@@ -81,6 +84,15 @@ func New(ctx context.Context, config *Config, dc dns.Client, validator vless.Val
|
|||||||
validator: validator,
|
validator: validator,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s := strings.Split(config.Decryption, "."); len(s) == 2 {
|
||||||
|
nfsDKeySeed, _ := base64.RawURLEncoding.DecodeString(s[0])
|
||||||
|
xorSKeyBytes, _ := base64.RawURLEncoding.DecodeString(s[1])
|
||||||
|
handler.decryption = &encryption.ServerInstance{}
|
||||||
|
if err := handler.decryption.Init(nfsDKeySeed, xorSKeyBytes, config.XorMode, config.Minutes); err != nil {
|
||||||
|
return nil, errors.New("failed to use mlkem768seed").Base(err).AtError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if config.Fallbacks != nil {
|
if config.Fallbacks != nil {
|
||||||
handler.fallbacks = make(map[string]map[string]map[string]*Fallback)
|
handler.fallbacks = make(map[string]map[string]map[string]*Fallback)
|
||||||
// handler.regexps = make(map[string]*regexp.Regexp)
|
// handler.regexps = make(map[string]*regexp.Regexp)
|
||||||
@@ -159,6 +171,9 @@ func isMuxAndNotXUDP(request *protocol.RequestHeader, first *buf.Buffer) bool {
|
|||||||
|
|
||||||
// Close implements common.Closable.Close().
|
// Close implements common.Closable.Close().
|
||||||
func (h *Handler) Close() error {
|
func (h *Handler) Close() error {
|
||||||
|
if h.decryption != nil {
|
||||||
|
h.decryption.Close()
|
||||||
|
}
|
||||||
return errors.Combine(common.Close(h.validator))
|
return errors.Combine(common.Close(h.validator))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,6 +214,14 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
|||||||
iConn = statConn.Connection
|
iConn = statConn.Connection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sessionPolicy := h.policyManager.ForLevel(0)
|
sessionPolicy := h.policyManager.ForLevel(0)
|
||||||
if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {
|
if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {
|
||||||
return errors.New("unable to set read deadline").Base(err).AtWarning()
|
return errors.New("unable to set read deadline").Base(err).AtWarning()
|
||||||
@@ -462,6 +485,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
|||||||
// Flow: requestAddons.Flow,
|
// Flow: requestAddons.Flow,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var peerCache *[]byte
|
||||||
var input *bytes.Reader
|
var input *bytes.Reader
|
||||||
var rawInput *bytes.Buffer
|
var rawInput *bytes.Buffer
|
||||||
switch requestAddons.Flow {
|
switch requestAddons.Flow {
|
||||||
@@ -474,6 +498,13 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
|||||||
case protocol.RequestCommandMux:
|
case protocol.RequestCommandMux:
|
||||||
fallthrough // we will break Mux connections that contain TCP requests
|
fallthrough // we will break Mux connections that contain TCP requests
|
||||||
case protocol.RequestCommandTCP:
|
case protocol.RequestCommandTCP:
|
||||||
|
if serverConn, ok := connection.(*encryption.ServerConn); ok {
|
||||||
|
peerCache = &serverConn.PeerCache
|
||||||
|
if xorConn, ok := serverConn.Conn.(*encryption.XorConn); (ok && !xorConn.Divide) || !proxy.IsRAWTransport(iConn) {
|
||||||
|
inbound.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
var t reflect.Type
|
var t reflect.Type
|
||||||
var p uintptr
|
var p uintptr
|
||||||
if tlsConn, ok := iConn.(*tls.Conn); ok {
|
if tlsConn, ok := iConn.(*tls.Conn); ok {
|
||||||
@@ -542,7 +573,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
|||||||
if requestAddons.Flow == vless.XRV {
|
if requestAddons.Flow == vless.XRV {
|
||||||
ctx1 := session.ContextWithInbound(ctx, nil) // TODO enable splice
|
ctx1 := session.ContextWithInbound(ctx, nil) // TODO enable splice
|
||||||
clientReader = proxy.NewVisionReader(clientReader, trafficState, true, ctx1)
|
clientReader = proxy.NewVisionReader(clientReader, trafficState, true, ctx1)
|
||||||
err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, input, rawInput, trafficState, nil, true, ctx1)
|
err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, peerCache, input, rawInput, trafficState, nil, true, ctx1)
|
||||||
} else {
|
} else {
|
||||||
// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBuffer
|
// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBuffer
|
||||||
err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))
|
err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))
|
||||||
|
@@ -4,7 +4,9 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
gotls "crypto/tls"
|
gotls "crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@@ -24,6 +26,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/proxy"
|
"github.com/xtls/xray-core/proxy"
|
||||||
"github.com/xtls/xray-core/proxy/vless"
|
"github.com/xtls/xray-core/proxy/vless"
|
||||||
"github.com/xtls/xray-core/proxy/vless/encoding"
|
"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"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"github.com/xtls/xray-core/transport/internet/reality"
|
"github.com/xtls/xray-core/transport/internet/reality"
|
||||||
@@ -43,6 +46,7 @@ type Handler struct {
|
|||||||
serverPicker protocol.ServerPicker
|
serverPicker protocol.ServerPicker
|
||||||
policyManager policy.Manager
|
policyManager policy.Manager
|
||||||
cone bool
|
cone bool
|
||||||
|
encryption *encryption.ClientInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new VLess outbound handler.
|
// New creates a new VLess outbound handler.
|
||||||
@@ -64,6 +68,16 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
|
|||||||
cone: ctx.Value("cone").(bool),
|
cone: ctx.Value("cone").(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a := handler.serverPicker.PickServer().PickUser().Account.(*vless.MemoryAccount)
|
||||||
|
if s := strings.Split(a.Encryption, "."); len(s) == 2 {
|
||||||
|
nfsEKeyBytes, _ := base64.RawURLEncoding.DecodeString(s[0])
|
||||||
|
xorPKeyBytes, _ := base64.RawURLEncoding.DecodeString(s[1])
|
||||||
|
handler.encryption = &encryption.ClientInstance{}
|
||||||
|
if err := handler.encryption.Init(nfsEKeyBytes, xorPKeyBytes, a.XorMode, a.Minutes); err != nil {
|
||||||
|
return nil, errors.New("failed to use mlkem768client").Base(err).AtError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return handler, nil
|
return handler, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +112,14 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
target := ob.Target
|
target := ob.Target
|
||||||
errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination().NetAddr())
|
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
|
command := protocol.RequestCommandTCP
|
||||||
if target.Network == net.Network_UDP {
|
if target.Network == net.Network_UDP {
|
||||||
command = protocol.RequestCommandUDP
|
command = protocol.RequestCommandUDP
|
||||||
@@ -120,6 +142,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
Flow: account.Flow,
|
Flow: account.Flow,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var peerCache *[]byte
|
||||||
var input *bytes.Reader
|
var input *bytes.Reader
|
||||||
var rawInput *bytes.Buffer
|
var rawInput *bytes.Buffer
|
||||||
allowUDP443 := false
|
allowUDP443 := false
|
||||||
@@ -138,6 +161,13 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
case protocol.RequestCommandMux:
|
case protocol.RequestCommandMux:
|
||||||
fallthrough // let server break Mux connections that contain TCP requests
|
fallthrough // let server break Mux connections that contain TCP requests
|
||||||
case protocol.RequestCommandTCP:
|
case protocol.RequestCommandTCP:
|
||||||
|
if clientConn, ok := conn.(*encryption.ClientConn); ok {
|
||||||
|
peerCache = &clientConn.PeerCache
|
||||||
|
if xorConn, ok := clientConn.Conn.(*encryption.XorConn); (ok && !xorConn.Divide) || !proxy.IsRAWTransport(iConn) {
|
||||||
|
ob.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
var t reflect.Type
|
var t reflect.Type
|
||||||
var p uintptr
|
var p uintptr
|
||||||
if tlsConn, ok := iConn.(*tls.Conn); ok {
|
if tlsConn, ok := iConn.(*tls.Conn); ok {
|
||||||
@@ -272,7 +302,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if requestAddons.Flow == vless.XRV {
|
if requestAddons.Flow == vless.XRV {
|
||||||
err = encoding.XtlsRead(serverReader, clientWriter, timer, conn, input, rawInput, trafficState, ob, false, ctx)
|
err = encoding.XtlsRead(serverReader, clientWriter, timer, conn, peerCache, input, rawInput, trafficState, ob, false, ctx)
|
||||||
} else {
|
} else {
|
||||||
// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBuffer
|
// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBuffer
|
||||||
err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer))
|
err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer))
|
||||||
|
@@ -489,17 +489,15 @@ func (w uploadWriter) Write(b []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
buffer := buf.MultiBufferContainer{}
|
buffer := buf.New()
|
||||||
n, err := buffer.Write(b)
|
n, err := buffer.Write(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, buff := range buffer.MultiBuffer {
|
err = w.WriteMultiBuffer([]*buf.Buffer{buffer})
|
||||||
err := w.WriteMultiBuffer(buf.MultiBuffer{buff})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user