123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- package vmess
- import (
- "bytes"
- "crypto/aes"
- "crypto/cipher"
- "crypto/hmac"
- "crypto/md5"
- "crypto/rand"
- "crypto/sha256"
- "encoding/binary"
- "errors"
- "hash/fnv"
- "io"
- "net"
- "time"
- "github.com/metacubex/randv2"
- "golang.org/x/crypto/chacha20poly1305"
- )
- // Conn wrapper a net.Conn with vmess protocol
- type Conn struct {
- net.Conn
- reader io.Reader
- writer io.Writer
- dst *DstAddr
- id *ID
- reqBodyIV []byte
- reqBodyKey []byte
- respBodyIV []byte
- respBodyKey []byte
- respV byte
- security byte
- isAead bool
- received bool
- }
- func (vc *Conn) Write(b []byte) (int, error) {
- return vc.writer.Write(b)
- }
- func (vc *Conn) Read(b []byte) (int, error) {
- if vc.received {
- return vc.reader.Read(b)
- }
- if err := vc.recvResponse(); err != nil {
- return 0, err
- }
- vc.received = true
- return vc.reader.Read(b)
- }
- func (vc *Conn) sendRequest() error {
- timestamp := time.Now()
- mbuf := &bytes.Buffer{}
- if !vc.isAead {
- h := hmac.New(md5.New, vc.id.UUID.Bytes())
- binary.Write(h, binary.BigEndian, uint64(timestamp.Unix()))
- mbuf.Write(h.Sum(nil))
- }
- buf := &bytes.Buffer{}
- // Ver IV Key V Opt
- buf.WriteByte(Version)
- buf.Write(vc.reqBodyIV[:])
- buf.Write(vc.reqBodyKey[:])
- buf.WriteByte(vc.respV)
- buf.WriteByte(OptionChunkStream)
- p := randv2.IntN(16)
- // P Sec Reserve Cmd
- buf.WriteByte(byte(p<<4) | byte(vc.security))
- buf.WriteByte(0)
- if vc.dst.UDP {
- buf.WriteByte(CommandUDP)
- } else {
- buf.WriteByte(CommandTCP)
- }
- // Port AddrType Addr
- binary.Write(buf, binary.BigEndian, uint16(vc.dst.Port))
- buf.WriteByte(vc.dst.AddrType)
- buf.Write(vc.dst.Addr)
- // padding
- if p > 0 {
- padding := make([]byte, p)
- rand.Read(padding)
- buf.Write(padding)
- }
- fnv1a := fnv.New32a()
- fnv1a.Write(buf.Bytes())
- buf.Write(fnv1a.Sum(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())
- mbuf.Write(buf.Bytes())
- _, err = vc.Conn.Write(mbuf.Bytes())
- 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 {
- 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.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 {
- return errors.New("unexpected response header")
- }
- if buf[2] != 0 {
- return errors.New("dynamic port is not supported now")
- }
- return nil
- }
- func hashTimestamp(t time.Time) []byte {
- md5hash := md5.New()
- ts := make([]byte, 8)
- binary.BigEndian.PutUint64(ts, uint64(t.Unix()))
- md5hash.Write(ts)
- md5hash.Write(ts)
- md5hash.Write(ts)
- md5hash.Write(ts)
- return md5hash.Sum(nil)
- }
- // newConn return a Conn instance
- 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)
- reqBodyKey := make([]byte, 16)
- copy(reqBodyIV[:], randBytes[:16])
- copy(reqBodyKey[:], randBytes[16:32])
- respV := randBytes[32]
- 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
- switch security {
- case SecurityNone:
- reader = newChunkReader(conn)
- writer = newChunkWriter(conn)
- case SecurityAES128GCM:
- block, _ := aes.NewCipher(reqBodyKey[:])
- aead, _ := cipher.NewGCM(block)
- writer = newAEADWriter(conn, aead, reqBodyIV[:])
- block, _ = aes.NewCipher(respBodyKey[:])
- aead, _ = cipher.NewGCM(block)
- reader = newAEADReader(conn, aead, respBodyIV[:])
- case SecurityCHACHA20POLY1305:
- key := make([]byte, 32)
- t := md5.Sum(reqBodyKey[:])
- copy(key, t[:])
- t = md5.Sum(key[:16])
- copy(key[16:], t[:])
- aead, _ := chacha20poly1305.New(key)
- writer = newAEADWriter(conn, aead, reqBodyIV[:])
- t = md5.Sum(respBodyKey[:])
- copy(key, t[:])
- t = md5.Sum(key[:16])
- copy(key[16:], t[:])
- aead, _ = chacha20poly1305.New(key)
- reader = newAEADReader(conn, aead, respBodyIV[:])
- }
- c := &Conn{
- Conn: conn,
- id: id,
- dst: dst,
- reqBodyIV: reqBodyIV,
- reqBodyKey: reqBodyKey,
- respV: respV,
- respBodyIV: respBodyIV[:],
- respBodyKey: respBodyKey[:],
- reader: reader,
- writer: writer,
- security: security,
- isAead: isAead,
- }
- if err := c.sendRequest(); err != nil {
- return nil, err
- }
- return c, nil
- }
|