From 109bfcb0f9bb07a23d55038ed4908b5d30037f31 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Tue, 30 Mar 2021 17:34:16 +0800 Subject: [PATCH] Feature: add vmess aead header support --- adapters/outbound/vmess.go | 1 + component/vmess/conn.go | 111 +++++++++++++++++++++++++++++-------- component/vmess/header.go | 103 ++++++++++++++++++++++++++++++++++ component/vmess/vmess.go | 5 +- 4 files changed, 196 insertions(+), 24 deletions(-) create mode 100644 component/vmess/header.go diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 302764cc..c759f6e1 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -246,6 +246,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { Security: security, HostName: option.Server, Port: strconv.Itoa(option.Port), + IsAead: option.AlterID == 0, }) if err != nil { return nil, err diff --git a/component/vmess/conn.go b/component/vmess/conn.go index 44ff57ee..e6e57be6 100644 --- a/component/vmess/conn.go +++ b/component/vmess/conn.go @@ -6,6 +6,7 @@ import ( "crypto/cipher" "crypto/hmac" "crypto/md5" + "crypto/sha256" "encoding/binary" "errors" "hash/fnv" @@ -34,6 +35,7 @@ type Conn struct { respBodyKey []byte respV byte security byte + isAead bool received bool } @@ -57,11 +59,12 @@ func (vc *Conn) Read(b []byte) (int, error) { func (vc *Conn) sendRequest() error { timestamp := time.Now() - h := hmac.New(md5.New, vc.id.UUID.Bytes()) - binary.Write(h, binary.BigEndian, uint64(timestamp.Unix())) - _, err := vc.Conn.Write(h.Sum(nil)) - if err != nil { - return err + if !vc.isAead { + h := hmac.New(md5.New, vc.id.UUID.Bytes()) + binary.Write(h, binary.BigEndian, uint64(timestamp.Unix())) + if _, err := vc.Conn.Write(h.Sum(nil)); err != nil { + return err + } } buf := &bytes.Buffer{} @@ -99,30 +102,77 @@ func (vc *Conn) sendRequest() error { fnv1a.Write(buf.Bytes()) buf.Write(fnv1a.Sum(nil)) - block, err := aes.NewCipher(vc.id.CmdKey) - if err != nil { + if !vc.isAead { + block, err := aes.NewCipher(vc.id.CmdKey) + if err != nil { + return err + } + + stream := cipher.NewCFBEncrypter(block, hashTimestamp(timestamp)) + stream.XORKeyStream(buf.Bytes(), buf.Bytes()) + _, err = vc.Conn.Write(buf.Bytes()) return err } - stream := cipher.NewCFBEncrypter(block, hashTimestamp(timestamp)) - stream.XORKeyStream(buf.Bytes(), buf.Bytes()) - _, err = vc.Conn.Write(buf.Bytes()) + var fixedLengthCmdKey [16]byte + copy(fixedLengthCmdKey[:], vc.id.CmdKey) + vmessout := sealVMessAEADHeader(fixedLengthCmdKey, buf.Bytes(), timestamp) + _, err := vc.Conn.Write(vmessout) return err } func (vc *Conn) recvResponse() error { - block, err := aes.NewCipher(vc.respBodyKey[:]) - if err != nil { - return err - } + var buf []byte + if !vc.isAead { + block, err := aes.NewCipher(vc.respBodyKey[:]) + if err != nil { + return err + } - stream := cipher.NewCFBDecrypter(block, vc.respBodyIV[:]) - buf := make([]byte, 4) - _, err = io.ReadFull(vc.Conn, buf) - if err != nil { - return err + stream := cipher.NewCFBDecrypter(block, vc.respBodyIV[:]) + buf = make([]byte, 4) + _, err = io.ReadFull(vc.Conn, buf) + if err != nil { + return err + } + stream.XORKeyStream(buf, buf) + } else { + aeadResponseHeaderLengthEncryptionKey := kdf(vc.respBodyKey[:], kdfSaltConstAEADRespHeaderLenKey)[:16] + aeadResponseHeaderLengthEncryptionIV := kdf(vc.respBodyIV[:], kdfSaltConstAEADRespHeaderLenIV)[:12] + + aeadResponseHeaderLengthEncryptionKeyAESBlock, _ := aes.NewCipher(aeadResponseHeaderLengthEncryptionKey) + aeadResponseHeaderLengthEncryptionAEAD, _ := cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock) + + aeadEncryptedResponseHeaderLength := make([]byte, 18) + if _, err := io.ReadFull(vc.Conn, aeadEncryptedResponseHeaderLength); err != nil { + return err + } + + decryptedResponseHeaderLengthBinaryBuffer, err := aeadResponseHeaderLengthEncryptionAEAD.Open(nil, aeadResponseHeaderLengthEncryptionIV, aeadEncryptedResponseHeaderLength[:], nil) + if err != nil { + return err + } + + decryptedResponseHeaderLength := binary.BigEndian.Uint16(decryptedResponseHeaderLengthBinaryBuffer) + aeadResponseHeaderPayloadEncryptionKey := kdf(vc.respBodyKey[:], kdfSaltConstAEADRespHeaderPayloadKey)[:16] + aeadResponseHeaderPayloadEncryptionIV := kdf(vc.respBodyIV[:], kdfSaltConstAEADRespHeaderPayloadIV)[:12] + aeadResponseHeaderPayloadEncryptionKeyAESBlock, _ := aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey) + aeadResponseHeaderPayloadEncryptionAEAD, _ := cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock) + + encryptedResponseHeaderBuffer := make([]byte, decryptedResponseHeaderLength+16) + if _, err := io.ReadFull(vc.Conn, encryptedResponseHeaderBuffer); err != nil { + return err + } + + buf, err = aeadResponseHeaderPayloadEncryptionAEAD.Open(nil, aeadResponseHeaderPayloadEncryptionIV, encryptedResponseHeaderBuffer, nil) + if err != nil { + return err + } + + if len(buf) < 4 { + return errors.New("unexpected buffer length") + } } - stream.XORKeyStream(buf, buf) if buf[0] != vc.respV { return errors.New("unexpected response header") @@ -147,7 +197,7 @@ func hashTimestamp(t time.Time) []byte { } // newConn return a Conn instance -func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) (*Conn, error) { +func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security, isAead bool) (*Conn, error) { randBytes := make([]byte, 33) rand.Read(randBytes) reqBodyIV := make([]byte, 16) @@ -156,8 +206,22 @@ func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) (*Conn, err copy(reqBodyKey[:], randBytes[16:32]) respV := randBytes[32] - respBodyKey := md5.Sum(reqBodyKey[:]) - respBodyIV := md5.Sum(reqBodyIV[:]) + var ( + respBodyKey []byte + respBodyIV []byte + ) + + if isAead { + bodyKey := sha256.Sum256(reqBodyKey) + bodyIV := sha256.Sum256(reqBodyIV) + respBodyKey = bodyKey[:16] + respBodyIV = bodyIV[:16] + } else { + bodyKey := md5.Sum(reqBodyKey) + bodyIV := md5.Sum(reqBodyIV) + respBodyKey = bodyKey[:] + respBodyIV = bodyIV[:] + } var writer io.Writer var reader io.Reader @@ -202,6 +266,7 @@ func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) (*Conn, err reader: reader, writer: writer, security: security, + isAead: isAead, } if err := c.sendRequest(); err != nil { return nil, err diff --git a/component/vmess/header.go b/component/vmess/header.go new file mode 100644 index 00000000..27a734be --- /dev/null +++ b/component/vmess/header.go @@ -0,0 +1,103 @@ +package vmess + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "encoding/binary" + "hash" + "hash/crc32" + "time" +) + +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 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 +} + +func sealVMessAEADHeader(key [16]byte, data []byte, t time.Time) []byte { + generatedAuthID := createAuthID(key[:], t.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() +} diff --git a/component/vmess/vmess.go b/component/vmess/vmess.go index 5b538f7e..cb0731d7 100644 --- a/component/vmess/vmess.go +++ b/component/vmess/vmess.go @@ -61,6 +61,7 @@ type Client struct { user []*ID uuid *uuid.UUID security Security + isAead bool } // Config of vmess @@ -70,12 +71,13 @@ type Config struct { Security string Port string HostName string + IsAead bool } // StreamConn return a Conn with net.Conn and DstAddr func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) { r := rand.Intn(len(c.user)) - return newConn(conn, c.user[r], dst, c.security) + return newConn(conn, c.user[r], dst, c.security, c.isAead) } // NewClient return Client instance @@ -106,5 +108,6 @@ func NewClient(config Config) (*Client, error) { user: newAlterIDs(newID(&uid), config.AlterID), uuid: &uid, security: security, + isAead: config.IsAead, }, nil }