mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-04-29 16:58:34 +00:00
parent
bf4b1fab3c
commit
9112cfd39c
25 changed files with 150 additions and 761 deletions
|
@ -17,6 +17,7 @@ func Authenticate(b []byte) uint32 {
|
|||
return fnv1hash.Sum32()
|
||||
}
|
||||
|
||||
// [DEPRECATED 2023-06]
|
||||
type NoOpAuthenticator struct{}
|
||||
|
||||
func (NoOpAuthenticator) NonceSize() int {
|
||||
|
@ -37,34 +38,6 @@ func (NoOpAuthenticator) Open(dst, nonce, ciphertext, additionalData []byte) ([]
|
|||
return append(dst[:0], ciphertext...), nil
|
||||
}
|
||||
|
||||
// FnvAuthenticator is an AEAD based on Fnv hash.
|
||||
type FnvAuthenticator struct{}
|
||||
|
||||
// NonceSize implements AEAD.NonceSize().
|
||||
func (*FnvAuthenticator) NonceSize() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Overhead impelements AEAD.Overhead().
|
||||
func (*FnvAuthenticator) Overhead() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
// Seal implements AEAD.Seal().
|
||||
func (*FnvAuthenticator) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
|
||||
dst = append(dst, 0, 0, 0, 0)
|
||||
binary.BigEndian.PutUint32(dst, Authenticate(plaintext))
|
||||
return append(dst, plaintext...)
|
||||
}
|
||||
|
||||
// Open implements AEAD.Open().
|
||||
func (*FnvAuthenticator) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
if binary.BigEndian.Uint32(ciphertext[:4]) != Authenticate(ciphertext[4:]) {
|
||||
return dst, newError("invalid authentication")
|
||||
}
|
||||
return append(dst, ciphertext[4:]...), nil
|
||||
}
|
||||
|
||||
// GenerateChacha20Poly1305Key generates a 32-byte key from a given 16-byte array.
|
||||
func GenerateChacha20Poly1305Key(b []byte) []byte {
|
||||
key := make([]byte, 32)
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
package encoding_test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/xtls/xray-core/common"
|
||||
. "github.com/xtls/xray-core/proxy/vmess/encoding"
|
||||
)
|
||||
|
||||
func TestFnvAuth(t *testing.T) {
|
||||
fnvAuth := new(FnvAuthenticator)
|
||||
|
||||
expectedText := make([]byte, 256)
|
||||
_, err := rand.Read(expectedText)
|
||||
common.Must(err)
|
||||
|
||||
buffer := make([]byte, 512)
|
||||
b := fnvAuth.Seal(buffer[:0], nil, expectedText, nil)
|
||||
b, err = fnvAuth.Open(buffer[:0], nil, b, nil)
|
||||
common.Must(err)
|
||||
if r := cmp.Diff(b, expectedText); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
|
@ -5,11 +5,9 @@ import (
|
|||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
|
||||
|
@ -20,24 +18,13 @@ import (
|
|||
"github.com/xtls/xray-core/common/dice"
|
||||
"github.com/xtls/xray-core/common/drain"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/proxy/vmess"
|
||||
vmessaead "github.com/xtls/xray-core/proxy/vmess/aead"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
func hashTimestamp(h hash.Hash, t protocol.Timestamp) []byte {
|
||||
common.Must2(serial.WriteUint64(h, uint64(t)))
|
||||
common.Must2(serial.WriteUint64(h, uint64(t)))
|
||||
common.Must2(serial.WriteUint64(h, uint64(t)))
|
||||
common.Must2(serial.WriteUint64(h, uint64(t)))
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
// ClientSession stores connection session info for VMess client.
|
||||
type ClientSession struct {
|
||||
isAEAD bool
|
||||
idHash protocol.IDHash
|
||||
requestBodyKey [16]byte
|
||||
requestBodyIV [16]byte
|
||||
responseBodyKey [16]byte
|
||||
|
@ -49,11 +36,8 @@ type ClientSession struct {
|
|||
}
|
||||
|
||||
// NewClientSession creates a new ClientSession.
|
||||
func NewClientSession(ctx context.Context, isAEAD bool, idHash protocol.IDHash, behaviorSeed int64) *ClientSession {
|
||||
session := &ClientSession{
|
||||
isAEAD: isAEAD,
|
||||
idHash: idHash,
|
||||
}
|
||||
func NewClientSession(ctx context.Context, behaviorSeed int64) *ClientSession {
|
||||
session := &ClientSession{}
|
||||
|
||||
randomBytes := make([]byte, 33) // 16 + 16 + 1
|
||||
common.Must2(rand.Read(randomBytes))
|
||||
|
@ -61,15 +45,10 @@ func NewClientSession(ctx context.Context, isAEAD bool, idHash protocol.IDHash,
|
|||
copy(session.requestBodyIV[:], randomBytes[16:32])
|
||||
session.responseHeader = randomBytes[32]
|
||||
|
||||
if !session.isAEAD {
|
||||
session.responseBodyKey = md5.Sum(session.requestBodyKey[:])
|
||||
session.responseBodyIV = md5.Sum(session.requestBodyIV[:])
|
||||
} else {
|
||||
BodyKey := sha256.Sum256(session.requestBodyKey[:])
|
||||
copy(session.responseBodyKey[:], BodyKey[:16])
|
||||
BodyIV := sha256.Sum256(session.requestBodyIV[:])
|
||||
copy(session.responseBodyIV[:], BodyIV[:16])
|
||||
}
|
||||
BodyKey := sha256.Sum256(session.requestBodyKey[:])
|
||||
copy(session.responseBodyKey[:], BodyKey[:16])
|
||||
BodyIV := sha256.Sum256(session.requestBodyIV[:])
|
||||
copy(session.responseBodyIV[:], BodyIV[:16])
|
||||
{
|
||||
var err error
|
||||
session.readDrainer, err = drain.NewBehaviorSeedLimitedDrainer(behaviorSeed, 18, 3266, 64)
|
||||
|
@ -83,13 +62,7 @@ func NewClientSession(ctx context.Context, isAEAD bool, idHash protocol.IDHash,
|
|||
}
|
||||
|
||||
func (c *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, writer io.Writer) error {
|
||||
timestamp := protocol.NewTimestampGenerator(protocol.NowTime(), 30)()
|
||||
account := header.User.Account.(*vmess.MemoryAccount)
|
||||
if !c.isAEAD {
|
||||
idHash := c.idHash(account.AnyValidID().Bytes())
|
||||
common.Must2(serial.WriteUint64(idHash, uint64(timestamp)))
|
||||
common.Must2(writer.Write(idHash.Sum(nil)))
|
||||
}
|
||||
|
||||
buffer := buf.New()
|
||||
defer buffer.Release()
|
||||
|
@ -121,17 +94,10 @@ func (c *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, writ
|
|||
fnv1a.Sum(hashBytes[:0])
|
||||
}
|
||||
|
||||
if !c.isAEAD {
|
||||
iv := hashTimestamp(md5.New(), timestamp)
|
||||
aesStream := crypto.NewAesEncryptionStream(account.ID.CmdKey(), iv)
|
||||
aesStream.XORKeyStream(buffer.Bytes(), buffer.Bytes())
|
||||
common.Must2(writer.Write(buffer.Bytes()))
|
||||
} else {
|
||||
var fixedLengthCmdKey [16]byte
|
||||
copy(fixedLengthCmdKey[:], account.ID.CmdKey())
|
||||
vmessout := vmessaead.SealVMessAEADHeader(fixedLengthCmdKey, buffer.Bytes())
|
||||
common.Must2(io.Copy(writer, bytes.NewReader(vmessout)))
|
||||
}
|
||||
var fixedLengthCmdKey [16]byte
|
||||
copy(fixedLengthCmdKey[:], account.ID.CmdKey())
|
||||
vmessout := vmessaead.SealVMessAEADHeader(fixedLengthCmdKey, buffer.Bytes())
|
||||
common.Must2(io.Copy(writer, bytes.NewReader(vmessout)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -165,19 +131,6 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write
|
|||
}
|
||||
|
||||
return buf.NewWriter(writer), nil
|
||||
case protocol.SecurityType_LEGACY:
|
||||
aesStream := crypto.NewAesEncryptionStream(c.requestBodyKey[:], c.requestBodyIV[:])
|
||||
cryptionWriter := crypto.NewCryptionWriter(aesStream, writer)
|
||||
if request.Option.Has(protocol.RequestOptionChunkStream) {
|
||||
auth := &crypto.AEADAuthenticator{
|
||||
AEAD: new(FnvAuthenticator),
|
||||
NonceGenerator: crypto.GenerateEmptyBytes(),
|
||||
AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
|
||||
}
|
||||
return crypto.NewAuthenticationWriter(auth, sizeParser, cryptionWriter, request.Command.TransferType(), padding), nil
|
||||
}
|
||||
|
||||
return &buf.SequentialWriter{Writer: cryptionWriter}, nil
|
||||
case protocol.SecurityType_AES128_GCM:
|
||||
aead := crypto.NewAesGcm(c.requestBodyKey[:])
|
||||
auth := &crypto.AEADAuthenticator{
|
||||
|
@ -225,53 +178,48 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write
|
|||
}
|
||||
|
||||
func (c *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.ResponseHeader, error) {
|
||||
if !c.isAEAD {
|
||||
aesStream := crypto.NewAesDecryptionStream(c.responseBodyKey[:], c.responseBodyIV[:])
|
||||
c.responseReader = crypto.NewCryptionReader(aesStream, reader)
|
||||
} else {
|
||||
aeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderLenKey)
|
||||
aeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderLenIV)[:12]
|
||||
aeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderLenKey)
|
||||
aeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderLenIV)[:12]
|
||||
|
||||
aeadResponseHeaderLengthEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderLengthEncryptionKey)).(cipher.Block)
|
||||
aeadResponseHeaderLengthEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock)).(cipher.AEAD)
|
||||
aeadResponseHeaderLengthEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderLengthEncryptionKey)).(cipher.Block)
|
||||
aeadResponseHeaderLengthEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock)).(cipher.AEAD)
|
||||
|
||||
var aeadEncryptedResponseHeaderLength [18]byte
|
||||
var decryptedResponseHeaderLength int
|
||||
var decryptedResponseHeaderLengthBinaryDeserializeBuffer uint16
|
||||
var aeadEncryptedResponseHeaderLength [18]byte
|
||||
var decryptedResponseHeaderLength int
|
||||
var decryptedResponseHeaderLengthBinaryDeserializeBuffer uint16
|
||||
|
||||
if n, err := io.ReadFull(reader, aeadEncryptedResponseHeaderLength[:]); err != nil {
|
||||
c.readDrainer.AcknowledgeReceive(n)
|
||||
return nil, drain.WithError(c.readDrainer, reader, newError("Unable to Read Header Len").Base(err))
|
||||
} else { // nolint: golint
|
||||
c.readDrainer.AcknowledgeReceive(n)
|
||||
}
|
||||
if decryptedResponseHeaderLengthBinaryBuffer, err := aeadResponseHeaderLengthEncryptionAEAD.Open(nil, aeadResponseHeaderLengthEncryptionIV, aeadEncryptedResponseHeaderLength[:], nil); err != nil {
|
||||
return nil, drain.WithError(c.readDrainer, reader, newError("Failed To Decrypt Length").Base(err))
|
||||
} else { // nolint: golint
|
||||
common.Must(binary.Read(bytes.NewReader(decryptedResponseHeaderLengthBinaryBuffer), binary.BigEndian, &decryptedResponseHeaderLengthBinaryDeserializeBuffer))
|
||||
decryptedResponseHeaderLength = int(decryptedResponseHeaderLengthBinaryDeserializeBuffer)
|
||||
}
|
||||
if n, err := io.ReadFull(reader, aeadEncryptedResponseHeaderLength[:]); err != nil {
|
||||
c.readDrainer.AcknowledgeReceive(n)
|
||||
return nil, drain.WithError(c.readDrainer, reader, newError("Unable to Read Header Len").Base(err))
|
||||
} else { // nolint: golint
|
||||
c.readDrainer.AcknowledgeReceive(n)
|
||||
}
|
||||
if decryptedResponseHeaderLengthBinaryBuffer, err := aeadResponseHeaderLengthEncryptionAEAD.Open(nil, aeadResponseHeaderLengthEncryptionIV, aeadEncryptedResponseHeaderLength[:], nil); err != nil {
|
||||
return nil, drain.WithError(c.readDrainer, reader, newError("Failed To Decrypt Length").Base(err))
|
||||
} else { // nolint: golint
|
||||
common.Must(binary.Read(bytes.NewReader(decryptedResponseHeaderLengthBinaryBuffer), binary.BigEndian, &decryptedResponseHeaderLengthBinaryDeserializeBuffer))
|
||||
decryptedResponseHeaderLength = int(decryptedResponseHeaderLengthBinaryDeserializeBuffer)
|
||||
}
|
||||
|
||||
aeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadKey)
|
||||
aeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadIV)[:12]
|
||||
aeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadKey)
|
||||
aeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadIV)[:12]
|
||||
|
||||
aeadResponseHeaderPayloadEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey)).(cipher.Block)
|
||||
aeadResponseHeaderPayloadEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock)).(cipher.AEAD)
|
||||
aeadResponseHeaderPayloadEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey)).(cipher.Block)
|
||||
aeadResponseHeaderPayloadEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock)).(cipher.AEAD)
|
||||
|
||||
encryptedResponseHeaderBuffer := make([]byte, decryptedResponseHeaderLength+16)
|
||||
encryptedResponseHeaderBuffer := make([]byte, decryptedResponseHeaderLength+16)
|
||||
|
||||
if n, err := io.ReadFull(reader, encryptedResponseHeaderBuffer); err != nil {
|
||||
c.readDrainer.AcknowledgeReceive(n)
|
||||
return nil, drain.WithError(c.readDrainer, reader, newError("Unable to Read Header Data").Base(err))
|
||||
} else { // nolint: golint
|
||||
c.readDrainer.AcknowledgeReceive(n)
|
||||
}
|
||||
if n, err := io.ReadFull(reader, encryptedResponseHeaderBuffer); err != nil {
|
||||
c.readDrainer.AcknowledgeReceive(n)
|
||||
return nil, drain.WithError(c.readDrainer, reader, newError("Unable to Read Header Data").Base(err))
|
||||
} else { // nolint: golint
|
||||
c.readDrainer.AcknowledgeReceive(n)
|
||||
}
|
||||
|
||||
if decryptedResponseHeaderBuffer, err := aeadResponseHeaderPayloadEncryptionAEAD.Open(nil, aeadResponseHeaderPayloadEncryptionIV, encryptedResponseHeaderBuffer, nil); err != nil {
|
||||
return nil, drain.WithError(c.readDrainer, reader, newError("Failed To Decrypt Payload").Base(err))
|
||||
} else { // nolint: golint
|
||||
c.responseReader = bytes.NewReader(decryptedResponseHeaderBuffer)
|
||||
}
|
||||
if decryptedResponseHeaderBuffer, err := aeadResponseHeaderPayloadEncryptionAEAD.Open(nil, aeadResponseHeaderPayloadEncryptionIV, encryptedResponseHeaderBuffer, nil); err != nil {
|
||||
return nil, drain.WithError(c.readDrainer, reader, newError("Failed To Decrypt Payload").Base(err))
|
||||
} else { // nolint: golint
|
||||
c.responseReader = bytes.NewReader(decryptedResponseHeaderBuffer)
|
||||
}
|
||||
|
||||
buffer := buf.StackNew()
|
||||
|
@ -302,10 +250,8 @@ func (c *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.Respon
|
|||
header.Command = command
|
||||
}
|
||||
}
|
||||
if c.isAEAD {
|
||||
aesStream := crypto.NewAesDecryptionStream(c.responseBodyKey[:], c.responseBodyIV[:])
|
||||
c.responseReader = crypto.NewCryptionReader(aesStream, reader)
|
||||
}
|
||||
aesStream := crypto.NewAesDecryptionStream(c.responseBodyKey[:], c.responseBodyIV[:])
|
||||
c.responseReader = crypto.NewCryptionReader(aesStream, reader)
|
||||
return header, nil
|
||||
}
|
||||
|
||||
|
@ -340,17 +286,6 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read
|
|||
}
|
||||
|
||||
return buf.NewReader(reader), nil
|
||||
case protocol.SecurityType_LEGACY:
|
||||
if request.Option.Has(protocol.RequestOptionChunkStream) {
|
||||
auth := &crypto.AEADAuthenticator{
|
||||
AEAD: new(FnvAuthenticator),
|
||||
NonceGenerator: crypto.GenerateEmptyBytes(),
|
||||
AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
|
||||
}
|
||||
return crypto.NewAuthenticationReader(auth, sizeParser, c.responseReader, request.Command.TransferType(), padding), nil
|
||||
}
|
||||
|
||||
return buf.NewReader(c.responseReader), nil
|
||||
case protocol.SecurityType_AES128_GCM:
|
||||
aead := crypto.NewAesGcm(c.responseBodyKey[:])
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ func (f *CommandSwitchAccountFactory) Marshal(command interface{}, writer io.Wri
|
|||
|
||||
idBytes := cmd.ID.Bytes()
|
||||
common.Must2(writer.Write(idBytes))
|
||||
common.Must2(serial.WriteUint16(writer, cmd.AlterIds))
|
||||
common.Must2(serial.WriteUint16(writer, 0)) // compatible with legacy alterId
|
||||
common.Must2(writer.Write([]byte{byte(cmd.Level)}))
|
||||
|
||||
common.Must2(writer.Write([]byte{cmd.ValidMin}))
|
||||
|
@ -130,12 +130,7 @@ func (f *CommandSwitchAccountFactory) Unmarshal(data []byte) (interface{}, error
|
|||
return nil, ErrInsufficientLength
|
||||
}
|
||||
cmd.ID, _ = uuid.ParseBytes(data[idStart : idStart+16])
|
||||
alterIDStart := idStart + 16
|
||||
if len(data) < alterIDStart+2 {
|
||||
return nil, ErrInsufficientLength
|
||||
}
|
||||
cmd.AlterIds = binary.BigEndian.Uint16(data[alterIDStart : alterIDStart+2])
|
||||
levelStart := alterIDStart + 2
|
||||
levelStart := idStart + 16 + 2
|
||||
if len(data) < levelStart+1 {
|
||||
return nil, ErrInsufficientLength
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ func TestSwitchAccount(t *testing.T) {
|
|||
sa := &protocol.CommandSwitchAccount{
|
||||
Port: 1234,
|
||||
ID: uuid.New(),
|
||||
AlterIds: 1024,
|
||||
Level: 128,
|
||||
ValidMin: 16,
|
||||
}
|
||||
|
@ -40,7 +39,6 @@ func TestSwitchAccountBugOffByOne(t *testing.T) {
|
|||
sa := &protocol.CommandSwitchAccount{
|
||||
Port: 1234,
|
||||
ID: uuid.New(),
|
||||
AlterIds: 1024,
|
||||
Level: 128,
|
||||
ValidMin: 16,
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ func TestRequestSerialization(t *testing.T) {
|
|||
}
|
||||
|
||||
buffer := buf.New()
|
||||
client := NewClientSession(context.TODO(), true, protocol.DefaultIDHash, 0)
|
||||
client := NewClientSession(context.TODO(), 0)
|
||||
common.Must(client.EncodeRequestHeader(expectedRequest, buffer))
|
||||
|
||||
buffer2 := buf.New()
|
||||
|
@ -50,7 +50,7 @@ func TestRequestSerialization(t *testing.T) {
|
|||
sessionHistory := NewSessionHistory()
|
||||
defer common.Close(sessionHistory)
|
||||
|
||||
userValidator := vmess.NewTimedUserValidator(protocol.DefaultIDHash)
|
||||
userValidator := vmess.NewTimedUserValidator()
|
||||
userValidator.Add(user)
|
||||
defer common.Close(userValidator)
|
||||
|
||||
|
@ -90,7 +90,7 @@ func TestInvalidRequest(t *testing.T) {
|
|||
}
|
||||
|
||||
buffer := buf.New()
|
||||
client := NewClientSession(context.TODO(), true, protocol.DefaultIDHash, 0)
|
||||
client := NewClientSession(context.TODO(), 0)
|
||||
common.Must(client.EncodeRequestHeader(expectedRequest, buffer))
|
||||
|
||||
buffer2 := buf.New()
|
||||
|
@ -99,7 +99,7 @@ func TestInvalidRequest(t *testing.T) {
|
|||
sessionHistory := NewSessionHistory()
|
||||
defer common.Close(sessionHistory)
|
||||
|
||||
userValidator := vmess.NewTimedUserValidator(protocol.DefaultIDHash)
|
||||
userValidator := vmess.NewTimedUserValidator()
|
||||
userValidator.Add(user)
|
||||
defer common.Close(userValidator)
|
||||
|
||||
|
@ -130,7 +130,7 @@ func TestMuxRequest(t *testing.T) {
|
|||
}
|
||||
|
||||
buffer := buf.New()
|
||||
client := NewClientSession(context.TODO(), true, protocol.DefaultIDHash, 0)
|
||||
client := NewClientSession(context.TODO(), 0)
|
||||
common.Must(client.EncodeRequestHeader(expectedRequest, buffer))
|
||||
|
||||
buffer2 := buf.New()
|
||||
|
@ -139,7 +139,7 @@ func TestMuxRequest(t *testing.T) {
|
|||
sessionHistory := NewSessionHistory()
|
||||
defer common.Close(sessionHistory)
|
||||
|
||||
userValidator := vmess.NewTimedUserValidator(protocol.DefaultIDHash)
|
||||
userValidator := vmess.NewTimedUserValidator()
|
||||
userValidator.Add(user)
|
||||
defer common.Close(userValidator)
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"hash/fnv"
|
||||
|
@ -102,10 +101,6 @@ type ServerSession struct {
|
|||
responseBodyIV [16]byte
|
||||
responseWriter io.Writer
|
||||
responseHeader byte
|
||||
|
||||
isAEADRequest bool
|
||||
|
||||
isAEADForced bool
|
||||
}
|
||||
|
||||
// NewServerSession creates a new ServerSession, using the given UserValidator.
|
||||
|
@ -117,17 +112,12 @@ func NewServerSession(validator *vmess.TimedUserValidator, sessionHistory *Sessi
|
|||
}
|
||||
}
|
||||
|
||||
// SetAEADForced sets isAEADForced for a ServerSession.
|
||||
func (s *ServerSession) SetAEADForced(isAEADForced bool) {
|
||||
s.isAEADForced = isAEADForced
|
||||
}
|
||||
|
||||
func parseSecurityType(b byte) protocol.SecurityType {
|
||||
if _, f := protocol.SecurityType_name[int32(b)]; f {
|
||||
st := protocol.SecurityType(b)
|
||||
// For backward compatibility.
|
||||
if st == protocol.SecurityType_UNKNOWN {
|
||||
st = protocol.SecurityType_LEGACY
|
||||
st = protocol.SecurityType_AUTO
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
@ -183,26 +173,6 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader, isDrain bool) (*pr
|
|||
}
|
||||
}
|
||||
decryptor = bytes.NewReader(aeadData)
|
||||
s.isAEADRequest = true
|
||||
|
||||
case errorAEAD == vmessaead.ErrNotFound:
|
||||
userLegacy, timestamp, valid, userValidationError := s.userValidator.Get(buffer.Bytes())
|
||||
if !valid || userValidationError != nil {
|
||||
return nil, drainConnection(newError("invalid user").Base(userValidationError))
|
||||
}
|
||||
if s.isAEADForced {
|
||||
return nil, drainConnection(newError("invalid user: VMessAEAD is enforced and a non VMessAEAD connection is received. You can still disable this security feature with environment variable xray.vmess.aead.forced = false . You will not be able to enable legacy header workaround in the future."))
|
||||
}
|
||||
if s.userValidator.ShouldShowLegacyWarn() {
|
||||
newError("Critical Warning: potentially invalid user: a non VMessAEAD connection is received. From 2022 Jan 1st, this kind of connection will be rejected by default. You should update or replace your client software now. This message will not be shown for further violation on this inbound.").AtWarning().WriteToLog()
|
||||
}
|
||||
user = userLegacy
|
||||
iv := hashTimestamp(md5.New(), timestamp)
|
||||
vmessAccount = userLegacy.Account.(*vmess.MemoryAccount)
|
||||
|
||||
aesStream := crypto.NewAesDecryptionStream(vmessAccount.ID.CmdKey(), iv)
|
||||
decryptor = crypto.NewCryptionReader(aesStream, reader)
|
||||
|
||||
default:
|
||||
return nil, drainConnection(newError("invalid user").Base(errorAEAD))
|
||||
}
|
||||
|
@ -225,15 +195,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader, isDrain bool) (*pr
|
|||
sid.key = s.requestBodyKey
|
||||
sid.nonce = s.requestBodyIV
|
||||
if !s.sessionHistory.addIfNotExits(sid) {
|
||||
if !s.isAEADRequest {
|
||||
drainErr := s.userValidator.BurnTaintFuse(fixedSizeAuthID[:])
|
||||
if drainErr != nil {
|
||||
return nil, drainConnection(newError("duplicated session id, possibly under replay attack, and failed to taint userHash").Base(drainErr))
|
||||
}
|
||||
return nil, drainConnection(newError("duplicated session id, possibly under replay attack, userHash tainted"))
|
||||
} else {
|
||||
return nil, newError("duplicated session id, possibly under replay attack, but this is a AEAD request")
|
||||
}
|
||||
return nil, newError("duplicated session id, possibly under replay attack, but this is a AEAD request")
|
||||
}
|
||||
|
||||
s.responseHeader = buffer.Byte(33) // 1 byte
|
||||
|
@ -257,25 +219,11 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader, isDrain bool) (*pr
|
|||
|
||||
if paddingLen > 0 {
|
||||
if _, err := buffer.ReadFullFrom(decryptor, int32(paddingLen)); err != nil {
|
||||
if !s.isAEADRequest {
|
||||
burnErr := s.userValidator.BurnTaintFuse(fixedSizeAuthID[:])
|
||||
if burnErr != nil {
|
||||
return nil, newError("failed to read padding, failed to taint userHash").Base(burnErr).Base(err)
|
||||
}
|
||||
return nil, newError("failed to read padding, userHash tainted").Base(err)
|
||||
}
|
||||
return nil, newError("failed to read padding").Base(err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := buffer.ReadFullFrom(decryptor, 4); err != nil {
|
||||
if !s.isAEADRequest {
|
||||
burnErr := s.userValidator.BurnTaintFuse(fixedSizeAuthID[:])
|
||||
if burnErr != nil {
|
||||
return nil, newError("failed to read checksum, failed to taint userHash").Base(burnErr).Base(err)
|
||||
}
|
||||
return nil, newError("failed to read checksum, userHash tainted").Base(err)
|
||||
}
|
||||
return nil, newError("failed to read checksum").Base(err)
|
||||
}
|
||||
|
||||
|
@ -285,17 +233,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader, isDrain bool) (*pr
|
|||
expectedHash := binary.BigEndian.Uint32(buffer.BytesFrom(-4))
|
||||
|
||||
if actualHash != expectedHash {
|
||||
if !s.isAEADRequest {
|
||||
Autherr := newError("invalid auth, legacy userHash tainted")
|
||||
burnErr := s.userValidator.BurnTaintFuse(fixedSizeAuthID[:])
|
||||
if burnErr != nil {
|
||||
Autherr = newError("invalid auth, can't taint legacy userHash").Base(burnErr)
|
||||
}
|
||||
// It is possible that we are under attack described in https://github.com/xray/xray-core/issues/2523
|
||||
return nil, drainConnection(Autherr)
|
||||
} else {
|
||||
return nil, newError("invalid auth, but this is a AEAD request")
|
||||
}
|
||||
return nil, newError("invalid auth, but this is a AEAD request")
|
||||
}
|
||||
|
||||
if request.Address == nil {
|
||||
|
@ -340,19 +278,6 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade
|
|||
}
|
||||
return buf.NewReader(reader), nil
|
||||
|
||||
case protocol.SecurityType_LEGACY:
|
||||
aesStream := crypto.NewAesDecryptionStream(s.requestBodyKey[:], s.requestBodyIV[:])
|
||||
cryptionReader := crypto.NewCryptionReader(aesStream, reader)
|
||||
if request.Option.Has(protocol.RequestOptionChunkStream) {
|
||||
auth := &crypto.AEADAuthenticator{
|
||||
AEAD: new(FnvAuthenticator),
|
||||
NonceGenerator: crypto.GenerateEmptyBytes(),
|
||||
AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
|
||||
}
|
||||
return crypto.NewAuthenticationReader(auth, sizeParser, cryptionReader, request.Command.TransferType(), padding), nil
|
||||
}
|
||||
return buf.NewReader(cryptionReader), nil
|
||||
|
||||
case protocol.SecurityType_AES128_GCM:
|
||||
aead := crypto.NewAesGcm(s.requestBodyKey[:])
|
||||
auth := &crypto.AEADAuthenticator{
|
||||
|
@ -403,25 +328,17 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade
|
|||
// EncodeResponseHeader writes encoded response header into the given writer.
|
||||
func (s *ServerSession) EncodeResponseHeader(header *protocol.ResponseHeader, writer io.Writer) {
|
||||
var encryptionWriter io.Writer
|
||||
if !s.isAEADRequest {
|
||||
s.responseBodyKey = md5.Sum(s.requestBodyKey[:])
|
||||
s.responseBodyIV = md5.Sum(s.requestBodyIV[:])
|
||||
} else {
|
||||
BodyKey := sha256.Sum256(s.requestBodyKey[:])
|
||||
copy(s.responseBodyKey[:], BodyKey[:16])
|
||||
BodyIV := sha256.Sum256(s.requestBodyIV[:])
|
||||
copy(s.responseBodyIV[:], BodyIV[:16])
|
||||
}
|
||||
BodyKey := sha256.Sum256(s.requestBodyKey[:])
|
||||
copy(s.responseBodyKey[:], BodyKey[:16])
|
||||
BodyIV := sha256.Sum256(s.requestBodyIV[:])
|
||||
copy(s.responseBodyIV[:], BodyIV[:16])
|
||||
|
||||
aesStream := crypto.NewAesEncryptionStream(s.responseBodyKey[:], s.responseBodyIV[:])
|
||||
encryptionWriter = crypto.NewCryptionWriter(aesStream, writer)
|
||||
s.responseWriter = encryptionWriter
|
||||
|
||||
aeadEncryptedHeaderBuffer := bytes.NewBuffer(nil)
|
||||
|
||||
if s.isAEADRequest {
|
||||
encryptionWriter = aeadEncryptedHeaderBuffer
|
||||
}
|
||||
encryptionWriter = aeadEncryptedHeaderBuffer
|
||||
|
||||
common.Must2(encryptionWriter.Write([]byte{s.responseHeader, byte(header.Option)}))
|
||||
err := MarshalCommand(header.Command, encryptionWriter)
|
||||
|
@ -429,31 +346,29 @@ func (s *ServerSession) EncodeResponseHeader(header *protocol.ResponseHeader, wr
|
|||
common.Must2(encryptionWriter.Write([]byte{0x00, 0x00}))
|
||||
}
|
||||
|
||||
if s.isAEADRequest {
|
||||
aeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(s.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderLenKey)
|
||||
aeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(s.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderLenIV)[:12]
|
||||
aeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(s.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderLenKey)
|
||||
aeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(s.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderLenIV)[:12]
|
||||
|
||||
aeadResponseHeaderLengthEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderLengthEncryptionKey)).(cipher.Block)
|
||||
aeadResponseHeaderLengthEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock)).(cipher.AEAD)
|
||||
aeadResponseHeaderLengthEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderLengthEncryptionKey)).(cipher.Block)
|
||||
aeadResponseHeaderLengthEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock)).(cipher.AEAD)
|
||||
|
||||
aeadResponseHeaderLengthEncryptionBuffer := bytes.NewBuffer(nil)
|
||||
aeadResponseHeaderLengthEncryptionBuffer := bytes.NewBuffer(nil)
|
||||
|
||||
decryptedResponseHeaderLengthBinaryDeserializeBuffer := uint16(aeadEncryptedHeaderBuffer.Len())
|
||||
decryptedResponseHeaderLengthBinaryDeserializeBuffer := uint16(aeadEncryptedHeaderBuffer.Len())
|
||||
|
||||
common.Must(binary.Write(aeadResponseHeaderLengthEncryptionBuffer, binary.BigEndian, decryptedResponseHeaderLengthBinaryDeserializeBuffer))
|
||||
common.Must(binary.Write(aeadResponseHeaderLengthEncryptionBuffer, binary.BigEndian, decryptedResponseHeaderLengthBinaryDeserializeBuffer))
|
||||
|
||||
AEADEncryptedLength := aeadResponseHeaderLengthEncryptionAEAD.Seal(nil, aeadResponseHeaderLengthEncryptionIV, aeadResponseHeaderLengthEncryptionBuffer.Bytes(), nil)
|
||||
common.Must2(io.Copy(writer, bytes.NewReader(AEADEncryptedLength)))
|
||||
AEADEncryptedLength := aeadResponseHeaderLengthEncryptionAEAD.Seal(nil, aeadResponseHeaderLengthEncryptionIV, aeadResponseHeaderLengthEncryptionBuffer.Bytes(), nil)
|
||||
common.Must2(io.Copy(writer, bytes.NewReader(AEADEncryptedLength)))
|
||||
|
||||
aeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(s.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadKey)
|
||||
aeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(s.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadIV)[:12]
|
||||
aeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(s.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadKey)
|
||||
aeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(s.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadIV)[:12]
|
||||
|
||||
aeadResponseHeaderPayloadEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey)).(cipher.Block)
|
||||
aeadResponseHeaderPayloadEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock)).(cipher.AEAD)
|
||||
aeadResponseHeaderPayloadEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey)).(cipher.Block)
|
||||
aeadResponseHeaderPayloadEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock)).(cipher.AEAD)
|
||||
|
||||
aeadEncryptedHeaderPayload := aeadResponseHeaderPayloadEncryptionAEAD.Seal(nil, aeadResponseHeaderPayloadEncryptionIV, aeadEncryptedHeaderBuffer.Bytes(), nil)
|
||||
common.Must2(io.Copy(writer, bytes.NewReader(aeadEncryptedHeaderPayload)))
|
||||
}
|
||||
aeadEncryptedHeaderPayload := aeadResponseHeaderPayloadEncryptionAEAD.Seal(nil, aeadResponseHeaderPayloadEncryptionIV, aeadEncryptedHeaderBuffer.Bytes(), nil)
|
||||
common.Must2(io.Copy(writer, bytes.NewReader(aeadEncryptedHeaderPayload)))
|
||||
}
|
||||
|
||||
// EncodeResponseBody returns a Writer that auto-encrypt content written by caller.
|
||||
|
@ -487,17 +402,6 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ
|
|||
}
|
||||
return buf.NewWriter(writer), nil
|
||||
|
||||
case protocol.SecurityType_LEGACY:
|
||||
if request.Option.Has(protocol.RequestOptionChunkStream) {
|
||||
auth := &crypto.AEADAuthenticator{
|
||||
AEAD: new(FnvAuthenticator),
|
||||
NonceGenerator: crypto.GenerateEmptyBytes(),
|
||||
AdditionalDataGenerator: crypto.GenerateEmptyBytes(),
|
||||
}
|
||||
return crypto.NewAuthenticationWriter(auth, sizeParser, s.responseWriter, request.Command.TransferType(), padding), nil
|
||||
}
|
||||
return &buf.SequentialWriter{Writer: s.responseWriter}, nil
|
||||
|
||||
case protocol.SecurityType_AES128_GCM:
|
||||
aead := crypto.NewAesGcm(s.responseBodyKey[:])
|
||||
auth := &crypto.AEADAuthenticator{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue