mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-12-27 17:55:37 +08:00
307 lines
7.9 KiB
Go
307 lines
7.9 KiB
Go
|
package protocol
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"crypto/cipher"
|
||
|
"crypto/rand"
|
||
|
"crypto/rc4"
|
||
|
"encoding/base64"
|
||
|
"encoding/binary"
|
||
|
"net"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/Dreamacro/clash/common/pool"
|
||
|
"github.com/Dreamacro/clash/transport/shadowsocks/core"
|
||
|
"github.com/Dreamacro/clash/transport/ssr/tools"
|
||
|
)
|
||
|
|
||
|
func init() {
|
||
|
register("auth_chain_a", newAuthChainA, 4)
|
||
|
}
|
||
|
|
||
|
type randDataLengthMethod func(int, []byte, *tools.XorShift128Plus) int
|
||
|
|
||
|
type authChainA struct {
|
||
|
*Base
|
||
|
*authData
|
||
|
*userData
|
||
|
iv []byte
|
||
|
salt string
|
||
|
hasSentHeader bool
|
||
|
rawTrans bool
|
||
|
lastClientHash []byte
|
||
|
lastServerHash []byte
|
||
|
encrypter cipher.Stream
|
||
|
decrypter cipher.Stream
|
||
|
randomClient tools.XorShift128Plus
|
||
|
randomServer tools.XorShift128Plus
|
||
|
randDataLength randDataLengthMethod
|
||
|
packID uint32
|
||
|
recvID uint32
|
||
|
}
|
||
|
|
||
|
func newAuthChainA(b *Base) Protocol {
|
||
|
a := &authChainA{
|
||
|
Base: b,
|
||
|
authData: &authData{},
|
||
|
userData: &userData{},
|
||
|
salt: "auth_chain_a",
|
||
|
}
|
||
|
a.initUserData()
|
||
|
return a
|
||
|
}
|
||
|
|
||
|
func (a *authChainA) initUserData() {
|
||
|
params := strings.Split(a.Param, ":")
|
||
|
if len(params) > 1 {
|
||
|
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
|
||
|
binary.LittleEndian.PutUint32(a.userID[:], uint32(userID))
|
||
|
a.userKey = []byte(params[1])
|
||
|
}
|
||
|
}
|
||
|
if len(a.userKey) == 0 {
|
||
|
a.userKey = a.Key
|
||
|
rand.Read(a.userID[:])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (a *authChainA) StreamConn(c net.Conn, iv []byte) net.Conn {
|
||
|
p := &authChainA{
|
||
|
Base: a.Base,
|
||
|
authData: a.next(),
|
||
|
userData: a.userData,
|
||
|
salt: a.salt,
|
||
|
packID: 1,
|
||
|
recvID: 1,
|
||
|
}
|
||
|
p.iv = iv
|
||
|
p.randDataLength = p.getRandLength
|
||
|
return &Conn{Conn: c, Protocol: p}
|
||
|
}
|
||
|
|
||
|
func (a *authChainA) PacketConn(c net.PacketConn) net.PacketConn {
|
||
|
p := &authChainA{
|
||
|
Base: a.Base,
|
||
|
salt: a.salt,
|
||
|
userData: a.userData,
|
||
|
}
|
||
|
return &PacketConn{PacketConn: c, Protocol: p}
|
||
|
}
|
||
|
|
||
|
func (a *authChainA) Decode(dst, src *bytes.Buffer) error {
|
||
|
if a.rawTrans {
|
||
|
dst.ReadFrom(src)
|
||
|
return nil
|
||
|
}
|
||
|
for src.Len() > 4 {
|
||
|
macKey := pool.Get(len(a.userKey) + 4)
|
||
|
defer pool.Put(macKey)
|
||
|
copy(macKey, a.userKey)
|
||
|
binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.recvID)
|
||
|
|
||
|
dataLength := int(binary.LittleEndian.Uint16(src.Bytes()[:2]) ^ binary.LittleEndian.Uint16(a.lastServerHash[14:16]))
|
||
|
randDataLength := a.randDataLength(dataLength, a.lastServerHash, &a.randomServer)
|
||
|
length := dataLength + randDataLength
|
||
|
|
||
|
if length >= 4096 {
|
||
|
a.rawTrans = true
|
||
|
src.Reset()
|
||
|
return errAuthChainLengthError
|
||
|
}
|
||
|
|
||
|
if 4+length > src.Len() {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
serverHash := tools.HmacMD5(macKey, src.Bytes()[:length+2])
|
||
|
if !bytes.Equal(serverHash[:2], src.Bytes()[length+2:length+4]) {
|
||
|
a.rawTrans = true
|
||
|
src.Reset()
|
||
|
return errAuthChainChksumError
|
||
|
}
|
||
|
a.lastServerHash = serverHash
|
||
|
|
||
|
pos := 2
|
||
|
if dataLength > 0 && randDataLength > 0 {
|
||
|
pos += getRandStartPos(randDataLength, &a.randomServer)
|
||
|
}
|
||
|
wantedData := src.Bytes()[pos : pos+dataLength]
|
||
|
a.decrypter.XORKeyStream(wantedData, wantedData)
|
||
|
if a.recvID == 1 {
|
||
|
dst.Write(wantedData[2:])
|
||
|
} else {
|
||
|
dst.Write(wantedData)
|
||
|
}
|
||
|
a.recvID++
|
||
|
src.Next(length + 4)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (a *authChainA) Encode(buf *bytes.Buffer, b []byte) error {
|
||
|
if !a.hasSentHeader {
|
||
|
dataLength := getDataLength(b)
|
||
|
a.packAuthData(buf, b[:dataLength])
|
||
|
b = b[dataLength:]
|
||
|
a.hasSentHeader = true
|
||
|
}
|
||
|
for len(b) > 2800 {
|
||
|
a.packData(buf, b[:2800])
|
||
|
b = b[2800:]
|
||
|
}
|
||
|
if len(b) > 0 {
|
||
|
a.packData(buf, b)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (a *authChainA) DecodePacket(b []byte) ([]byte, error) {
|
||
|
if len(b) < 9 {
|
||
|
return nil, errAuthChainLengthError
|
||
|
}
|
||
|
if !bytes.Equal(tools.HmacMD5(a.userKey, b[:len(b)-1])[:1], b[len(b)-1:]) {
|
||
|
return nil, errAuthChainChksumError
|
||
|
}
|
||
|
md5Data := tools.HmacMD5(a.Key, b[len(b)-8:len(b)-1])
|
||
|
|
||
|
randDataLength := udpGetRandLength(md5Data, &a.randomServer)
|
||
|
|
||
|
key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(md5Data), 16)
|
||
|
rc4Cipher, err := rc4.NewCipher(key)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
wantedData := b[:len(b)-8-randDataLength]
|
||
|
rc4Cipher.XORKeyStream(wantedData, wantedData)
|
||
|
return wantedData, nil
|
||
|
}
|
||
|
|
||
|
func (a *authChainA) EncodePacket(buf *bytes.Buffer, b []byte) error {
|
||
|
authData := pool.Get(3)
|
||
|
defer pool.Put(authData)
|
||
|
rand.Read(authData)
|
||
|
|
||
|
md5Data := tools.HmacMD5(a.Key, authData)
|
||
|
|
||
|
randDataLength := udpGetRandLength(md5Data, &a.randomClient)
|
||
|
|
||
|
key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(md5Data), 16)
|
||
|
rc4Cipher, err := rc4.NewCipher(key)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
rc4Cipher.XORKeyStream(b, b)
|
||
|
|
||
|
buf.Write(b)
|
||
|
tools.AppendRandBytes(buf, randDataLength)
|
||
|
buf.Write(authData)
|
||
|
binary.Write(buf, binary.LittleEndian, binary.LittleEndian.Uint32(a.userID[:])^binary.LittleEndian.Uint32(md5Data[:4]))
|
||
|
buf.Write(tools.HmacMD5(a.userKey, buf.Bytes())[:1])
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (a *authChainA) packAuthData(poolBuf *bytes.Buffer, data []byte) {
|
||
|
/*
|
||
|
dataLength := len(data)
|
||
|
12: checkHead(4) and hmac of checkHead(8)
|
||
|
4: uint32 LittleEndian uid (uid = userID ^ last client hash)
|
||
|
16: encrypted data of authdata(12), uint16 LittleEndian overhead(2) and uint16 LittleEndian number zero(2)
|
||
|
4: last server hash(4)
|
||
|
packedAuthDataLength := 12 + 4 + 16 + 4 + dataLength
|
||
|
*/
|
||
|
|
||
|
macKey := pool.Get(len(a.iv) + len(a.Key))
|
||
|
defer pool.Put(macKey)
|
||
|
copy(macKey, a.iv)
|
||
|
copy(macKey[len(a.iv):], a.Key)
|
||
|
|
||
|
// check head
|
||
|
tools.AppendRandBytes(poolBuf, 4)
|
||
|
a.lastClientHash = tools.HmacMD5(macKey, poolBuf.Bytes())
|
||
|
a.initRC4Cipher()
|
||
|
poolBuf.Write(a.lastClientHash[:8])
|
||
|
// uid
|
||
|
binary.Write(poolBuf, binary.LittleEndian, binary.LittleEndian.Uint32(a.userID[:])^binary.LittleEndian.Uint32(a.lastClientHash[8:12]))
|
||
|
// encrypted data
|
||
|
err := a.putEncryptedData(poolBuf, a.userKey, [2]int{a.Overhead, 0}, a.salt)
|
||
|
if err != nil {
|
||
|
poolBuf.Reset()
|
||
|
return
|
||
|
}
|
||
|
// last server hash
|
||
|
a.lastServerHash = tools.HmacMD5(a.userKey, poolBuf.Bytes()[12:])
|
||
|
poolBuf.Write(a.lastServerHash[:4])
|
||
|
// packed data
|
||
|
a.packData(poolBuf, data)
|
||
|
}
|
||
|
|
||
|
func (a *authChainA) packData(poolBuf *bytes.Buffer, data []byte) {
|
||
|
a.encrypter.XORKeyStream(data, data)
|
||
|
|
||
|
macKey := pool.Get(len(a.userKey) + 4)
|
||
|
defer pool.Put(macKey)
|
||
|
copy(macKey, a.userKey)
|
||
|
binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.packID)
|
||
|
a.packID++
|
||
|
|
||
|
length := uint16(len(data)) ^ binary.LittleEndian.Uint16(a.lastClientHash[14:16])
|
||
|
|
||
|
originalLength := poolBuf.Len()
|
||
|
binary.Write(poolBuf, binary.LittleEndian, length)
|
||
|
a.putMixedRandDataAndData(poolBuf, data)
|
||
|
a.lastClientHash = tools.HmacMD5(macKey, poolBuf.Bytes()[originalLength:])
|
||
|
poolBuf.Write(a.lastClientHash[:2])
|
||
|
}
|
||
|
|
||
|
func (a *authChainA) putMixedRandDataAndData(poolBuf *bytes.Buffer, data []byte) {
|
||
|
randDataLength := a.randDataLength(len(data), a.lastClientHash, &a.randomClient)
|
||
|
if len(data) == 0 {
|
||
|
tools.AppendRandBytes(poolBuf, randDataLength)
|
||
|
return
|
||
|
}
|
||
|
if randDataLength > 0 {
|
||
|
startPos := getRandStartPos(randDataLength, &a.randomClient)
|
||
|
tools.AppendRandBytes(poolBuf, startPos)
|
||
|
poolBuf.Write(data)
|
||
|
tools.AppendRandBytes(poolBuf, randDataLength-startPos)
|
||
|
return
|
||
|
}
|
||
|
poolBuf.Write(data)
|
||
|
}
|
||
|
|
||
|
func getRandStartPos(length int, random *tools.XorShift128Plus) int {
|
||
|
if length == 0 {
|
||
|
return 0
|
||
|
}
|
||
|
return int(int64(random.Next()%8589934609) % int64(length))
|
||
|
}
|
||
|
|
||
|
func (a *authChainA) getRandLength(length int, lastHash []byte, random *tools.XorShift128Plus) int {
|
||
|
if length > 1440 {
|
||
|
return 0
|
||
|
}
|
||
|
random.InitFromBinAndLength(lastHash, length)
|
||
|
if length > 1300 {
|
||
|
return int(random.Next() % 31)
|
||
|
}
|
||
|
if length > 900 {
|
||
|
return int(random.Next() % 127)
|
||
|
}
|
||
|
if length > 400 {
|
||
|
return int(random.Next() % 521)
|
||
|
}
|
||
|
return int(random.Next() % 1021)
|
||
|
}
|
||
|
|
||
|
func (a *authChainA) initRC4Cipher() {
|
||
|
key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(a.lastClientHash), 16)
|
||
|
a.encrypter, _ = rc4.NewCipher(key)
|
||
|
a.decrypter, _ = rc4.NewCipher(key)
|
||
|
}
|
||
|
|
||
|
func udpGetRandLength(lastHash []byte, random *tools.XorShift128Plus) int {
|
||
|
random.InitFromBin(lastHash)
|
||
|
return int(random.Next() % 127)
|
||
|
}
|