Feature: add vmess aead header support

This commit is contained in:
Dreamacro 2021-03-30 17:34:16 +08:00
parent 7ee49f5171
commit 109bfcb0f9
4 changed files with 196 additions and 24 deletions

View file

@ -246,6 +246,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
Security: security, Security: security,
HostName: option.Server, HostName: option.Server,
Port: strconv.Itoa(option.Port), Port: strconv.Itoa(option.Port),
IsAead: option.AlterID == 0,
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -6,6 +6,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"
@ -34,6 +35,7 @@ type Conn struct {
respBodyKey []byte respBodyKey []byte
respV byte respV byte
security byte security byte
isAead bool
received bool received bool
} }
@ -57,12 +59,13 @@ func (vc *Conn) Read(b []byte) (int, error) {
func (vc *Conn) sendRequest() error { func (vc *Conn) sendRequest() error {
timestamp := time.Now() timestamp := time.Now()
if !vc.isAead {
h := hmac.New(md5.New, vc.id.UUID.Bytes()) h := hmac.New(md5.New, vc.id.UUID.Bytes())
binary.Write(h, binary.BigEndian, uint64(timestamp.Unix())) binary.Write(h, binary.BigEndian, uint64(timestamp.Unix()))
_, err := vc.Conn.Write(h.Sum(nil)) if _, err := vc.Conn.Write(h.Sum(nil)); err != nil {
if err != nil {
return err return err
} }
}
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
@ -99,6 +102,7 @@ func (vc *Conn) sendRequest() error {
fnv1a.Write(buf.Bytes()) fnv1a.Write(buf.Bytes())
buf.Write(fnv1a.Sum(nil)) buf.Write(fnv1a.Sum(nil))
if !vc.isAead {
block, err := aes.NewCipher(vc.id.CmdKey) block, err := aes.NewCipher(vc.id.CmdKey)
if err != nil { if err != nil {
return err return err
@ -110,19 +114,65 @@ func (vc *Conn) sendRequest() error {
return err return err
} }
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 { func (vc *Conn) recvResponse() error {
var buf []byte
if !vc.isAead {
block, err := aes.NewCipher(vc.respBodyKey[:]) block, err := aes.NewCipher(vc.respBodyKey[:])
if err != nil { if err != nil {
return err return err
} }
stream := cipher.NewCFBDecrypter(block, vc.respBodyIV[:]) stream := cipher.NewCFBDecrypter(block, vc.respBodyIV[:])
buf := make([]byte, 4) buf = make([]byte, 4)
_, err = io.ReadFull(vc.Conn, buf) _, err = io.ReadFull(vc.Conn, buf)
if err != nil { if err != nil {
return err return err
} }
stream.XORKeyStream(buf, buf) 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")
}
}
if buf[0] != vc.respV { if buf[0] != vc.respV {
return errors.New("unexpected response header") return errors.New("unexpected response header")
@ -147,7 +197,7 @@ func hashTimestamp(t time.Time) []byte {
} }
// newConn return a Conn instance // 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) randBytes := make([]byte, 33)
rand.Read(randBytes) rand.Read(randBytes)
reqBodyIV := make([]byte, 16) 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]) copy(reqBodyKey[:], randBytes[16:32])
respV := randBytes[32] respV := randBytes[32]
respBodyKey := md5.Sum(reqBodyKey[:]) var (
respBodyIV := md5.Sum(reqBodyIV[:]) 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 writer io.Writer
var reader io.Reader var reader io.Reader
@ -202,6 +266,7 @@ func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) (*Conn, err
reader: reader, reader: reader,
writer: writer, writer: writer,
security: security, security: security,
isAead: isAead,
} }
if err := c.sendRequest(); err != nil { if err := c.sendRequest(); err != nil {
return nil, err return nil, err

103
component/vmess/header.go Normal file
View file

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

View file

@ -61,6 +61,7 @@ type Client struct {
user []*ID user []*ID
uuid *uuid.UUID uuid *uuid.UUID
security Security security Security
isAead bool
} }
// Config of vmess // Config of vmess
@ -70,12 +71,13 @@ type Config struct {
Security string Security string
Port string Port string
HostName string HostName string
IsAead bool
} }
// StreamConn return a Conn with net.Conn and DstAddr // StreamConn return a Conn with net.Conn and DstAddr
func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) { func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) {
r := rand.Intn(len(c.user)) 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 // NewClient return Client instance
@ -106,5 +108,6 @@ func NewClient(config Config) (*Client, error) {
user: newAlterIDs(newID(&uid), config.AlterID), user: newAlterIDs(newID(&uid), config.AlterID),
uuid: &uid, uuid: &uid,
security: security, security: security,
isAead: config.IsAead,
}, nil }, nil
} }