33a6579a3a
* Refactor ssr stream cipher to expose iv and key References: https://github.com/Dreamacro/go-shadowsocks2 https://github.com/sh4d0wfiend/go-shadowsocksr2 * Implement ssr obfs Reference: https://github.com/mzz2017/shadowsocksR * Implement ssr protocol References: https://github.com/mzz2017/shadowsocksR https://github.com/shadowsocksRb/shadowsocksr-libev https://github.com/shadowsocksr-backup/shadowsocksr
253 lines
6 KiB
Go
253 lines
6 KiB
Go
package protocol
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"hash/adler32"
|
|
"hash/crc32"
|
|
"math/rand"
|
|
"time"
|
|
|
|
"github.com/Dreamacro/clash/common/pool"
|
|
"github.com/Dreamacro/clash/component/ssr/tools"
|
|
)
|
|
|
|
type authSHA1V4 struct {
|
|
*Base
|
|
*authData
|
|
headerSent bool
|
|
buffer bytes.Buffer
|
|
}
|
|
|
|
func init() {
|
|
register("auth_sha1_v4", newAuthSHA1V4)
|
|
}
|
|
|
|
func newAuthSHA1V4(b *Base) Protocol {
|
|
return &authSHA1V4{Base: b, authData: &authData{}}
|
|
}
|
|
|
|
func (a *authSHA1V4) initForConn(iv []byte) Protocol {
|
|
return &authSHA1V4{
|
|
Base: &Base{
|
|
IV: iv,
|
|
Key: a.Key,
|
|
TCPMss: a.TCPMss,
|
|
Overhead: a.Overhead,
|
|
Param: a.Param,
|
|
},
|
|
authData: a.authData,
|
|
}
|
|
}
|
|
|
|
func (a *authSHA1V4) GetProtocolOverhead() int {
|
|
return 7
|
|
}
|
|
|
|
func (a *authSHA1V4) SetOverhead(overhead int) {
|
|
a.Overhead = overhead
|
|
}
|
|
|
|
func (a *authSHA1V4) Decode(b []byte) ([]byte, int, error) {
|
|
a.buffer.Reset()
|
|
bSize := len(b)
|
|
originalSize := bSize
|
|
for bSize > 4 {
|
|
crc := crc32.ChecksumIEEE(b[:2]) & 0xFFFF
|
|
if binary.LittleEndian.Uint16(b[2:4]) != uint16(crc) {
|
|
return nil, 0, errAuthSHA1v4CRC32Error
|
|
}
|
|
length := int(binary.BigEndian.Uint16(b[:2]))
|
|
if length >= 8192 || length < 8 {
|
|
return nil, 0, errAuthSHA1v4DataLengthError
|
|
}
|
|
if length > bSize {
|
|
break
|
|
}
|
|
|
|
if adler32.Checksum(b[:length-4]) == binary.LittleEndian.Uint32(b[length-4:]) {
|
|
pos := int(b[4])
|
|
if pos != 0xFF {
|
|
pos += 4
|
|
} else {
|
|
pos = int(binary.BigEndian.Uint16(b[5:5+2])) + 4
|
|
}
|
|
retSize := length - pos - 4
|
|
a.buffer.Write(b[pos : pos+retSize])
|
|
bSize -= length
|
|
b = b[length:]
|
|
} else {
|
|
return nil, 0, errAuthSHA1v4IncorrectChecksum
|
|
}
|
|
}
|
|
return a.buffer.Bytes(), originalSize - bSize, nil
|
|
}
|
|
|
|
func (a *authSHA1V4) Encode(b []byte) ([]byte, error) {
|
|
a.buffer.Reset()
|
|
bSize := len(b)
|
|
offset := 0
|
|
if !a.headerSent && bSize > 0 {
|
|
headSize := getHeadSize(b, 30)
|
|
if headSize > bSize {
|
|
headSize = bSize
|
|
}
|
|
a.buffer.Write(a.packAuthData(b[:headSize]))
|
|
offset += headSize
|
|
bSize -= headSize
|
|
a.headerSent = true
|
|
}
|
|
const blockSize = 4096
|
|
for bSize > blockSize {
|
|
packSize, randSize := a.packedDataSize(b[offset : offset+blockSize])
|
|
pack := pool.Get(packSize)
|
|
a.packData(b[offset:offset+blockSize], pack, randSize)
|
|
a.buffer.Write(pack)
|
|
pool.Put(pack)
|
|
offset += blockSize
|
|
bSize -= blockSize
|
|
}
|
|
if bSize > 0 {
|
|
packSize, randSize := a.packedDataSize(b[offset:])
|
|
pack := pool.Get(packSize)
|
|
a.packData(b[offset:], pack, randSize)
|
|
a.buffer.Write(pack)
|
|
pool.Put(pack)
|
|
}
|
|
return a.buffer.Bytes(), nil
|
|
}
|
|
|
|
func (a *authSHA1V4) DecodePacket(b []byte) ([]byte, int, error) {
|
|
return b, len(b), nil
|
|
}
|
|
|
|
func (a *authSHA1V4) EncodePacket(b []byte) ([]byte, error) {
|
|
return b, nil
|
|
}
|
|
|
|
func (a *authSHA1V4) packedDataSize(data []byte) (packSize, randSize int) {
|
|
dataSize := len(data)
|
|
randSize = 1
|
|
if dataSize <= 1300 {
|
|
if dataSize > 400 {
|
|
randSize += rand.Intn(128)
|
|
} else {
|
|
randSize += rand.Intn(1024)
|
|
}
|
|
}
|
|
packSize = randSize + dataSize + 8
|
|
return
|
|
}
|
|
|
|
func (a *authSHA1V4) packData(data, ret []byte, randSize int) {
|
|
dataSize := len(data)
|
|
retSize := len(ret)
|
|
// 0~1, ret size
|
|
binary.BigEndian.PutUint16(ret[:2], uint16(retSize&0xFFFF))
|
|
// 2~3, crc of ret size
|
|
crc := crc32.ChecksumIEEE(ret[:2]) & 0xFFFF
|
|
binary.LittleEndian.PutUint16(ret[2:4], uint16(crc))
|
|
// 4, rand size
|
|
if randSize < 128 {
|
|
ret[4] = uint8(randSize & 0xFF)
|
|
} else {
|
|
ret[4] = uint8(0xFF)
|
|
binary.BigEndian.PutUint16(ret[5:7], uint16(randSize&0xFFFF))
|
|
}
|
|
// (rand size+4)~(ret size-4), data
|
|
if dataSize > 0 {
|
|
copy(ret[randSize+4:], data)
|
|
}
|
|
// (ret size-4)~end, adler32 of full data
|
|
adler := adler32.Checksum(ret[:retSize-4])
|
|
binary.LittleEndian.PutUint32(ret[retSize-4:], adler)
|
|
}
|
|
|
|
func (a *authSHA1V4) packAuthData(data []byte) (ret []byte) {
|
|
dataSize := len(data)
|
|
randSize := 1
|
|
if dataSize <= 1300 {
|
|
if dataSize > 400 {
|
|
randSize += rand.Intn(128)
|
|
} else {
|
|
randSize += rand.Intn(1024)
|
|
}
|
|
}
|
|
dataOffset := randSize + 4 + 2
|
|
retSize := dataOffset + dataSize + 12 + tools.HmacSHA1Len
|
|
ret = make([]byte, retSize)
|
|
a.mutex.Lock()
|
|
defer a.mutex.Unlock()
|
|
a.connectionID++
|
|
if a.connectionID > 0xFF000000 {
|
|
a.clientID = nil
|
|
}
|
|
if len(a.clientID) == 0 {
|
|
a.clientID = make([]byte, 8)
|
|
rand.Read(a.clientID)
|
|
b := make([]byte, 4)
|
|
rand.Read(b)
|
|
a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
|
}
|
|
// 0~1, ret size
|
|
binary.BigEndian.PutUint16(ret[:2], uint16(retSize&0xFFFF))
|
|
|
|
// 2~6, crc of (ret size+salt+key)
|
|
salt := []byte("auth_sha1_v4")
|
|
crcData := make([]byte, len(salt)+len(a.Key)+2)
|
|
copy(crcData[:2], ret[:2])
|
|
copy(crcData[2:], salt)
|
|
copy(crcData[2+len(salt):], a.Key)
|
|
crc := crc32.ChecksumIEEE(crcData) & 0xFFFFFFFF
|
|
// 2~6, crc of (ret size+salt+key)
|
|
binary.LittleEndian.PutUint32(ret[2:], crc)
|
|
// 6~(rand size+6), rand numbers
|
|
rand.Read(ret[dataOffset-randSize : dataOffset])
|
|
// 6, rand size
|
|
if randSize < 128 {
|
|
ret[6] = byte(randSize & 0xFF)
|
|
} else {
|
|
// 6, magic number 0xFF
|
|
ret[6] = 0xFF
|
|
// 7~8, rand size
|
|
binary.BigEndian.PutUint16(ret[7:9], uint16(randSize&0xFFFF))
|
|
}
|
|
// rand size+6~(rand size+10), time stamp
|
|
now := time.Now().Unix()
|
|
binary.LittleEndian.PutUint32(ret[dataOffset:dataOffset+4], uint32(now))
|
|
// rand size+10~(rand size+14), client ID
|
|
copy(ret[dataOffset+4:dataOffset+4+4], a.clientID[:4])
|
|
// rand size+14~(rand size+18), connection ID
|
|
binary.LittleEndian.PutUint32(ret[dataOffset+8:dataOffset+8+4], a.connectionID)
|
|
// rand size+18~(rand size+18)+data length, data
|
|
copy(ret[dataOffset+12:], data)
|
|
|
|
key := make([]byte, len(a.IV)+len(a.Key))
|
|
copy(key, a.IV)
|
|
copy(key[len(a.IV):], a.Key)
|
|
|
|
h := tools.HmacSHA1(key, ret[:retSize-tools.HmacSHA1Len])
|
|
// (ret size-10)~(ret size)/(rand size)+18+data length~end, hmac
|
|
copy(ret[retSize-tools.HmacSHA1Len:], h[:tools.HmacSHA1Len])
|
|
return ret
|
|
}
|
|
|
|
func getHeadSize(data []byte, defaultValue int) int {
|
|
if data == nil || len(data) < 2 {
|
|
return defaultValue
|
|
}
|
|
headType := data[0] & 0x07
|
|
switch headType {
|
|
case 1:
|
|
// IPv4 1+4+2
|
|
return 7
|
|
case 4:
|
|
// IPv6 1+16+2
|
|
return 19
|
|
case 3:
|
|
// domain name, variant length
|
|
return 4 + int(data[1])
|
|
}
|
|
|
|
return defaultValue
|
|
}
|