Feature: add vmess aead header support
This commit is contained in:
parent
c21bd48213
commit
20e6e5296b
4 changed files with 196 additions and 24 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
103
component/vmess/header.go
Normal file
103
component/vmess/header.go
Normal 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()
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue