1
0
mirror of https://github.com/MetaCubeX/mihomo.git synced 2025-09-20 12:35:59 +08:00
Files
mihomo/transport/vless/encryption/client.go

256 lines
6.0 KiB
Go
Raw Normal View History

2025-08-10 22:16:25 +08:00
package encryption
import (
"bytes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"errors"
"io"
"net"
"runtime"
"sync"
"time"
"github.com/metacubex/utls/mlkem"
"golang.org/x/crypto/hkdf"
"golang.org/x/sys/cpu"
)
var (
// 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
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH
hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le"
HasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64
)
var ClientCipher byte
func init() {
2025-08-12 20:06:08 +08:00
if HasAESGCMHardwareSupport {
2025-08-10 22:16:25 +08:00
ClientCipher = 1
}
}
type ClientInstance struct {
sync.RWMutex
2025-08-12 20:06:08 +08:00
nfsEKey *mlkem.EncapsulationKey768
nfsEKeyBytes []byte
xor uint32
minutes time.Duration
expire time.Time
baseKey []byte
ticket []byte
2025-08-10 22:16:25 +08:00
}
type ClientConn struct {
net.Conn
instance *ClientInstance
baseKey []byte
ticket []byte
2025-08-10 22:16:25 +08:00
random []byte
aead cipher.AEAD
nonce []byte
peerAead cipher.AEAD
peerNonce []byte
peerCache []byte
}
2025-08-12 20:06:08 +08:00
func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Duration) (err error) {
i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes)
if xor > 0 {
i.nfsEKeyBytes = nfsEKeyBytes
i.xor = xor
}
2025-08-10 22:16:25 +08:00
i.minutes = minutes
return
}
func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
2025-08-12 20:06:08 +08:00
if i.nfsEKey == nil {
2025-08-10 22:16:25 +08:00
return nil, errors.New("uninitialized")
}
2025-08-12 20:06:08 +08:00
if i.xor > 0 {
conn = NewXorConn(conn, i.nfsEKeyBytes)
}
2025-08-10 22:16:25 +08:00
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
2025-08-10 22:16:25 +08:00
i.RUnlock()
return c, nil
}
i.RUnlock()
}
2025-08-12 20:06:08 +08:00
pfsDKeySeed := make([]byte, 64)
rand.Read(pfsDKeySeed)
pfsDKey, _ := mlkem.NewDecapsulationKey768(pfsDKeySeed)
pfsEKeyBytes := pfsDKey.EncapsulationKey().Bytes()
nfsKey, encapsulatedNfsKey := i.nfsEKey.Encapsulate()
paddingLen := randBetween(100, 1000)
2025-08-10 22:16:25 +08:00
2025-08-12 20:06:08 +08:00
clientHello := make([]byte, 1+1184+1088+5+paddingLen)
clientHello[0] = ClientCipher
copy(clientHello[1:], pfsEKeyBytes)
copy(clientHello[1185:], encapsulatedNfsKey)
encodeHeader(clientHello[2273:], int(paddingLen))
rand.Read(clientHello[2278:])
2025-08-10 22:16:25 +08:00
if _, err := c.Conn.Write(clientHello); err != nil {
return nil, err
}
// we can send more padding if needed
peerServerHello := make([]byte, 1088+21)
if _, err := io.ReadFull(c.Conn, peerServerHello); err != nil {
return nil, err
}
encapsulatedPfsKey := peerServerHello[:1088]
c.ticket = peerServerHello[1088:]
2025-08-10 22:16:25 +08:00
2025-08-12 20:06:08 +08:00
pfsKey, err := pfsDKey.Decapsulate(encapsulatedPfsKey)
2025-08-10 22:16:25 +08:00
if err != nil {
return nil, err
}
2025-08-12 20:06:08 +08:00
c.baseKey = append(pfsKey, nfsKey...)
2025-08-10 22:16:25 +08:00
authKey := make([]byte, 32)
2025-08-12 20:06:08 +08:00
hkdf.New(sha256.New, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Read(authKey)
nonce := [12]byte{ClientCipher}
VLESS, _ := newAead(ClientCipher, authKey).Open(nil, nonce[:], c.ticket, pfsEKeyBytes)
2025-08-10 22:16:25 +08:00
if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more message
return nil, errors.New("invalid server")
}
if i.minutes > 0 {
i.Lock()
i.expire = time.Now().Add(i.minutes)
i.baseKey = c.baseKey
i.ticket = c.ticket
2025-08-10 22:16:25 +08:00
i.Unlock()
}
return c, nil
}
func (c *ClientConn) Write(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
2025-08-12 20:06:08 +08:00
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)
var data []byte
if c.aead == nil {
c.random = make([]byte, 32)
rand.Read(c.random)
key := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, c.random, c.ticket).Read(key)
c.aead = newAead(ClientCipher, key)
c.nonce = make([]byte, 12)
data = make([]byte, 21+32+5+len(b)+16)
copy(data, c.ticket)
copy(data[21:], c.random)
encodeHeader(data[53:], len(b)+16)
c.aead.Seal(data[:58], c.nonce, b, data[53:58])
} else {
data = make([]byte, 5+len(b)+16)
encodeHeader(data, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
}
increaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
}
2025-08-10 22:16:25 +08:00
}
return len(b), nil
}
2025-08-12 20:06:08 +08:00
func (c *ClientConn) Read(b []byte) (int, error) {
2025-08-10 22:16:25 +08:00
if len(b) == 0 {
return 0, nil
}
peerHeader := make([]byte, 5)
if c.peerAead == nil {
if c.instance == nil {
for {
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
2025-08-12 20:06:08 +08:00
peerPaddingLen, _ := decodeHeader(peerHeader)
if peerPaddingLen == 0 {
2025-08-10 22:16:25 +08:00
break
}
2025-08-12 20:06:08 +08:00
if _, err := io.ReadFull(c.Conn, make([]byte, peerPaddingLen)); err != nil {
2025-08-10 22:16:25 +08:00
return 0, err
}
}
} else {
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
}
peerRandom := make([]byte, 32)
copy(peerRandom, peerHeader)
if _, err := io.ReadFull(c.Conn, peerRandom[5:]); err != nil {
return 0, err
}
if c.random == nil {
2025-08-12 20:06:08 +08:00
return 0, errors.New("empty c.random")
2025-08-10 22:16:25 +08:00
}
peerKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, peerRandom, c.random).Read(peerKey)
c.peerAead = newAead(ClientCipher, peerKey)
c.peerNonce = make([]byte, 12)
}
if len(c.peerCache) != 0 {
n := copy(b, c.peerCache)
c.peerCache = c.peerCache[n:]
return n, nil
}
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerLength, err := decodeHeader(peerHeader) // 17~17000
if err != nil {
if c.instance != nil {
c.instance.Lock()
if bytes.Equal(c.ticket, c.instance.ticket) {
2025-08-10 22:16:25 +08:00
c.instance.expire = time.Now() // expired
}
c.instance.Unlock()
}
return 0, err
}
peerData := make([]byte, peerLength)
if _, err := io.ReadFull(c.Conn, peerData); err != nil {
return 0, err
}
dst := peerData[:peerLength-16]
if len(dst) <= len(b) {
2025-08-12 20:06:08 +08:00
dst = b[:len(dst)] // avoids another copy()
2025-08-10 22:16:25 +08:00
}
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
increaseNonce(c.peerNonce)
if err != nil {
return 0, err
}
if len(dst) > len(b) {
c.peerCache = dst[copy(b, dst):]
dst = b // for len(dst)
}
return len(dst), nil
}