mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-12-19 16:30:07 +08:00
feat: support optional aes128xor layer for vless encryption
This commit is contained in:
parent
7392529677
commit
9b90719ddd
@ -456,7 +456,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
option: &option,
|
option: &option,
|
||||||
}
|
}
|
||||||
|
|
||||||
if s := strings.Split(option.Encryption, "-mlkem768client-"); len(s) == 2 {
|
if s := strings.SplitN(option.Encryption, "-", 4); len(s) == 4 && s[2] == "mlkem768client" {
|
||||||
var minutes uint32
|
var minutes uint32
|
||||||
if s[0] != "1rtt" {
|
if s[0] != "1rtt" {
|
||||||
t := strings.TrimSuffix(s[0], "min")
|
t := strings.TrimSuffix(s[0], "min")
|
||||||
@ -470,14 +470,22 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
}
|
}
|
||||||
minutes = uint32(i)
|
minutes = uint32(i)
|
||||||
}
|
}
|
||||||
|
var xor uint32
|
||||||
|
switch s[1] {
|
||||||
|
case "vless":
|
||||||
|
case "aes128xor":
|
||||||
|
xor = 1
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
|
||||||
|
}
|
||||||
var b []byte
|
var b []byte
|
||||||
b, err = base64.RawURLEncoding.DecodeString(s[1])
|
b, err = base64.RawURLEncoding.DecodeString(s[3])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
|
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
|
||||||
}
|
}
|
||||||
if len(b) == encryption.MLKEM768ClientLength {
|
if len(b) == encryption.MLKEM768ClientLength {
|
||||||
v.encryption = &encryption.ClientInstance{}
|
v.encryption = &encryption.ClientInstance{}
|
||||||
if err = v.encryption.Init(b, time.Duration(minutes)*time.Minute); err != nil {
|
if err = v.encryption.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
|
||||||
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
|
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -638,7 +638,8 @@ proxies: # socks5
|
|||||||
port: 443
|
port: 443
|
||||||
uuid: uuid
|
uuid: uuid
|
||||||
network: tcp
|
network: tcp
|
||||||
encryption: "8min-mlkem768client-bas64RawURLEncoding" # 复用八分钟后协商新的 sharedKey,需小于服务端的值
|
encryption: "8min-vless-mlkem768client-bas64RawURLEncoding" # 复用八分钟后协商新的 sharedKey,需小于服务端的值
|
||||||
|
# encryption: "8min-aes128xor-mlkem768client-bas64RawURLEncoding"
|
||||||
tls: false #可以不开启tls
|
tls: false #可以不开启tls
|
||||||
udp: true
|
udp: true
|
||||||
|
|
||||||
@ -1346,7 +1347,8 @@ listeners:
|
|||||||
flow: xtls-rprx-vision
|
flow: xtls-rprx-vision
|
||||||
# ws-path: "/" # 如果不为空则开启 websocket 传输层
|
# ws-path: "/" # 如果不为空则开启 websocket 传输层
|
||||||
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
|
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
|
||||||
# decryption: "10min-mlkem768seed-bas64RawURLEncoding" # 同时允许 1-RTT 模式与十分钟复用的 0-RTT 模式, 后面base64字符串可由可由 mihomo generate vless-mlkem768 命令生成
|
# decryption: "10min-vless-mlkem768seed-bas64RawURLEncoding" # 同时允许 1-RTT 模式与十分钟复用的 0-RTT 模式, 后面base64字符串可由可由 mihomo generate vless-mlkem768 命令生成
|
||||||
|
# decryption: "10min-aes128xor-mlkem768seed-bas64RawURLEncoding"
|
||||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||||
# certificate: ./server.crt
|
# certificate: ./server.crt
|
||||||
# private-key: ./server.key
|
# private-key: ./server.key
|
||||||
|
|||||||
@ -94,13 +94,24 @@ func TestInboundVless_Encryption(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inboundOptions := inbound.VlessOption{
|
t.Run("-vless-", func(t *testing.T) {
|
||||||
Decryption: "10min-mlkem768seed-" + seedBase64,
|
inboundOptions := inbound.VlessOption{
|
||||||
}
|
Decryption: "10min-vless-mlkem768seed-" + seedBase64,
|
||||||
outboundOptions := outbound.VlessOption{
|
}
|
||||||
Encryption: "8min-mlkem768client-" + clientBase64,
|
outboundOptions := outbound.VlessOption{
|
||||||
}
|
Encryption: "8min-vless-mlkem768client-" + clientBase64,
|
||||||
testInboundVless(t, inboundOptions, outboundOptions)
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
|
t.Run("-aes128xor-", func(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VlessOption{
|
||||||
|
Decryption: "10min-aes128xor-mlkem768seed-" + seedBase64,
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VlessOption{
|
||||||
|
Encryption: "8min-aes128xor-mlkem768client-" + clientBase64,
|
||||||
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInboundVless_Wss1(t *testing.T) {
|
func TestInboundVless_Wss1(t *testing.T) {
|
||||||
|
|||||||
@ -88,7 +88,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||||||
|
|
||||||
sl = &Listener{config: config, service: service}
|
sl = &Listener{config: config, service: service}
|
||||||
|
|
||||||
if s := strings.Split(config.Decryption, "-mlkem768seed-"); len(s) == 2 {
|
if s := strings.SplitN(config.Decryption, "-", 4); len(s) == 4 && s[2] == "mlkem768seed" {
|
||||||
var minutes uint32
|
var minutes uint32
|
||||||
if s[0] != "1rtt" {
|
if s[0] != "1rtt" {
|
||||||
t := strings.TrimSuffix(s[0], "min")
|
t := strings.TrimSuffix(s[0], "min")
|
||||||
@ -102,14 +102,22 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||||||
}
|
}
|
||||||
minutes = uint32(i)
|
minutes = uint32(i)
|
||||||
}
|
}
|
||||||
|
var xor uint32
|
||||||
|
switch s[1] {
|
||||||
|
case "vless":
|
||||||
|
case "aes128xor":
|
||||||
|
xor = 1
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
|
||||||
|
}
|
||||||
var b []byte
|
var b []byte
|
||||||
b, err = base64.RawURLEncoding.DecodeString(s[1])
|
b, err = base64.RawURLEncoding.DecodeString(s[3])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
|
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
|
||||||
}
|
}
|
||||||
if len(b) == encryption.MLKEM768SeedLength {
|
if len(b) == encryption.MLKEM768SeedLength {
|
||||||
sl.decryption = &encryption.ServerInstance{}
|
sl.decryption = &encryption.ServerInstance{}
|
||||||
if err = sl.decryption.Init(b, time.Duration(minutes)*time.Minute); err != nil {
|
if err = sl.decryption.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
|
||||||
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
|
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -38,6 +38,7 @@ func init() {
|
|||||||
type ClientInstance struct {
|
type ClientInstance struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
eKeyNfs *mlkem.EncapsulationKey768
|
eKeyNfs *mlkem.EncapsulationKey768
|
||||||
|
xor uint32
|
||||||
minutes time.Duration
|
minutes time.Duration
|
||||||
expire time.Time
|
expire time.Time
|
||||||
baseKey []byte
|
baseKey []byte
|
||||||
@ -57,8 +58,9 @@ type ClientConn struct {
|
|||||||
peerCache []byte
|
peerCache []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ClientInstance) Init(eKeyNfsData []byte, minutes time.Duration) (err error) {
|
func (i *ClientInstance) Init(eKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) {
|
||||||
i.eKeyNfs, err = mlkem.NewEncapsulationKey768(eKeyNfsData)
|
i.eKeyNfs, err = mlkem.NewEncapsulationKey768(eKeyNfsData)
|
||||||
|
i.xor = xor
|
||||||
i.minutes = minutes
|
i.minutes = minutes
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -67,6 +69,9 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
|
|||||||
if i.eKeyNfs == nil {
|
if i.eKeyNfs == nil {
|
||||||
return nil, errors.New("uninitialized")
|
return nil, errors.New("uninitialized")
|
||||||
}
|
}
|
||||||
|
if i.xor == 1 {
|
||||||
|
conn = NewXorConn(conn, i.eKeyNfs.Bytes())
|
||||||
|
}
|
||||||
c := &ClientConn{Conn: conn}
|
c := &ClientConn{Conn: conn}
|
||||||
|
|
||||||
if i.minutes > 0 {
|
if i.minutes > 0 {
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
// Package encryption copy and modify from xray-core
|
// Package encryption copy and modify from xray-core
|
||||||
// https://github.com/XTLS/Xray-core/commit/f61c14e9c63dc41a8a09135db3aea337974f3f37
|
// https://github.com/XTLS/Xray-core/commit/f61c14e9c63dc41a8a09135db3aea337974f3f37
|
||||||
|
// https://github.com/XTLS/Xray-core/commit/3e19bf9233bdd9bafc073a71c65b737cc1ffba5e
|
||||||
|
// https://github.com/XTLS/Xray-core/commit/7ffb555fc8ec51bd1e3e60f26f1d6957984dba80
|
||||||
package encryption
|
package encryption
|
||||||
|
|||||||
@ -25,6 +25,7 @@ type ServerSession struct {
|
|||||||
type ServerInstance struct {
|
type ServerInstance struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
dKeyNfs *mlkem.DecapsulationKey768
|
dKeyNfs *mlkem.DecapsulationKey768
|
||||||
|
xor uint32
|
||||||
minutes time.Duration
|
minutes time.Duration
|
||||||
sessions map[[21]byte]*ServerSession
|
sessions map[[21]byte]*ServerSession
|
||||||
stop bool
|
stop bool
|
||||||
@ -43,8 +44,9 @@ type ServerConn struct {
|
|||||||
nonce []byte
|
nonce []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ServerInstance) Init(dKeyNfsData []byte, minutes time.Duration) (err error) {
|
func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) {
|
||||||
i.dKeyNfs, err = mlkem.NewDecapsulationKey768(dKeyNfsData)
|
i.dKeyNfs, err = mlkem.NewDecapsulationKey768(dKeyNfsData)
|
||||||
|
i.xor = xor
|
||||||
if minutes > 0 {
|
if minutes > 0 {
|
||||||
i.minutes = minutes
|
i.minutes = minutes
|
||||||
i.sessions = make(map[[21]byte]*ServerSession)
|
i.sessions = make(map[[21]byte]*ServerSession)
|
||||||
@ -79,6 +81,9 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) {
|
|||||||
if i.dKeyNfs == nil {
|
if i.dKeyNfs == nil {
|
||||||
return nil, errors.New("uninitialized")
|
return nil, errors.New("uninitialized")
|
||||||
}
|
}
|
||||||
|
if i.xor == 1 {
|
||||||
|
conn = NewXorConn(conn, i.dKeyNfs.EncapsulationKey().Bytes())
|
||||||
|
}
|
||||||
c := &ServerConn{Conn: conn}
|
c := &ServerConn{Conn: conn}
|
||||||
|
|
||||||
peerTicketHello := make([]byte, 21+32)
|
peerTicketHello := make([]byte, 21+32)
|
||||||
|
|||||||
63
transport/vless/encryption/xor.go
Normal file
63
transport/vless/encryption/xor.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package encryption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type XorConn struct {
|
||||||
|
net.Conn
|
||||||
|
key []byte
|
||||||
|
ctr cipher.Stream
|
||||||
|
peerCtr cipher.Stream
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewXorConn(conn net.Conn, key []byte) *XorConn {
|
||||||
|
return &XorConn{Conn: conn, key: key[:16]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *XorConn) Write(b []byte) (int, error) {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
var iv []byte
|
||||||
|
if c.ctr == nil {
|
||||||
|
block, _ := aes.NewCipher(c.key)
|
||||||
|
iv = make([]byte, 16)
|
||||||
|
rand.Read(iv)
|
||||||
|
c.ctr = cipher.NewCTR(block, iv)
|
||||||
|
}
|
||||||
|
c.ctr.XORKeyStream(b, b) // caller MUST discard b
|
||||||
|
if iv != nil {
|
||||||
|
b = append(iv, b...)
|
||||||
|
}
|
||||||
|
if _, err := c.Conn.Write(b); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if iv != nil {
|
||||||
|
b = b[16:]
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *XorConn) Read(b []byte) (int, error) {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if c.peerCtr == nil {
|
||||||
|
peerIv := make([]byte, 16)
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerIv); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
block, _ := aes.NewCipher(c.key)
|
||||||
|
c.peerCtr = cipher.NewCTR(block, peerIv)
|
||||||
|
}
|
||||||
|
n, err := c.Conn.Read(b)
|
||||||
|
if n > 0 {
|
||||||
|
c.peerCtr.XORKeyStream(b[:n], b[:n])
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user