mirror of
https://github.com/nadoo/glider.git
synced 2025-02-23 17:35:40 +08:00
vmess: support aead auth
This commit is contained in:
parent
7486373821
commit
8729908660
136
proxy/vmess/auth.go
Normal file
136
proxy/vmess/auth.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package vmess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
"hash"
|
||||||
|
"hash/crc32"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://github.com/v2fly/v2ray-core/tree/327cbdc5cd10ee1aec6ef4dce1af027b3e277f33/proxy/vmess/aead
|
||||||
|
const (
|
||||||
|
kdfSaltConstAuthIDEncryptionKey = "AES Auth ID Encryption"
|
||||||
|
kdfSaltConstAEADRespHeaderLenKey = "AEAD Resp Header Len Key"
|
||||||
|
kdfSaltConstAEADRespHeaderLenIV = "AEAD Resp Header Len IV"
|
||||||
|
kdfSaltConstAEADRespHeaderPayloadKey = "AEAD Resp Header Key"
|
||||||
|
kdfSaltConstAEADRespHeaderPayloadIV = "AEAD Resp Header IV"
|
||||||
|
kdfSaltConstVMessAEADKDF = "VMess AEAD KDF"
|
||||||
|
kdfSaltConstVMessHeaderPayloadAEADKey = "VMess Header AEAD Key"
|
||||||
|
kdfSaltConstVMessHeaderPayloadAEADIV = "VMess Header AEAD Nonce"
|
||||||
|
kdfSaltConstVMessHeaderPayloadLengthAEADKey = "VMess Header AEAD Key_Length"
|
||||||
|
kdfSaltConstVMessHeaderPayloadLengthAEADIV = "VMess Header AEAD Nonce_Length"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sealVMessAEADHeader(key [16]byte, data []byte) []byte {
|
||||||
|
generatedAuthID := createAuthID(key[:], time.Now().Unix())
|
||||||
|
connectionNonce := make([]byte, 8)
|
||||||
|
rand.Read(connectionNonce)
|
||||||
|
|
||||||
|
aeadPayloadLengthSerializedByte := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(aeadPayloadLengthSerializedByte, uint16(len(data)))
|
||||||
|
|
||||||
|
var payloadHeaderLengthAEADEncrypted []byte
|
||||||
|
|
||||||
|
{
|
||||||
|
payloadHeaderLengthAEADKey := kdf(key[:], kdfSaltConstVMessHeaderPayloadLengthAEADKey, string(generatedAuthID[:]), string(connectionNonce))[:16]
|
||||||
|
payloadHeaderLengthAEADNonce := kdf(key[:], kdfSaltConstVMessHeaderPayloadLengthAEADIV, string(generatedAuthID[:]), string(connectionNonce))[:12]
|
||||||
|
payloadHeaderLengthAEADAESBlock, _ := aes.NewCipher(payloadHeaderLengthAEADKey)
|
||||||
|
payloadHeaderAEAD, _ := cipher.NewGCM(payloadHeaderLengthAEADAESBlock)
|
||||||
|
payloadHeaderLengthAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderLengthAEADNonce, aeadPayloadLengthSerializedByte, generatedAuthID[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
var payloadHeaderAEADEncrypted []byte
|
||||||
|
|
||||||
|
{
|
||||||
|
payloadHeaderAEADKey := kdf(key[:], kdfSaltConstVMessHeaderPayloadAEADKey, string(generatedAuthID[:]), string(connectionNonce))[:16]
|
||||||
|
payloadHeaderAEADNonce := kdf(key[:], kdfSaltConstVMessHeaderPayloadAEADIV, string(generatedAuthID[:]), string(connectionNonce))[:12]
|
||||||
|
payloadHeaderAEADAESBlock, _ := aes.NewCipher(payloadHeaderAEADKey)
|
||||||
|
payloadHeaderAEAD, _ := cipher.NewGCM(payloadHeaderAEADAESBlock)
|
||||||
|
payloadHeaderAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderAEADNonce, data, generatedAuthID[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputBuffer = &bytes.Buffer{}
|
||||||
|
|
||||||
|
outputBuffer.Write(generatedAuthID[:])
|
||||||
|
outputBuffer.Write(payloadHeaderLengthAEADEncrypted)
|
||||||
|
outputBuffer.Write(connectionNonce)
|
||||||
|
outputBuffer.Write(payloadHeaderAEADEncrypted)
|
||||||
|
|
||||||
|
return outputBuffer.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func openVMessAEADHeader(key [16]byte, iv [16]byte, r io.Reader) ([]byte, error) {
|
||||||
|
aeadResponseHeaderLengthEncryptionKey := kdf(key[:], kdfSaltConstAEADRespHeaderLenKey)[:16]
|
||||||
|
aeadResponseHeaderLengthEncryptionIV := kdf(iv[:], kdfSaltConstAEADRespHeaderLenIV)[:12]
|
||||||
|
|
||||||
|
aeadResponseHeaderLengthEncryptionKeyAESBlock, _ := aes.NewCipher(aeadResponseHeaderLengthEncryptionKey)
|
||||||
|
aeadResponseHeaderLengthEncryptionAEAD, _ := cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock)
|
||||||
|
|
||||||
|
aeadEncryptedResponseHeaderLength := make([]byte, 18)
|
||||||
|
if _, err := io.ReadFull(r, aeadEncryptedResponseHeaderLength); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedResponseHeaderLengthBinaryBuffer, err := aeadResponseHeaderLengthEncryptionAEAD.Open(nil, aeadResponseHeaderLengthEncryptionIV, aeadEncryptedResponseHeaderLength[:], nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedResponseHeaderLength := binary.BigEndian.Uint16(decryptedResponseHeaderLengthBinaryBuffer)
|
||||||
|
aeadResponseHeaderPayloadEncryptionKey := kdf(key[:], kdfSaltConstAEADRespHeaderPayloadKey)[:16]
|
||||||
|
aeadResponseHeaderPayloadEncryptionIV := kdf(iv[:], kdfSaltConstAEADRespHeaderPayloadIV)[:12]
|
||||||
|
aeadResponseHeaderPayloadEncryptionKeyAESBlock, _ := aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey)
|
||||||
|
aeadResponseHeaderPayloadEncryptionAEAD, _ := cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock)
|
||||||
|
|
||||||
|
encryptedResponseHeaderBuffer := make([]byte, decryptedResponseHeaderLength+16)
|
||||||
|
if _, err := io.ReadFull(r, encryptedResponseHeaderBuffer); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return aeadResponseHeaderPayloadEncryptionAEAD.Open(nil, aeadResponseHeaderPayloadEncryptionIV, encryptedResponseHeaderBuffer, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func kdf(key []byte, path ...string) []byte {
|
||||||
|
hmacCreator := &hMacCreator{value: []byte(kdfSaltConstVMessAEADKDF)}
|
||||||
|
for _, v := range path {
|
||||||
|
hmacCreator = &hMacCreator{value: []byte(v), parent: hmacCreator}
|
||||||
|
}
|
||||||
|
hmacf := hmacCreator.Create()
|
||||||
|
hmacf.Write(key)
|
||||||
|
return hmacf.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
type hMacCreator struct {
|
||||||
|
parent *hMacCreator
|
||||||
|
value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hMacCreator) Create() hash.Hash {
|
||||||
|
if h.parent == nil {
|
||||||
|
return hmac.New(sha256.New, h.value)
|
||||||
|
}
|
||||||
|
return hmac.New(h.parent.Create, h.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createAuthID(cmdKey []byte, time int64) [16]byte {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
binary.Write(buf, binary.BigEndian, time)
|
||||||
|
|
||||||
|
random := make([]byte, 4)
|
||||||
|
rand.Read(random)
|
||||||
|
buf.Write(random)
|
||||||
|
zero := crc32.ChecksumIEEE(buf.Bytes())
|
||||||
|
binary.Write(buf, binary.BigEndian, zero)
|
||||||
|
|
||||||
|
aesBlock, _ := aes.NewCipher(kdf(cmdKey[:], kdfSaltConstAuthIDEncryptionKey)[:16])
|
||||||
|
var result [16]byte
|
||||||
|
aesBlock.Encrypt(result[:], buf.Bytes())
|
||||||
|
return result
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
@ -17,6 +18,7 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/log"
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -49,6 +51,7 @@ type Client struct {
|
|||||||
users []*User
|
users []*User
|
||||||
count int
|
count int
|
||||||
opt byte
|
opt byte
|
||||||
|
aead bool
|
||||||
security byte
|
security byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,6 +59,7 @@ type Client struct {
|
|||||||
type Conn struct {
|
type Conn struct {
|
||||||
user *User
|
user *User
|
||||||
opt byte
|
opt byte
|
||||||
|
aead bool
|
||||||
security byte
|
security byte
|
||||||
|
|
||||||
atyp Atyp
|
atyp Atyp
|
||||||
@ -74,7 +78,7 @@ type Conn struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewClient returns a new vmess client.
|
// NewClient returns a new vmess client.
|
||||||
func NewClient(uuidStr, security string, alterID int) (*Client, error) {
|
func NewClient(uuidStr, security string, alterID int, aead bool) (*Client, error) {
|
||||||
uuid, err := StrToUUID(uuidStr)
|
uuid, err := StrToUUID(uuidStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -87,6 +91,7 @@ func NewClient(uuidStr, security string, alterID int) (*Client, error) {
|
|||||||
c.count = len(c.users)
|
c.count = len(c.users)
|
||||||
|
|
||||||
c.opt = OptChunkStream
|
c.opt = OptChunkStream
|
||||||
|
c.aead = aead
|
||||||
|
|
||||||
security = strings.ToLower(security)
|
security = strings.ToLower(security)
|
||||||
switch security {
|
switch security {
|
||||||
@ -114,7 +119,7 @@ func NewClient(uuidStr, security string, alterID int) (*Client, error) {
|
|||||||
// NewConn returns a new vmess conn.
|
// NewConn returns a new vmess conn.
|
||||||
func (c *Client) NewConn(rc net.Conn, target string, cmd CmdType) (*Conn, error) {
|
func (c *Client) NewConn(rc net.Conn, target string, cmd CmdType) (*Conn, error) {
|
||||||
r := rand.Intn(c.count)
|
r := rand.Intn(c.count)
|
||||||
conn := &Conn{user: c.users[r], opt: c.opt, security: c.security, Conn: rc}
|
conn := &Conn{user: c.users[r], opt: c.opt, aead: c.aead, security: c.security, Conn: rc}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
conn.atyp, conn.addr, conn.port, err = ParseAddr(target)
|
conn.atyp, conn.addr, conn.port, err = ParseAddr(target)
|
||||||
@ -130,14 +135,21 @@ func (c *Client) NewConn(rc net.Conn, target string, cmd CmdType) (*Conn, error)
|
|||||||
|
|
||||||
conn.reqRespV = byte(rand.Intn(1 << 8))
|
conn.reqRespV = byte(rand.Intn(1 << 8))
|
||||||
|
|
||||||
|
if conn.aead {
|
||||||
|
bodyIV := sha256.Sum256(conn.reqBodyIV[:])
|
||||||
|
bodyKey := sha256.Sum256(conn.reqBodyKey[:])
|
||||||
|
copy(conn.respBodyIV[:], bodyIV[:16])
|
||||||
|
copy(conn.respBodyKey[:], bodyKey[:16])
|
||||||
|
} else {
|
||||||
conn.respBodyIV = md5.Sum(conn.reqBodyIV[:])
|
conn.respBodyIV = md5.Sum(conn.reqBodyIV[:])
|
||||||
conn.respBodyKey = md5.Sum(conn.reqBodyKey[:])
|
conn.respBodyKey = md5.Sum(conn.reqBodyKey[:])
|
||||||
|
|
||||||
// Auth
|
// MD5 Auth
|
||||||
err = conn.Auth()
|
err = conn.Auth()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Request
|
// Request
|
||||||
err = conn.Request(cmd)
|
err = conn.Request(cmd)
|
||||||
@ -183,11 +195,7 @@ func (c *Conn) Request(cmd CmdType) error {
|
|||||||
buf.WriteByte(byte(cmd)) // cmd
|
buf.WriteByte(byte(cmd)) // cmd
|
||||||
|
|
||||||
// target
|
// target
|
||||||
err := binary.Write(buf, binary.BigEndian, uint16(c.port)) // port
|
binary.Write(buf, binary.BigEndian, uint16(c.port)) // port
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteByte(byte(c.atyp)) // atyp
|
buf.WriteByte(byte(c.atyp)) // atyp
|
||||||
buf.Write(c.addr) // addr
|
buf.Write(c.addr) // addr
|
||||||
|
|
||||||
@ -201,11 +209,14 @@ func (c *Conn) Request(cmd CmdType) error {
|
|||||||
|
|
||||||
// F
|
// F
|
||||||
fnv1a := fnv.New32a()
|
fnv1a := fnv.New32a()
|
||||||
_, err = fnv1a.Write(buf.Bytes())
|
fnv1a.Write(buf.Bytes())
|
||||||
if err != nil {
|
buf.Write(fnv1a.Sum(nil))
|
||||||
|
|
||||||
|
if c.aead {
|
||||||
|
encHeader := sealVMessAEADHeader(c.user.CmdKey, buf.Bytes())
|
||||||
|
_, err := c.Conn.Write(encHeader)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
buf.Write(fnv1a.Sum(nil))
|
|
||||||
|
|
||||||
block, err := aes.NewCipher(c.user.CmdKey[:])
|
block, err := aes.NewCipher(c.user.CmdKey[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -222,29 +233,49 @@ func (c *Conn) Request(cmd CmdType) error {
|
|||||||
|
|
||||||
// DecodeRespHeader decodes response header.
|
// DecodeRespHeader decodes response header.
|
||||||
func (c *Conn) DecodeRespHeader() error {
|
func (c *Conn) DecodeRespHeader() error {
|
||||||
block, err := aes.NewCipher(c.respBodyKey[:])
|
if c.aead {
|
||||||
|
buf, err := openVMessAEADHeader(c.respBodyKey, c.respBodyIV, c.Conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stream := cipher.NewCFBDecrypter(block, c.respBodyIV[:])
|
if len(buf) < 4 {
|
||||||
|
return errors.New("unexpected buffer length")
|
||||||
b := pool.GetBuffer(4)
|
|
||||||
defer pool.PutBuffer(b)
|
|
||||||
|
|
||||||
_, err = io.ReadFull(c.Conn, b)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.XORKeyStream(b, b)
|
if buf[0] != c.reqRespV {
|
||||||
|
|
||||||
if b[0] != c.reqRespV {
|
|
||||||
return errors.New("unexpected response header")
|
return errors.New("unexpected response header")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Dynamic port support
|
// TODO: Dynamic port support
|
||||||
if b[2] != 0 {
|
if buf[2] != 0 {
|
||||||
|
// dataLen := int32(buf[3])
|
||||||
|
return errors.New("dynamic port is not supported now")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(c.respBodyKey[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stream := cipher.NewCFBDecrypter(block, c.respBodyIV[:])
|
||||||
|
|
||||||
|
buf := pool.GetBuffer(4)
|
||||||
|
defer pool.PutBuffer(buf)
|
||||||
|
|
||||||
|
_, err = io.ReadFull(c.Conn, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stream.XORKeyStream(buf, buf)
|
||||||
|
|
||||||
|
if buf[0] != c.reqRespV {
|
||||||
|
return errors.New("unexpected response header")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Dynamic port support
|
||||||
|
if buf[2] != 0 {
|
||||||
// dataLen := int32(buf[3])
|
// dataLen := int32(buf[3])
|
||||||
return errors.New("dynamic port is not supported now")
|
return errors.New("dynamic port is not supported now")
|
||||||
}
|
}
|
||||||
@ -290,6 +321,7 @@ func (c *Conn) Read(b []byte) (n int, err error) {
|
|||||||
|
|
||||||
err = c.DecodeRespHeader()
|
err = c.DecodeRespHeader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.F("[vmess] DecodeRespHeader error: %s", err)
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ type VMess struct {
|
|||||||
addr string
|
addr string
|
||||||
|
|
||||||
uuid string
|
uuid string
|
||||||
|
aead bool
|
||||||
alterID int
|
alterID int
|
||||||
security string
|
security string
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ func NewVMess(s string, d proxy.Dialer) (*VMess, error) {
|
|||||||
query := u.Query()
|
query := u.Query()
|
||||||
aid := query.Get("alterID")
|
aid := query.Get("alterID")
|
||||||
if aid == "" {
|
if aid == "" {
|
||||||
aid = "1"
|
aid = "0"
|
||||||
}
|
}
|
||||||
|
|
||||||
alterID, err := strconv.ParseUint(aid, 10, 32)
|
alterID, err := strconv.ParseUint(aid, 10, 32)
|
||||||
@ -54,7 +55,8 @@ func NewVMess(s string, d proxy.Dialer) (*VMess, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := NewClient(uuid, security, int(alterID))
|
aead := alterID == 0
|
||||||
|
client, err := NewClient(uuid, security, int(alterID), aead)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("create vmess client err: %s", err)
|
log.F("create vmess client err: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
Loading…
Reference in New Issue
Block a user