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,
|
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
|
||||||
|
|
|
@ -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
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
|
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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue