vmess: support aead auth

This commit is contained in:
nadoo 2021-08-05 23:32:53 +08:00
parent 7486373821
commit 8729908660
3 changed files with 198 additions and 28 deletions

136
proxy/vmess/auth.go Normal file
View 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
}

View File

@ -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
}

View File

@ -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