mirror of
https://github.com/bolucat/Archive.git
synced 2025-09-26 20:21:35 +08:00
Update On Tue Aug 12 20:40:50 CEST 2025
This commit is contained in:
@@ -3,14 +3,11 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/convert"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
@@ -456,41 +453,11 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
option: &option,
|
||||
}
|
||||
|
||||
if s := strings.SplitN(option.Encryption, "-", 4); len(s) == 4 && s[2] == "mlkem768client" {
|
||||
var minutes uint32
|
||||
if s[0] != "1rtt" {
|
||||
t := strings.TrimSuffix(s[0], "min")
|
||||
if t == s[0] {
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
|
||||
}
|
||||
var i int
|
||||
i, err = strconv.Atoi(t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
|
||||
}
|
||||
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
|
||||
b, err = base64.RawURLEncoding.DecodeString(s[3])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
|
||||
}
|
||||
if len(b) == encryption.MLKEM768ClientLength {
|
||||
v.encryption = &encryption.ClientInstance{}
|
||||
if err = v.encryption.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
|
||||
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
|
||||
}
|
||||
v.encryption, err = encryption.NewClient(option.Encryption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v.encryption != nil {
|
||||
if option.Flow != "" {
|
||||
return nil, errors.New(`vless "encryption" doesn't support "flow" yet`)
|
||||
}
|
||||
|
@@ -10,11 +10,13 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/common/pool"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
@@ -30,7 +32,7 @@ import (
|
||||
)
|
||||
|
||||
var httpPath = "/inbound_test"
|
||||
var httpData = make([]byte, 10240)
|
||||
var httpData = make([]byte, 2*pool.RelayBufferSize)
|
||||
var remoteAddr = netip.MustParseAddr("1.2.3.4")
|
||||
var userUUID = utils.NewUUIDV4().String()
|
||||
var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256)
|
||||
@@ -134,14 +136,21 @@ func NewHttpTestTunnel() *TestTunnel {
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Get(httpPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
render.Data(w, r, httpData)
|
||||
query := r.URL.Query()
|
||||
size, err := strconv.Atoi(query.Get("size"))
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.PlainText(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
render.Data(w, r, httpData[:size])
|
||||
})
|
||||
h2Server := &http2.Server{}
|
||||
server := http.Server{Handler: r}
|
||||
_ = http2.ConfigureServer(&server, h2Server)
|
||||
go server.Serve(ln)
|
||||
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) {
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil)
|
||||
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string, size int) {
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s?size=%d", proto, remoteAddr, httpPath, size), nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
@@ -200,7 +209,7 @@ func NewHttpTestTunnel() *TestTunnel {
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, httpData, data)
|
||||
assert.Equal(t, httpData[:size], data)
|
||||
}
|
||||
tunnel := &TestTunnel{
|
||||
HandleTCPConnFn: func(conn net.Conn, metadata *C.Metadata) {
|
||||
@@ -241,27 +250,30 @@ func NewHttpTestTunnel() *TestTunnel {
|
||||
},
|
||||
CloseFn: ln.Close,
|
||||
DoTestFn: func(t *testing.T, proxy C.ProxyAdapter) {
|
||||
|
||||
// Sequential testing for debugging
|
||||
t.Run("Sequential", func(t *testing.T) {
|
||||
testFn(t, proxy, "http")
|
||||
testFn(t, proxy, "https")
|
||||
testFn(t, proxy, "http", len(httpData))
|
||||
testFn(t, proxy, "https", len(httpData))
|
||||
})
|
||||
|
||||
// Concurrent testing to detect stress
|
||||
t.Run("Concurrent", func(t *testing.T) {
|
||||
wg := sync.WaitGroup{}
|
||||
const num = 50
|
||||
for i := 0; i < num; i++ {
|
||||
num := len(httpData) / 1024
|
||||
for i := 1; i <= num; i++ {
|
||||
i := i
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
testFn(t, proxy, "https")
|
||||
testFn(t, proxy, "https", i*1024)
|
||||
defer wg.Done()
|
||||
}()
|
||||
}
|
||||
for i := 0; i < num; i++ {
|
||||
for i := 1; i <= num; i++ {
|
||||
i := i
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
testFn(t, proxy, "http")
|
||||
testFn(t, proxy, "http", i*1024)
|
||||
defer wg.Done()
|
||||
}()
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var singMuxProtocolList = []string{"h2mux", "smux"} // don't test "yamux" because it has some confused bugs
|
||||
var singMuxProtocolList = []string{"smux"} // don't test "h2mux" and "yamux" because it has some confused bugs
|
||||
|
||||
// notCloseProxyAdapter is a proxy adapter that does not close the underlying outbound.ProxyAdapter.
|
||||
// The outbound.SingMux will close the underlying outbound.ProxyAdapter when it is closed, but we don't want to close it.
|
||||
|
@@ -2,15 +2,11 @@ package sing_vless
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
@@ -88,42 +84,11 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
|
||||
sl = &Listener{config: config, service: service}
|
||||
|
||||
if s := strings.SplitN(config.Decryption, "-", 4); len(s) == 4 && s[2] == "mlkem768seed" {
|
||||
var minutes uint32
|
||||
if s[0] != "1rtt" {
|
||||
t := strings.TrimSuffix(s[0], "min")
|
||||
if t == s[0] {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
|
||||
}
|
||||
var i int
|
||||
i, err = strconv.Atoi(t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
|
||||
}
|
||||
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
|
||||
b, err = base64.RawURLEncoding.DecodeString(s[3])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
|
||||
}
|
||||
if len(b) == encryption.MLKEM768SeedLength {
|
||||
sl.decryption = &encryption.ServerInstance{}
|
||||
if err = sl.decryption.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
|
||||
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
|
||||
}
|
||||
|
||||
sl.decryption, err = encryption.NewServer(config.Decryption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sl.decryption != nil {
|
||||
defer func() { // decryption must be closed to avoid the goroutine leak
|
||||
if err != nil {
|
||||
_ = sl.decryption.Close()
|
||||
|
@@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
@@ -13,7 +12,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/utls/mlkem"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
@@ -30,19 +28,20 @@ var (
|
||||
var ClientCipher byte
|
||||
|
||||
func init() {
|
||||
if !HasAESGCMHardwareSupport {
|
||||
if HasAESGCMHardwareSupport {
|
||||
ClientCipher = 1
|
||||
}
|
||||
}
|
||||
|
||||
type ClientInstance struct {
|
||||
sync.RWMutex
|
||||
eKeyNfs *mlkem.EncapsulationKey768
|
||||
xor uint32
|
||||
minutes time.Duration
|
||||
expire time.Time
|
||||
baseKey []byte
|
||||
ticket []byte
|
||||
nfsEKey *mlkem.EncapsulationKey768
|
||||
nfsEKeyBytes []byte
|
||||
xor uint32
|
||||
minutes time.Duration
|
||||
expire time.Time
|
||||
baseKey []byte
|
||||
ticket []byte
|
||||
}
|
||||
|
||||
type ClientConn struct {
|
||||
@@ -58,19 +57,22 @@ type ClientConn struct {
|
||||
peerCache []byte
|
||||
}
|
||||
|
||||
func (i *ClientInstance) Init(eKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) {
|
||||
i.eKeyNfs, err = mlkem.NewEncapsulationKey768(eKeyNfsData)
|
||||
i.xor = xor
|
||||
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
|
||||
}
|
||||
i.minutes = minutes
|
||||
return
|
||||
}
|
||||
|
||||
func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
|
||||
if i.eKeyNfs == nil {
|
||||
if i.nfsEKey == nil {
|
||||
return nil, errors.New("uninitialized")
|
||||
}
|
||||
if i.xor == 1 {
|
||||
conn = NewXorConn(conn, i.eKeyNfs.Bytes())
|
||||
if i.xor > 0 {
|
||||
conn = NewXorConn(conn, i.nfsEKeyBytes)
|
||||
}
|
||||
c := &ClientConn{Conn: conn}
|
||||
|
||||
@@ -86,18 +88,19 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
|
||||
i.RUnlock()
|
||||
}
|
||||
|
||||
nfsKey, encapsulatedNfsKey := i.eKeyNfs.Encapsulate()
|
||||
seed := make([]byte, 64)
|
||||
rand.Read(seed)
|
||||
dKeyPfs, _ := mlkem.NewDecapsulationKey768(seed)
|
||||
eKeyPfs := dKeyPfs.EncapsulationKey().Bytes()
|
||||
padding := randBetween(100, 1000)
|
||||
pfsDKeySeed := make([]byte, 64)
|
||||
rand.Read(pfsDKeySeed)
|
||||
pfsDKey, _ := mlkem.NewDecapsulationKey768(pfsDKeySeed)
|
||||
pfsEKeyBytes := pfsDKey.EncapsulationKey().Bytes()
|
||||
nfsKey, encapsulatedNfsKey := i.nfsEKey.Encapsulate()
|
||||
paddingLen := randBetween(100, 1000)
|
||||
|
||||
clientHello := make([]byte, 1088+1184+1+5+padding)
|
||||
copy(clientHello, encapsulatedNfsKey)
|
||||
copy(clientHello[1088:], eKeyPfs)
|
||||
clientHello[2272] = ClientCipher
|
||||
encodeHeader(clientHello[2273:], int(padding))
|
||||
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:])
|
||||
|
||||
if _, err := c.Conn.Write(clientHello); err != nil {
|
||||
return nil, err
|
||||
@@ -111,17 +114,15 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
|
||||
encapsulatedPfsKey := peerServerHello[:1088]
|
||||
c.ticket = peerServerHello[1088:]
|
||||
|
||||
pfsKey, err := dKeyPfs.Decapsulate(encapsulatedPfsKey)
|
||||
pfsKey, err := pfsDKey.Decapsulate(encapsulatedPfsKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.baseKey = append(nfsKey, pfsKey...)
|
||||
c.baseKey = append(pfsKey, nfsKey...)
|
||||
|
||||
authKey := make([]byte, 32)
|
||||
hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfs).Read(authKey)
|
||||
nonce := make([]byte, 12)
|
||||
VLESS, _ := newAead(ClientCipher, authKey).Open(nil, nonce, c.ticket, encapsulatedPfsKey)
|
||||
if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more message
|
||||
nonce := [12]byte{ClientCipher}
|
||||
VLESS, _ := NewAead(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, nonce[:], c.ticket, pfsEKeyBytes)
|
||||
if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more messages
|
||||
return nil, errors.New("invalid server")
|
||||
}
|
||||
|
||||
@@ -141,32 +142,39 @@ func (c *ClientConn) Write(b []byte) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
var data []byte
|
||||
if c.aead == nil {
|
||||
c.random = make([]byte, 32)
|
||||
rand.Read(c.random)
|
||||
key := make([]byte, 32)
|
||||
hkdf.New(sha256.New, c.baseKey, c.random, c.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
|
||||
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 {
|
||||
c.random = make([]byte, 32)
|
||||
rand.Read(c.random)
|
||||
c.aead = NewAead(ClientCipher, c.baseKey, c.random, c.ticket)
|
||||
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])
|
||||
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) { // after first Write()
|
||||
func (c *ClientConn) Read(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
@@ -177,11 +185,11 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
|
||||
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
peerPadding, _ := decodeHeader(peerHeader)
|
||||
if peerPadding == 0 {
|
||||
peerPaddingLen, _ := DecodeHeader(peerHeader)
|
||||
if peerPaddingLen == 0 {
|
||||
break
|
||||
}
|
||||
if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil {
|
||||
if _, err := io.ReadFull(c.Conn, make([]byte, peerPaddingLen)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
@@ -196,11 +204,9 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
|
||||
return 0, err
|
||||
}
|
||||
if c.random == nil {
|
||||
return 0, errors.New("can not Read() first")
|
||||
return 0, errors.New("empty c.random")
|
||||
}
|
||||
peerKey := make([]byte, 32)
|
||||
hkdf.New(sha256.New, c.baseKey, peerRandom, c.random).Read(peerKey)
|
||||
c.peerAead = newAead(ClientCipher, peerKey)
|
||||
c.peerAead = NewAead(ClientCipher, c.baseKey, peerRandom, c.random)
|
||||
c.peerNonce = make([]byte, 12)
|
||||
}
|
||||
if len(c.peerCache) != 0 {
|
||||
@@ -211,7 +217,7 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
|
||||
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
peerLength, err := decodeHeader(peerHeader) // 17~17000
|
||||
peerLength, err := DecodeHeader(peerHeader) // 17~17000
|
||||
if err != nil {
|
||||
if c.instance != nil {
|
||||
c.instance.Lock()
|
||||
@@ -228,10 +234,17 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
|
||||
}
|
||||
dst := peerData[:peerLength-16]
|
||||
if len(dst) <= len(b) {
|
||||
dst = b[:len(dst)] // max=8192 is recommended for peer
|
||||
dst = b[:len(dst)] // avoids another copy()
|
||||
}
|
||||
var peerAead cipher.AEAD
|
||||
if bytes.Equal(c.peerNonce, MaxNonce) {
|
||||
peerAead = NewAead(ClientCipher, c.baseKey, peerData, peerHeader)
|
||||
}
|
||||
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
|
||||
increaseNonce(c.peerNonce)
|
||||
if peerAead != nil {
|
||||
c.peerAead = peerAead
|
||||
}
|
||||
IncreaseNonce(c.peerNonce)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
@@ -1,17 +1,22 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
func encodeHeader(b []byte, l int) {
|
||||
var MaxNonce = bytes.Repeat([]byte{255}, 12)
|
||||
|
||||
func EncodeHeader(b []byte, l int) {
|
||||
b[0] = 23
|
||||
b[1] = 3
|
||||
b[2] = 3
|
||||
@@ -19,10 +24,10 @@ func encodeHeader(b []byte, l int) {
|
||||
b[4] = byte(l)
|
||||
}
|
||||
|
||||
func decodeHeader(b []byte) (int, error) {
|
||||
func DecodeHeader(b []byte) (int, error) {
|
||||
if b[0] == 23 && b[1] == 3 && b[2] == 3 {
|
||||
l := int(b[3])<<8 | int(b[4])
|
||||
if l < 17 || l > 17000 { // TODO
|
||||
if l < 17 || l > 17000 { // TODO: TLSv1.3 max length
|
||||
return 0, errors.New("invalid length in record's header: " + strconv.Itoa(l))
|
||||
}
|
||||
return l, nil
|
||||
@@ -30,29 +35,24 @@ func decodeHeader(b []byte) (int, error) {
|
||||
return 0, errors.New("invalid record's header")
|
||||
}
|
||||
|
||||
func newAead(c byte, k []byte) cipher.AEAD {
|
||||
switch c {
|
||||
case 0:
|
||||
if block, err := aes.NewCipher(k); err == nil {
|
||||
aead, _ := cipher.NewGCM(block)
|
||||
return aead
|
||||
}
|
||||
case 1:
|
||||
aead, _ := chacha20poly1305.New(k)
|
||||
return aead
|
||||
func NewAead(c byte, secret, salt, info []byte) (aead cipher.AEAD) {
|
||||
key := make([]byte, 32)
|
||||
hkdf.New(sha256.New, secret, salt, info).Read(key)
|
||||
if c&1 == 1 {
|
||||
block, _ := aes.NewCipher(key)
|
||||
aead, _ = cipher.NewGCM(block)
|
||||
} else {
|
||||
aead, _ = chacha20poly1305.New(key)
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
func increaseNonce(nonce []byte) {
|
||||
func IncreaseNonce(nonce []byte) {
|
||||
for i := 0; i < 12; i++ {
|
||||
nonce[11-i]++
|
||||
if nonce[11-i] != 0 {
|
||||
break
|
||||
}
|
||||
if i == 11 {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -2,4 +2,7 @@
|
||||
// https://github.com/XTLS/Xray-core/commit/f61c14e9c63dc41a8a09135db3aea337974f3f37
|
||||
// https://github.com/XTLS/Xray-core/commit/3e19bf9233bdd9bafc073a71c65b737cc1ffba5e
|
||||
// https://github.com/XTLS/Xray-core/commit/7ffb555fc8ec51bd1e3e60f26f1d6957984dba80
|
||||
// https://github.com/XTLS/Xray-core/commit/ec1cc35188c1a5f38a2ff75e88b5d043ffdc59da
|
||||
// https://github.com/XTLS/Xray-core/commit/5c611420487a92f931faefc01d4bf03869f477f6
|
||||
// https://github.com/XTLS/Xray-core/commit/23d7aad461d232bc5bed52dd6aaa731ecd88ad35
|
||||
package encryption
|
||||
|
99
mihomo/transport/vless/encryption/factory.go
Normal file
99
mihomo/transport/vless/encryption/factory.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewClient new client from encryption string
|
||||
// maybe return a nil *ClientInstance without any error, that means don't need to encrypt
|
||||
func NewClient(encryption string) (*ClientInstance, error) {
|
||||
switch encryption {
|
||||
case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility
|
||||
return nil, nil
|
||||
}
|
||||
if s := strings.SplitN(encryption, "-", 4); len(s) == 4 && s[2] == "mlkem768client" {
|
||||
var minutes uint32
|
||||
if s[0] != "1rtt" {
|
||||
t := strings.TrimSuffix(s[0], "min")
|
||||
if t == s[0] {
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
|
||||
}
|
||||
i, err := strconv.Atoi(t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
|
||||
}
|
||||
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", encryption)
|
||||
}
|
||||
b, err := base64.RawURLEncoding.DecodeString(s[3])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
|
||||
}
|
||||
if len(b) == MLKEM768ClientLength {
|
||||
client := &ClientInstance{}
|
||||
if err = client.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
|
||||
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
|
||||
}
|
||||
return client, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
|
||||
}
|
||||
|
||||
// NewServer new server from decryption string
|
||||
// maybe return a nil *ServerInstance without any error, that means don't need to decrypt
|
||||
func NewServer(decryption string) (*ServerInstance, error) {
|
||||
switch decryption {
|
||||
case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility
|
||||
return nil, nil
|
||||
}
|
||||
if s := strings.SplitN(decryption, "-", 4); len(s) == 4 && s[2] == "mlkem768seed" {
|
||||
var minutes uint32
|
||||
if s[0] != "1rtt" {
|
||||
t := strings.TrimSuffix(s[0], "min")
|
||||
if t == s[0] {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
}
|
||||
i, err := strconv.Atoi(t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
}
|
||||
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", decryption)
|
||||
}
|
||||
b, err := base64.RawURLEncoding.DecodeString(s[3])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
}
|
||||
if len(b) == MLKEM768SeedLength {
|
||||
server := &ServerInstance{}
|
||||
if err = server.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
|
||||
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
|
||||
}
|
||||
return server, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
}
|
@@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
@@ -12,7 +11,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/utls/mlkem"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
type ServerSession struct {
|
||||
@@ -24,11 +22,12 @@ type ServerSession struct {
|
||||
|
||||
type ServerInstance struct {
|
||||
sync.RWMutex
|
||||
dKeyNfs *mlkem.DecapsulationKey768
|
||||
xor uint32
|
||||
minutes time.Duration
|
||||
sessions map[[21]byte]*ServerSession
|
||||
stop bool
|
||||
nfsDKey *mlkem.DecapsulationKey768
|
||||
nfsEKeyBytes []byte
|
||||
xor uint32
|
||||
minutes time.Duration
|
||||
sessions map[[21]byte]*ServerSession
|
||||
closed bool
|
||||
}
|
||||
|
||||
type ServerConn struct {
|
||||
@@ -44,9 +43,12 @@ type ServerConn struct {
|
||||
nonce []byte
|
||||
}
|
||||
|
||||
func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) {
|
||||
i.dKeyNfs, err = mlkem.NewDecapsulationKey768(dKeyNfsData)
|
||||
i.xor = xor
|
||||
func (i *ServerInstance) Init(nfsDKeySeed []byte, xor uint32, minutes time.Duration) (err error) {
|
||||
i.nfsDKey, err = mlkem.NewDecapsulationKey768(nfsDKeySeed)
|
||||
if xor > 0 {
|
||||
i.nfsEKeyBytes = i.nfsDKey.EncapsulationKey().Bytes()
|
||||
i.xor = xor
|
||||
}
|
||||
if minutes > 0 {
|
||||
i.minutes = minutes
|
||||
i.sessions = make(map[[21]byte]*ServerSession)
|
||||
@@ -55,12 +57,13 @@ func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Durat
|
||||
time.Sleep(time.Minute)
|
||||
now := time.Now()
|
||||
i.Lock()
|
||||
if i.stop {
|
||||
if i.closed {
|
||||
i.Unlock()
|
||||
return
|
||||
}
|
||||
for index, session := range i.sessions {
|
||||
for ticket, session := range i.sessions {
|
||||
if now.After(session.expire) {
|
||||
delete(i.sessions, index)
|
||||
delete(i.sessions, ticket)
|
||||
}
|
||||
}
|
||||
i.Unlock()
|
||||
@@ -72,17 +75,17 @@ func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Durat
|
||||
|
||||
func (i *ServerInstance) Close() (err error) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
i.stop = true
|
||||
i.closed = true
|
||||
i.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) {
|
||||
if i.dKeyNfs == nil {
|
||||
if i.nfsDKey == nil {
|
||||
return nil, errors.New("uninitialized")
|
||||
}
|
||||
if i.xor == 1 {
|
||||
conn = NewXorConn(conn, i.dKeyNfs.EncapsulationKey().Bytes())
|
||||
if i.xor > 0 {
|
||||
conn = NewXorConn(conn, i.nfsEKeyBytes)
|
||||
}
|
||||
c := &ServerConn{Conn: conn}
|
||||
|
||||
@@ -109,50 +112,49 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) {
|
||||
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if l, _ := decodeHeader(peerHeader); l != 0 {
|
||||
c.Conn.Write(make([]byte, randBetween(100, 1000))) // make client do new handshake
|
||||
if l, _ := DecodeHeader(peerHeader); l != 0 {
|
||||
noise := make([]byte, randBetween(100, 1000))
|
||||
rand.Read(noise)
|
||||
c.Conn.Write(noise) // make client do new handshake
|
||||
return nil, errors.New("invalid ticket")
|
||||
}
|
||||
|
||||
peerClientHello := make([]byte, 1088+1184+1)
|
||||
peerClientHello := make([]byte, 1+1184+1088)
|
||||
copy(peerClientHello, peerTicketHello)
|
||||
copy(peerClientHello[53:], peerHeader)
|
||||
if _, err := io.ReadFull(c.Conn, peerClientHello[58:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encapsulatedNfsKey := peerClientHello[:1088]
|
||||
eKeyPfsData := peerClientHello[1088:2272]
|
||||
c.cipher = peerClientHello[2272]
|
||||
if c.cipher != 0 && c.cipher != 1 {
|
||||
return nil, errors.New("invalid cipher")
|
||||
}
|
||||
c.cipher = peerClientHello[0]
|
||||
pfsEKeyBytes := peerClientHello[1:1185]
|
||||
encapsulatedNfsKey := peerClientHello[1185:2273]
|
||||
|
||||
nfsKey, err := i.dKeyNfs.Decapsulate(encapsulatedNfsKey)
|
||||
pfsEKey, err := mlkem.NewEncapsulationKey768(pfsEKeyBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eKeyPfs, err := mlkem.NewEncapsulationKey768(eKeyPfsData)
|
||||
nfsKey, err := i.nfsDKey.Decapsulate(encapsulatedNfsKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pfsKey, encapsulatedPfsKey := eKeyPfs.Encapsulate()
|
||||
c.baseKey = append(nfsKey, pfsKey...)
|
||||
pfsKey, encapsulatedPfsKey := pfsEKey.Encapsulate()
|
||||
c.baseKey = append(pfsKey, nfsKey...)
|
||||
|
||||
authKey := make([]byte, 32)
|
||||
hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfsData).Read(authKey)
|
||||
nonce := make([]byte, 12)
|
||||
c.ticket = newAead(c.cipher, authKey).Seal(nil, nonce, []byte("VLESS"), encapsulatedPfsKey)
|
||||
nonce := [12]byte{c.cipher}
|
||||
c.ticket = NewAead(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Seal(nil, nonce[:], []byte("VLESS"), pfsEKeyBytes)
|
||||
|
||||
padding := randBetween(100, 1000)
|
||||
paddingLen := randBetween(100, 1000)
|
||||
|
||||
serverHello := make([]byte, 1088+21+5+padding)
|
||||
serverHello := make([]byte, 1088+21+5+paddingLen)
|
||||
copy(serverHello, encapsulatedPfsKey)
|
||||
copy(serverHello[1088:], c.ticket)
|
||||
encodeHeader(serverHello[1109:], int(padding))
|
||||
EncodeHeader(serverHello[1109:], int(paddingLen))
|
||||
rand.Read(serverHello[1114:])
|
||||
|
||||
if _, err := c.Conn.Write(serverHello); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// we can send more padding if needed
|
||||
|
||||
if i.minutes > 0 {
|
||||
i.Lock()
|
||||
@@ -178,11 +180,11 @@ func (c *ServerConn) Read(b []byte) (int, error) {
|
||||
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
peerPadding, _ := decodeHeader(peerHeader)
|
||||
if peerPadding == 0 {
|
||||
peerPaddingLen, _ := DecodeHeader(peerHeader)
|
||||
if peerPaddingLen == 0 {
|
||||
break
|
||||
}
|
||||
if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil {
|
||||
if _, err := io.ReadFull(c.Conn, make([]byte, peerPaddingLen)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
@@ -199,9 +201,7 @@ func (c *ServerConn) Read(b []byte) (int, error) {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
peerKey := make([]byte, 32)
|
||||
hkdf.New(sha256.New, c.baseKey, c.peerRandom, c.ticket).Read(peerKey)
|
||||
c.peerAead = newAead(c.cipher, peerKey)
|
||||
c.peerAead = NewAead(c.cipher, c.baseKey, c.peerRandom, c.ticket)
|
||||
c.peerNonce = make([]byte, 12)
|
||||
}
|
||||
if len(c.peerCache) != 0 {
|
||||
@@ -212,7 +212,7 @@ func (c *ServerConn) Read(b []byte) (int, error) {
|
||||
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
peerLength, err := decodeHeader(peerHeader) // 17~17000
|
||||
peerLength, err := DecodeHeader(peerHeader) // 17~17000
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -222,10 +222,17 @@ func (c *ServerConn) Read(b []byte) (int, error) {
|
||||
}
|
||||
dst := peerData[:peerLength-16]
|
||||
if len(dst) <= len(b) {
|
||||
dst = b[:len(dst)] // max=8192 is recommended for peer
|
||||
dst = b[:len(dst)] // avoids another copy()
|
||||
}
|
||||
var peerAead cipher.AEAD
|
||||
if bytes.Equal(c.peerNonce, MaxNonce) {
|
||||
peerAead = NewAead(c.cipher, c.baseKey, peerData, peerHeader)
|
||||
}
|
||||
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
|
||||
increaseNonce(c.peerNonce)
|
||||
if peerAead != nil {
|
||||
c.peerAead = peerAead
|
||||
}
|
||||
IncreaseNonce(c.peerNonce)
|
||||
if err != nil {
|
||||
return 0, errors.New("error")
|
||||
}
|
||||
@@ -236,31 +243,39 @@ func (c *ServerConn) Read(b []byte) (int, error) {
|
||||
return len(dst), nil
|
||||
}
|
||||
|
||||
func (c *ServerConn) Write(b []byte) (int, error) { // after first Read()
|
||||
func (c *ServerConn) Write(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
var data []byte
|
||||
if c.aead == nil {
|
||||
if c.peerRandom == nil {
|
||||
return 0, errors.New("can not Write() first")
|
||||
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 {
|
||||
if c.peerRandom == nil {
|
||||
return 0, errors.New("empty c.peerRandom")
|
||||
}
|
||||
data = make([]byte, 32+5+len(b)+16)
|
||||
rand.Read(data[:32])
|
||||
c.aead = NewAead(c.cipher, c.baseKey, data[:32], c.peerRandom)
|
||||
c.nonce = make([]byte, 12)
|
||||
EncodeHeader(data[32:], len(b)+16)
|
||||
c.aead.Seal(data[:37], c.nonce, b, data[32:37])
|
||||
} else {
|
||||
data = make([]byte, 5+len(b)+16)
|
||||
EncodeHeader(data, len(b)+16)
|
||||
c.aead.Seal(data[:5], c.nonce, b, data[:5])
|
||||
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
|
||||
}
|
||||
data = make([]byte, 32+5+len(b)+16)
|
||||
rand.Read(data[:32])
|
||||
key := make([]byte, 32)
|
||||
hkdf.New(sha256.New, c.baseKey, data[:32], c.peerRandom).Read(key)
|
||||
c.aead = newAead(c.cipher, key)
|
||||
c.nonce = make([]byte, 12)
|
||||
encodeHeader(data[32:], len(b)+16)
|
||||
c.aead.Seal(data[:37], c.nonce, b, data[32:37])
|
||||
} else {
|
||||
data = make([]byte, 5+len(b)+16)
|
||||
encodeHeader(data, len(b)+16)
|
||||
c.aead.Seal(data[:5], c.nonce, b, data[:5])
|
||||
}
|
||||
increaseNonce(c.nonce)
|
||||
if _, err := c.Conn.Write(data); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
@@ -38,7 +38,7 @@ func (c *XorConn) Write(b []byte) (int, error) {
|
||||
return 0, err
|
||||
}
|
||||
if iv != nil {
|
||||
b = b[16:]
|
||||
b = b[16:] // for len(b)
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user