mirror of
https://github.com/nadoo/glider.git
synced 2025-02-23 01:15:41 +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/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash/fnv"
|
||||
@ -17,6 +18,7 @@ import (
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pool"
|
||||
)
|
||||
|
||||
@ -49,6 +51,7 @@ type Client struct {
|
||||
users []*User
|
||||
count int
|
||||
opt byte
|
||||
aead bool
|
||||
security byte
|
||||
}
|
||||
|
||||
@ -56,6 +59,7 @@ type Client struct {
|
||||
type Conn struct {
|
||||
user *User
|
||||
opt byte
|
||||
aead bool
|
||||
security byte
|
||||
|
||||
atyp Atyp
|
||||
@ -74,7 +78,7 @@ type Conn struct {
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -87,6 +91,7 @@ func NewClient(uuidStr, security string, alterID int) (*Client, error) {
|
||||
c.count = len(c.users)
|
||||
|
||||
c.opt = OptChunkStream
|
||||
c.aead = aead
|
||||
|
||||
security = strings.ToLower(security)
|
||||
switch security {
|
||||
@ -114,7 +119,7 @@ func NewClient(uuidStr, security string, alterID int) (*Client, error) {
|
||||
// NewConn returns a new vmess conn.
|
||||
func (c *Client) NewConn(rc net.Conn, target string, cmd CmdType) (*Conn, error) {
|
||||
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
|
||||
conn.atyp, conn.addr, conn.port, err = ParseAddr(target)
|
||||
@ -130,13 +135,20 @@ func (c *Client) NewConn(rc net.Conn, target string, cmd CmdType) (*Conn, error)
|
||||
|
||||
conn.reqRespV = byte(rand.Intn(1 << 8))
|
||||
|
||||
conn.respBodyIV = md5.Sum(conn.reqBodyIV[:])
|
||||
conn.respBodyKey = md5.Sum(conn.reqBodyKey[:])
|
||||
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.respBodyKey = md5.Sum(conn.reqBodyKey[:])
|
||||
|
||||
// Auth
|
||||
err = conn.Auth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// MD5 Auth
|
||||
err = conn.Auth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Request
|
||||
@ -183,13 +195,9 @@ func (c *Conn) Request(cmd CmdType) error {
|
||||
buf.WriteByte(byte(cmd)) // cmd
|
||||
|
||||
// target
|
||||
err := binary.Write(buf, binary.BigEndian, uint16(c.port)) // port
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf.WriteByte(byte(c.atyp)) // atyp
|
||||
buf.Write(c.addr) // addr
|
||||
binary.Write(buf, binary.BigEndian, uint16(c.port)) // port
|
||||
buf.WriteByte(byte(c.atyp)) // atyp
|
||||
buf.Write(c.addr) // addr
|
||||
|
||||
// padding
|
||||
if paddingLen > 0 {
|
||||
@ -201,11 +209,14 @@ func (c *Conn) Request(cmd CmdType) error {
|
||||
|
||||
// F
|
||||
fnv1a := fnv.New32a()
|
||||
_, err = fnv1a.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
fnv1a.Write(buf.Bytes())
|
||||
buf.Write(fnv1a.Sum(nil))
|
||||
|
||||
if c.aead {
|
||||
encHeader := sealVMessAEADHeader(c.user.CmdKey, buf.Bytes())
|
||||
_, err := c.Conn.Write(encHeader)
|
||||
return err
|
||||
}
|
||||
buf.Write(fnv1a.Sum(nil))
|
||||
|
||||
block, err := aes.NewCipher(c.user.CmdKey[:])
|
||||
if err != nil {
|
||||
@ -222,29 +233,49 @@ func (c *Conn) Request(cmd CmdType) error {
|
||||
|
||||
// DecodeRespHeader decodes response header.
|
||||
func (c *Conn) DecodeRespHeader() error {
|
||||
if c.aead {
|
||||
buf, err := openVMessAEADHeader(c.respBodyKey, c.respBodyIV, c.Conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(buf) < 4 {
|
||||
return errors.New("unexpected buffer length")
|
||||
}
|
||||
|
||||
if buf[0] != c.reqRespV {
|
||||
return errors.New("unexpected response header")
|
||||
}
|
||||
|
||||
// TODO: Dynamic port support
|
||||
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[:])
|
||||
|
||||
b := pool.GetBuffer(4)
|
||||
defer pool.PutBuffer(b)
|
||||
buf := pool.GetBuffer(4)
|
||||
defer pool.PutBuffer(buf)
|
||||
|
||||
_, err = io.ReadFull(c.Conn, b)
|
||||
_, err = io.ReadFull(c.Conn, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stream.XORKeyStream(buf, buf)
|
||||
|
||||
stream.XORKeyStream(b, b)
|
||||
|
||||
if b[0] != c.reqRespV {
|
||||
if buf[0] != c.reqRespV {
|
||||
return errors.New("unexpected response header")
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
@ -290,6 +321,7 @@ func (c *Conn) Read(b []byte) (n int, err error) {
|
||||
|
||||
err = c.DecodeRespHeader()
|
||||
if err != nil {
|
||||
log.F("[vmess] DecodeRespHeader error: %s", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ type VMess struct {
|
||||
addr string
|
||||
|
||||
uuid string
|
||||
aead bool
|
||||
alterID int
|
||||
security string
|
||||
|
||||
@ -45,7 +46,7 @@ func NewVMess(s string, d proxy.Dialer) (*VMess, error) {
|
||||
query := u.Query()
|
||||
aid := query.Get("alterID")
|
||||
if aid == "" {
|
||||
aid = "1"
|
||||
aid = "0"
|
||||
}
|
||||
|
||||
alterID, err := strconv.ParseUint(aid, 10, 32)
|
||||
@ -54,7 +55,8 @@ func NewVMess(s string, d proxy.Dialer) (*VMess, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := NewClient(uuid, security, int(alterID))
|
||||
aead := alterID == 0
|
||||
client, err := NewClient(uuid, security, int(alterID), aead)
|
||||
if err != nil {
|
||||
log.F("create vmess client err: %s", err)
|
||||
return nil, err
|
||||
|
Loading…
Reference in New Issue
Block a user