mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-12-20 17:10:08 +08:00
feat: support vless encryption
This commit is contained in:
parent
e89af723cd
commit
1b0c72bfab
@ -3,10 +3,14 @@ package outbound
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/common/convert"
|
"github.com/metacubex/mihomo/common/convert"
|
||||||
N "github.com/metacubex/mihomo/common/net"
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
@ -19,6 +23,7 @@ import (
|
|||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
"github.com/metacubex/mihomo/transport/gun"
|
"github.com/metacubex/mihomo/transport/gun"
|
||||||
"github.com/metacubex/mihomo/transport/vless"
|
"github.com/metacubex/mihomo/transport/vless"
|
||||||
|
"github.com/metacubex/mihomo/transport/vless/encryption"
|
||||||
"github.com/metacubex/mihomo/transport/vmess"
|
"github.com/metacubex/mihomo/transport/vmess"
|
||||||
|
|
||||||
vmessSing "github.com/metacubex/sing-vmess"
|
vmessSing "github.com/metacubex/sing-vmess"
|
||||||
@ -31,6 +36,8 @@ type Vless struct {
|
|||||||
client *vless.Client
|
client *vless.Client
|
||||||
option *VlessOption
|
option *VlessOption
|
||||||
|
|
||||||
|
encryption *encryption.ClientInstance
|
||||||
|
|
||||||
// for gun mux
|
// for gun mux
|
||||||
gunTLSConfig *tls.Config
|
gunTLSConfig *tls.Config
|
||||||
gunConfig *gun.Config
|
gunConfig *gun.Config
|
||||||
@ -53,6 +60,7 @@ type VlessOption struct {
|
|||||||
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
||||||
XUDP bool `proxy:"xudp,omitempty"`
|
XUDP bool `proxy:"xudp,omitempty"`
|
||||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||||
|
Encryption string `proxy:"encryption,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||||
@ -164,6 +172,12 @@ func (v *Vless) streamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
|||||||
done := N.SetupContextForConn(ctx, c)
|
done := N.SetupContextForConn(ctx, c)
|
||||||
defer done(&err)
|
defer done(&err)
|
||||||
}
|
}
|
||||||
|
if v.encryption != nil {
|
||||||
|
c, err = v.encryption.Handshake(c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if metadata.NetWork == C.UDP {
|
if metadata.NetWork == C.UDP {
|
||||||
if v.option.PacketAddr {
|
if v.option.PacketAddr {
|
||||||
metadata = &C.Metadata{
|
metadata = &C.Metadata{
|
||||||
@ -442,6 +456,36 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
option: &option,
|
option: &option,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s := strings.Split(option.Encryption, "-mlkem768client-"); len(s) == 2 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
i, err := strconv.Atoi(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
|
||||||
|
}
|
||||||
|
minutes = uint32(i)
|
||||||
|
}
|
||||||
|
b, err := base64.RawURLEncoding.DecodeString(s[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
|
||||||
|
}
|
||||||
|
if len(b) == 1184 {
|
||||||
|
v.encryption = &encryption.ClientInstance{}
|
||||||
|
if err := v.encryption.Init(b, 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)
|
||||||
|
}
|
||||||
|
if option.Flow != "" {
|
||||||
|
return nil, errors.New(`VLESS users: "encryption" doesn't support "flow" yet`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
v.realityConfig, err = v.option.RealityOpts.Parse()
|
v.realityConfig, err = v.option.RealityOpts.Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -5,13 +5,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/component/ech"
|
"github.com/metacubex/mihomo/component/ech"
|
||||||
|
"github.com/metacubex/mihomo/transport/vless/encryption"
|
||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Main(args []string) {
|
func Main(args []string) {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair")
|
panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair/vless-mlkem768")
|
||||||
}
|
}
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "uuid":
|
case "uuid":
|
||||||
@ -45,5 +46,16 @@ func Main(args []string) {
|
|||||||
}
|
}
|
||||||
fmt.Println("Config:", configBase64)
|
fmt.Println("Config:", configBase64)
|
||||||
fmt.Println("Key:", keyPem)
|
fmt.Println("Key:", keyPem)
|
||||||
|
case "vless-mlkem768":
|
||||||
|
var seed string
|
||||||
|
if len(args) > 1 {
|
||||||
|
seed = args[1]
|
||||||
|
}
|
||||||
|
seedBase64, pubBase64, err := encryption.GenMLKEM768(seed)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Seed: " + seedBase64)
|
||||||
|
fmt.Println("Client: " + pubBase64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -632,6 +632,16 @@ proxies: # socks5
|
|||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
|
|
||||||
|
- name: "vless-encryption"
|
||||||
|
type: vless
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
uuid: uuid
|
||||||
|
network: tcp
|
||||||
|
encryption: "8min-mlkem768client-bas64RawURLEncoding" # 复用八分钟后协商新的 sharedKey,需小于服务端的值
|
||||||
|
tls: false #可以不开启tls
|
||||||
|
udp: true
|
||||||
|
|
||||||
- name: "vless-reality-vision"
|
- name: "vless-reality-vision"
|
||||||
type: vless
|
type: vless
|
||||||
server: server
|
server: server
|
||||||
@ -1336,6 +1346,7 @@ 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 模式
|
||||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||||
# certificate: ./server.crt
|
# certificate: ./server.crt
|
||||||
# private-key: ./server.key
|
# private-key: ./server.key
|
||||||
@ -1364,7 +1375,7 @@ listeners:
|
|||||||
after-bytes: 0 # 传输指定字节后开始限速
|
after-bytes: 0 # 传输指定字节后开始限速
|
||||||
bytes-per-sec: 0 # 基准速率(字节/秒)
|
bytes-per-sec: 0 # 基准速率(字节/秒)
|
||||||
burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
|
burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
|
||||||
### 注意,对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 的其中一项 ###
|
### 注意,对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 或 “decryption” 的其中一项 ###
|
||||||
|
|
||||||
- name: anytls-in-1
|
- name: anytls-in-1
|
||||||
type: anytls
|
type: anytls
|
||||||
|
|||||||
2
go.mod
2
go.mod
@ -35,7 +35,7 @@ require (
|
|||||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
|
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
|
||||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee
|
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee
|
||||||
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4
|
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4
|
||||||
github.com/metacubex/utls v1.8.0
|
github.com/metacubex/utls v1.8.1-0.20250810142204-d0e55ab2e852
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
|
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
|
||||||
github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20
|
github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20
|
||||||
github.com/mroth/weightedrand/v2 v2.1.0
|
github.com/mroth/weightedrand/v2 v2.1.0
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -141,6 +141,8 @@ github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nU
|
|||||||
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||||
github.com/metacubex/utls v1.8.0 h1:mSYi6FMnmc5riARl5UZDmWVy710z+P5b7xuGW0lV9ac=
|
github.com/metacubex/utls v1.8.0 h1:mSYi6FMnmc5riARl5UZDmWVy710z+P5b7xuGW0lV9ac=
|
||||||
github.com/metacubex/utls v1.8.0/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ=
|
github.com/metacubex/utls v1.8.0/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ=
|
||||||
|
github.com/metacubex/utls v1.8.1-0.20250810142204-d0e55ab2e852 h1:MLHUGmASNH7/AeoGmSrVM2RutRZAqIDSbQWBp0P7ItE=
|
||||||
|
github.com/metacubex/utls v1.8.1-0.20250810142204-d0e55ab2e852/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ=
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
|
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
|
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
|
||||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||||
|
|||||||
@ -17,6 +17,7 @@ type VlessServer struct {
|
|||||||
Enable bool
|
Enable bool
|
||||||
Listen string
|
Listen string
|
||||||
Users []VlessUser
|
Users []VlessUser
|
||||||
|
Decryption string
|
||||||
WsPath string
|
WsPath string
|
||||||
GrpcServiceName string
|
GrpcServiceName string
|
||||||
Certificate string
|
Certificate string
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
type VlessOption struct {
|
type VlessOption struct {
|
||||||
BaseOption
|
BaseOption
|
||||||
Users []VlessUser `inbound:"users"`
|
Users []VlessUser `inbound:"users"`
|
||||||
|
Decryption string `inbound:"decryption,omitempty"`
|
||||||
WsPath string `inbound:"ws-path,omitempty"`
|
WsPath string `inbound:"ws-path,omitempty"`
|
||||||
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
|
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
|
||||||
Certificate string `inbound:"certificate,omitempty"`
|
Certificate string `inbound:"certificate,omitempty"`
|
||||||
@ -58,6 +59,7 @@ func NewVless(options *VlessOption) (*Vless, error) {
|
|||||||
Enable: true,
|
Enable: true,
|
||||||
Listen: base.RawAddress(),
|
Listen: base.RawAddress(),
|
||||||
Users: users,
|
Users: users,
|
||||||
|
Decryption: options.Decryption,
|
||||||
WsPath: options.WsPath,
|
WsPath: options.WsPath,
|
||||||
GrpcServiceName: options.GrpcServiceName,
|
GrpcServiceName: options.GrpcServiceName,
|
||||||
Certificate: options.Certificate,
|
Certificate: options.Certificate,
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/metacubex/mihomo/adapter/outbound"
|
"github.com/metacubex/mihomo/adapter/outbound"
|
||||||
"github.com/metacubex/mihomo/listener/inbound"
|
"github.com/metacubex/mihomo/listener/inbound"
|
||||||
|
"github.com/metacubex/mihomo/transport/vless/encryption"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -87,6 +88,21 @@ func TestInboundVless_TLS(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInboundVless_Encryption(t *testing.T) {
|
||||||
|
seedBase64, pubBase64, err := encryption.GenMLKEM768("")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inboundOptions := inbound.VlessOption{
|
||||||
|
Decryption: "10min-mlkem768seed-" + seedBase64,
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VlessOption{
|
||||||
|
Encryption: "8min-mlkem768client-" + pubBase64,
|
||||||
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
func TestInboundVless_Wss1(t *testing.T) {
|
func TestInboundVless_Wss1(t *testing.T) {
|
||||||
inboundOptions := inbound.VlessOption{
|
inboundOptions := inbound.VlessOption{
|
||||||
Certificate: tlsCertificate,
|
Certificate: tlsCertificate,
|
||||||
|
|||||||
@ -2,11 +2,15 @@ package sing_vless
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/adapter/inbound"
|
"github.com/metacubex/mihomo/adapter/inbound"
|
||||||
@ -19,6 +23,7 @@ import (
|
|||||||
"github.com/metacubex/mihomo/listener/sing"
|
"github.com/metacubex/mihomo/listener/sing"
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
"github.com/metacubex/mihomo/transport/gun"
|
"github.com/metacubex/mihomo/transport/gun"
|
||||||
|
"github.com/metacubex/mihomo/transport/vless/encryption"
|
||||||
mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
|
mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
|
||||||
|
|
||||||
"github.com/metacubex/sing-vmess/vless"
|
"github.com/metacubex/sing-vmess/vless"
|
||||||
@ -45,10 +50,11 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
closed bool
|
closed bool
|
||||||
config LC.VlessServer
|
config LC.VlessServer
|
||||||
listeners []net.Listener
|
listeners []net.Listener
|
||||||
service *vless.Service[string]
|
service *vless.Service[string]
|
||||||
|
decryption *encryption.ServerInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition) (sl *Listener, err error) {
|
func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition) (sl *Listener, err error) {
|
||||||
@ -80,7 +86,34 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||||||
return it.Flow
|
return it.Flow
|
||||||
}))
|
}))
|
||||||
|
|
||||||
sl = &Listener{false, config, nil, service}
|
sl = &Listener{config: config, service: service}
|
||||||
|
|
||||||
|
if s := strings.Split(config.Decryption, "-mlkem768seed-"); len(s) == 2 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
i, err := strconv.Atoi(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
|
||||||
|
}
|
||||||
|
minutes = uint32(i)
|
||||||
|
}
|
||||||
|
b, err := base64.RawURLEncoding.DecodeString(s[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
|
||||||
|
}
|
||||||
|
if len(b) == 64 {
|
||||||
|
sl.decryption = &encryption.ServerInstance{}
|
||||||
|
if err = sl.decryption.Init(b, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tlsConfig := &tlsC.Config{}
|
tlsConfig := &tlsC.Config{}
|
||||||
var realityBuilder *reality.Builder
|
var realityBuilder *reality.Builder
|
||||||
@ -149,8 +182,8 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||||||
} else {
|
} else {
|
||||||
l = tlsC.NewListener(l, tlsConfig)
|
l = tlsC.NewListener(l, tlsConfig)
|
||||||
}
|
}
|
||||||
} else {
|
} else if sl.decryption == nil {
|
||||||
return nil, errors.New("disallow using Vless without both certificates/reality config")
|
return nil, errors.New("disallow using Vless without any certificates/reality/decryption config")
|
||||||
}
|
}
|
||||||
sl.listeners = append(sl.listeners, l)
|
sl.listeners = append(sl.listeners, l)
|
||||||
|
|
||||||
@ -201,6 +234,13 @@ func (l *Listener) AddrList() (addrList []net.Addr) {
|
|||||||
|
|
||||||
func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) {
|
func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) {
|
||||||
ctx := sing.WithAdditions(context.TODO(), additions...)
|
ctx := sing.WithAdditions(context.TODO(), additions...)
|
||||||
|
if l.decryption != nil {
|
||||||
|
var err error
|
||||||
|
conn, err = l.decryption.Handshake(conn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
err := l.service.NewConnection(ctx, conn, metadata.Metadata{
|
err := l.service.NewConnection(ctx, conn, metadata.Metadata{
|
||||||
Protocol: "vless",
|
Protocol: "vless",
|
||||||
Source: metadata.SocksaddrFromNet(conn.RemoteAddr()),
|
Source: metadata.SocksaddrFromNet(conn.RemoteAddr()),
|
||||||
|
|||||||
238
transport/vless/encryption/client.go
Normal file
238
transport/vless/encryption/client.go
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
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() {
|
||||||
|
if !HasAESGCMHardwareSupport {
|
||||||
|
ClientCipher = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientInstance struct {
|
||||||
|
sync.RWMutex
|
||||||
|
eKeyNfs *mlkem.EncapsulationKey768
|
||||||
|
minutes time.Duration
|
||||||
|
expire time.Time
|
||||||
|
baseKey []byte
|
||||||
|
reuse []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientConn struct {
|
||||||
|
net.Conn
|
||||||
|
instance *ClientInstance
|
||||||
|
baseKey []byte
|
||||||
|
reuse []byte
|
||||||
|
random []byte
|
||||||
|
aead cipher.AEAD
|
||||||
|
nonce []byte
|
||||||
|
peerAead cipher.AEAD
|
||||||
|
peerNonce []byte
|
||||||
|
peerCache []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ClientInstance) Init(eKeyNfsData []byte, minutes time.Duration) (err error) {
|
||||||
|
i.eKeyNfs, err = mlkem.NewEncapsulationKey768(eKeyNfsData)
|
||||||
|
i.minutes = minutes
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
|
||||||
|
if i.eKeyNfs == nil {
|
||||||
|
return nil, errors.New("uninitialized")
|
||||||
|
}
|
||||||
|
c := &ClientConn{Conn: conn}
|
||||||
|
|
||||||
|
if i.minutes > 0 {
|
||||||
|
i.RLock()
|
||||||
|
if time.Now().Before(i.expire) {
|
||||||
|
c.instance = i
|
||||||
|
c.baseKey = i.baseKey
|
||||||
|
c.reuse = i.reuse
|
||||||
|
i.RUnlock()
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
i.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
nfsKey, encapsulatedNfsKey := i.eKeyNfs.Encapsulate()
|
||||||
|
seed := make([]byte, 64)
|
||||||
|
rand.Read(seed)
|
||||||
|
dKeyPfs, _ := mlkem.NewDecapsulationKey768(seed)
|
||||||
|
eKeyPfs := dKeyPfs.EncapsulationKey().Bytes()
|
||||||
|
padding := randBetween(100, 1000)
|
||||||
|
|
||||||
|
clientHello := make([]byte, 1088+1184+1+5+padding)
|
||||||
|
copy(clientHello, encapsulatedNfsKey)
|
||||||
|
copy(clientHello[1088:], eKeyPfs)
|
||||||
|
clientHello[2272] = ClientCipher
|
||||||
|
encodeHeader(clientHello[2273:], int(padding))
|
||||||
|
|
||||||
|
if _, err := c.Conn.Write(clientHello); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// we can send more padding if needed
|
||||||
|
|
||||||
|
peerServerHello := make([]byte, 1088+21)
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerServerHello); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
encapsulatedPfsKey := peerServerHello[:1088]
|
||||||
|
c.reuse = peerServerHello[1088:]
|
||||||
|
|
||||||
|
pfsKey, err := dKeyPfs.Decapsulate(encapsulatedPfsKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.baseKey = append(nfsKey, pfsKey...)
|
||||||
|
|
||||||
|
authKey := make([]byte, 32)
|
||||||
|
hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfs).Read(authKey)
|
||||||
|
nonce := make([]byte, 12)
|
||||||
|
VLESS, _ := newAead(ClientCipher, authKey).Open(nil, nonce, c.reuse, encapsulatedPfsKey)
|
||||||
|
if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more message
|
||||||
|
return nil, errors.New("invalid server")
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.minutes > 0 {
|
||||||
|
i.Lock()
|
||||||
|
i.expire = time.Now().Add(i.minutes)
|
||||||
|
i.baseKey = c.baseKey
|
||||||
|
i.reuse = c.reuse
|
||||||
|
i.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) Write(b []byte) (int, error) {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
var data []byte
|
||||||
|
if c.aead == nil {
|
||||||
|
c.random = make([]byte, 32)
|
||||||
|
rand.Read(c.random)
|
||||||
|
key := make([]byte, 32)
|
||||||
|
hkdf.New(sha256.New, c.baseKey, c.random, c.reuse).Read(key)
|
||||||
|
c.aead = newAead(ClientCipher, key)
|
||||||
|
c.nonce = make([]byte, 12)
|
||||||
|
|
||||||
|
data = make([]byte, 21+32+5+len(b)+16)
|
||||||
|
copy(data, c.reuse)
|
||||||
|
copy(data[21:], c.random)
|
||||||
|
encodeHeader(data[53:], len(b)+16)
|
||||||
|
c.aead.Seal(data[:58], c.nonce, b, data[53:58])
|
||||||
|
} else {
|
||||||
|
data = make([]byte, 5+len(b)+16)
|
||||||
|
encodeHeader(data, len(b)+16)
|
||||||
|
c.aead.Seal(data[:5], c.nonce, b, data[:5])
|
||||||
|
}
|
||||||
|
increaseNonce(c.nonce)
|
||||||
|
if _, err := c.Conn.Write(data); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
peerHeader := make([]byte, 5)
|
||||||
|
if c.peerAead == nil {
|
||||||
|
if c.instance == nil {
|
||||||
|
for {
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
peerPadding, _ := decodeHeader(peerHeader)
|
||||||
|
if peerPadding == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
peerRandom := make([]byte, 32)
|
||||||
|
copy(peerRandom, peerHeader)
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerRandom[5:]); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if c.random == nil {
|
||||||
|
return 0, errors.New("can not Read() first")
|
||||||
|
}
|
||||||
|
peerKey := make([]byte, 32)
|
||||||
|
hkdf.New(sha256.New, c.baseKey, peerRandom, c.random).Read(peerKey)
|
||||||
|
c.peerAead = newAead(ClientCipher, peerKey)
|
||||||
|
c.peerNonce = make([]byte, 12)
|
||||||
|
}
|
||||||
|
if len(c.peerCache) != 0 {
|
||||||
|
n := copy(b, c.peerCache)
|
||||||
|
c.peerCache = c.peerCache[n:]
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
peerLength, err := decodeHeader(peerHeader) // 17~17000
|
||||||
|
if err != nil {
|
||||||
|
if c.instance != nil {
|
||||||
|
c.instance.Lock()
|
||||||
|
if bytes.Equal(c.reuse, c.instance.reuse) {
|
||||||
|
c.instance.expire = time.Now() // expired
|
||||||
|
}
|
||||||
|
c.instance.Unlock()
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
peerData := make([]byte, peerLength)
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerData); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
dst := peerData[:peerLength-16]
|
||||||
|
if len(dst) <= len(b) {
|
||||||
|
dst = b[:len(dst)] // max=8192 is recommended for peer
|
||||||
|
}
|
||||||
|
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
|
||||||
|
increaseNonce(c.peerNonce)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if len(dst) > len(b) {
|
||||||
|
c.peerCache = dst[copy(b, dst):]
|
||||||
|
dst = b // for len(dst)
|
||||||
|
}
|
||||||
|
return len(dst), nil
|
||||||
|
}
|
||||||
65
transport/vless/encryption/common.go
Normal file
65
transport/vless/encryption/common.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package encryption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
|
)
|
||||||
|
|
||||||
|
func encodeHeader(b []byte, l int) {
|
||||||
|
b[0] = 23
|
||||||
|
b[1] = 3
|
||||||
|
b[2] = 3
|
||||||
|
b[3] = byte(l >> 8)
|
||||||
|
b[4] = byte(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeHeader(b []byte) (int, error) {
|
||||||
|
if b[0] == 23 && b[1] == 3 && b[2] == 3 {
|
||||||
|
l := int(b[3])<<8 | int(b[4])
|
||||||
|
if l < 17 || l > 17000 { // TODO
|
||||||
|
return 0, errors.New("invalid length in record's header: " + strconv.Itoa(l))
|
||||||
|
}
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
return 0, errors.New("invalid record's header")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAead(c byte, k []byte) cipher.AEAD {
|
||||||
|
switch c {
|
||||||
|
case 0:
|
||||||
|
if block, err := aes.NewCipher(k); err == nil {
|
||||||
|
aead, _ := cipher.NewGCM(block)
|
||||||
|
return aead
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
aead, _ := chacha20poly1305.New(k)
|
||||||
|
return aead
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func increaseNonce(nonce []byte) {
|
||||||
|
for i := 0; i < 12; i++ {
|
||||||
|
nonce[11-i]++
|
||||||
|
if nonce[11-i] != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i == 11 {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func randBetween(from int64, to int64) int64 {
|
||||||
|
if from == to {
|
||||||
|
return from
|
||||||
|
}
|
||||||
|
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
|
||||||
|
return from + bigInt.Int64()
|
||||||
|
}
|
||||||
3
transport/vless/encryption/doc.go
Normal file
3
transport/vless/encryption/doc.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// Package encryption copy and modify from xray-core
|
||||||
|
// https://github.com/XTLS/Xray-core/commit/f61c14e9c63dc41a8a09135db3aea337974f3f37
|
||||||
|
package encryption
|
||||||
32
transport/vless/encryption/key.go
Normal file
32
transport/vless/encryption/key.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package encryption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/metacubex/utls/mlkem"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenMLKEM768(seedStr string) (seedBase64, pubBase64 string, err error) {
|
||||||
|
var seed [64]byte
|
||||||
|
if len(seedStr) > 0 {
|
||||||
|
s, _ := base64.RawURLEncoding.DecodeString(seedStr)
|
||||||
|
if len(s) != 64 {
|
||||||
|
err = fmt.Errorf("invalid length of ML-KEM-768 seed: %s", seedStr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seed = [64]byte(s)
|
||||||
|
} else {
|
||||||
|
_, err = rand.Read(seed[:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
key, _ := mlkem.NewDecapsulationKey768(seed[:])
|
||||||
|
pub := key.EncapsulationKey()
|
||||||
|
seedBase64 = base64.RawURLEncoding.EncodeToString(seed[:])
|
||||||
|
pubBase64 = base64.RawURLEncoding.EncodeToString(pub.Bytes())
|
||||||
|
return
|
||||||
|
}
|
||||||
250
transport/vless/encryption/server.go
Normal file
250
transport/vless/encryption/server.go
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
package encryption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/metacubex/utls/mlkem"
|
||||||
|
"golang.org/x/crypto/hkdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerSession struct {
|
||||||
|
expire time.Time
|
||||||
|
cipher byte
|
||||||
|
baseKey []byte
|
||||||
|
randoms sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerInstance struct {
|
||||||
|
sync.RWMutex
|
||||||
|
dKeyNfs *mlkem.DecapsulationKey768
|
||||||
|
minutes time.Duration
|
||||||
|
sessions map[[21]byte]*ServerSession
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerConn struct {
|
||||||
|
net.Conn
|
||||||
|
cipher byte
|
||||||
|
baseKey []byte
|
||||||
|
reuse []byte
|
||||||
|
peerRandom []byte
|
||||||
|
peerAead cipher.AEAD
|
||||||
|
peerNonce []byte
|
||||||
|
peerCache []byte
|
||||||
|
aead cipher.AEAD
|
||||||
|
nonce []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ServerInstance) Init(dKeyNfsData []byte, minutes time.Duration) (err error) {
|
||||||
|
i.dKeyNfs, err = mlkem.NewDecapsulationKey768(dKeyNfsData)
|
||||||
|
if minutes > 0 {
|
||||||
|
i.minutes = minutes
|
||||||
|
i.sessions = make(map[[21]byte]*ServerSession)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Minute)
|
||||||
|
now := time.Now()
|
||||||
|
i.Lock()
|
||||||
|
for index, session := range i.sessions {
|
||||||
|
if now.After(session.expire) {
|
||||||
|
delete(i.sessions, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) {
|
||||||
|
if i.dKeyNfs == nil {
|
||||||
|
return nil, errors.New("uninitialized")
|
||||||
|
}
|
||||||
|
c := &ServerConn{Conn: conn}
|
||||||
|
|
||||||
|
peerReuseHello := make([]byte, 21+32)
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerReuseHello); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if i.minutes > 0 {
|
||||||
|
i.RLock()
|
||||||
|
s := i.sessions[[21]byte(peerReuseHello)]
|
||||||
|
i.RUnlock()
|
||||||
|
if s != nil {
|
||||||
|
if _, replay := s.randoms.LoadOrStore([32]byte(peerReuseHello[21:]), true); !replay {
|
||||||
|
c.cipher = s.cipher
|
||||||
|
c.baseKey = s.baseKey
|
||||||
|
c.reuse = peerReuseHello[:21]
|
||||||
|
c.peerRandom = peerReuseHello[21:]
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
peerHeader := make([]byte, 5)
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if l, _ := decodeHeader(peerHeader); l != 0 {
|
||||||
|
c.Conn.Write(make([]byte, randBetween(100, 1000))) // make client do new handshake
|
||||||
|
return nil, errors.New("invalid reuse")
|
||||||
|
}
|
||||||
|
|
||||||
|
peerClientHello := make([]byte, 1088+1184+1)
|
||||||
|
copy(peerClientHello, peerReuseHello)
|
||||||
|
copy(peerClientHello[53:], peerHeader)
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerClientHello[58:]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
encapsulatedNfsKey := peerClientHello[:1088]
|
||||||
|
eKeyPfsData := peerClientHello[1088:2272]
|
||||||
|
c.cipher = peerClientHello[2272]
|
||||||
|
if c.cipher != 0 && c.cipher != 1 {
|
||||||
|
return nil, errors.New("invalid cipher")
|
||||||
|
}
|
||||||
|
|
||||||
|
nfsKey, err := i.dKeyNfs.Decapsulate(encapsulatedNfsKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
eKeyPfs, err := mlkem.NewEncapsulationKey768(eKeyPfsData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pfsKey, encapsulatedPfsKey := eKeyPfs.Encapsulate()
|
||||||
|
c.baseKey = append(nfsKey, pfsKey...)
|
||||||
|
|
||||||
|
authKey := make([]byte, 32)
|
||||||
|
hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfsData).Read(authKey)
|
||||||
|
nonce := make([]byte, 12)
|
||||||
|
c.reuse = newAead(c.cipher, authKey).Seal(nil, nonce, []byte("VLESS"), encapsulatedPfsKey)
|
||||||
|
|
||||||
|
padding := randBetween(100, 1000)
|
||||||
|
|
||||||
|
serverHello := make([]byte, 1088+21+5+padding)
|
||||||
|
copy(serverHello, encapsulatedPfsKey)
|
||||||
|
copy(serverHello[1088:], c.reuse)
|
||||||
|
encodeHeader(serverHello[1109:], int(padding))
|
||||||
|
|
||||||
|
if _, err := c.Conn.Write(serverHello); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.minutes > 0 {
|
||||||
|
i.Lock()
|
||||||
|
i.sessions[[21]byte(c.reuse)] = &ServerSession{
|
||||||
|
expire: time.Now().Add(i.minutes),
|
||||||
|
cipher: c.cipher,
|
||||||
|
baseKey: c.baseKey,
|
||||||
|
}
|
||||||
|
i.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerConn) Read(b []byte) (int, error) {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
peerHeader := make([]byte, 5)
|
||||||
|
if c.peerAead == nil {
|
||||||
|
if c.peerRandom == nil {
|
||||||
|
for {
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
peerPadding, _ := decodeHeader(peerHeader)
|
||||||
|
if peerPadding == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
peerIndex := make([]byte, 21)
|
||||||
|
copy(peerIndex, peerHeader)
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerIndex[5:]); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if !bytes.Equal(peerIndex, c.reuse) {
|
||||||
|
return 0, errors.New("naughty boy")
|
||||||
|
}
|
||||||
|
c.peerRandom = make([]byte, 32)
|
||||||
|
if _, err := io.ReadFull(c.Conn, c.peerRandom); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
peerKey := make([]byte, 32)
|
||||||
|
hkdf.New(sha256.New, c.baseKey, c.peerRandom, c.reuse).Read(peerKey)
|
||||||
|
c.peerAead = newAead(c.cipher, peerKey)
|
||||||
|
c.peerNonce = make([]byte, 12)
|
||||||
|
}
|
||||||
|
if len(c.peerCache) != 0 {
|
||||||
|
n := copy(b, c.peerCache)
|
||||||
|
c.peerCache = c.peerCache[n:]
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
peerLength, err := decodeHeader(peerHeader) // 17~17000
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
peerData := make([]byte, peerLength)
|
||||||
|
if _, err := io.ReadFull(c.Conn, peerData); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
dst := peerData[:peerLength-16]
|
||||||
|
if len(dst) <= len(b) {
|
||||||
|
dst = b[:len(dst)] // max=8192 is recommended for peer
|
||||||
|
}
|
||||||
|
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
|
||||||
|
increaseNonce(c.peerNonce)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.New("error")
|
||||||
|
}
|
||||||
|
if len(dst) > len(b) {
|
||||||
|
c.peerCache = dst[copy(b, dst):]
|
||||||
|
dst = b // for len(dst)
|
||||||
|
}
|
||||||
|
return len(dst), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerConn) Write(b []byte) (int, error) { // after first Read()
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
var data []byte
|
||||||
|
if c.aead == nil {
|
||||||
|
if c.peerRandom == nil {
|
||||||
|
return 0, errors.New("can not Write() first")
|
||||||
|
}
|
||||||
|
data = make([]byte, 32+5+len(b)+16)
|
||||||
|
rand.Read(data[:32])
|
||||||
|
key := make([]byte, 32)
|
||||||
|
hkdf.New(sha256.New, c.baseKey, data[:32], c.peerRandom).Read(key)
|
||||||
|
c.aead = newAead(c.cipher, key)
|
||||||
|
c.nonce = make([]byte, 12)
|
||||||
|
encodeHeader(data[32:], len(b)+16)
|
||||||
|
c.aead.Seal(data[:37], c.nonce, b, data[32:37])
|
||||||
|
} else {
|
||||||
|
data = make([]byte, 5+len(b)+16)
|
||||||
|
encodeHeader(data, len(b)+16)
|
||||||
|
c.aead.Seal(data[:5], c.nonce, b, data[:5])
|
||||||
|
}
|
||||||
|
increaseNonce(c.nonce)
|
||||||
|
if _, err := c.Conn.Write(data); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user