mirror of
https://github.com/SagerNet/sing.git
synced 2025-04-07 05:47:38 +03:00
Add shadowsocks 2022 EIH support
This commit is contained in:
parent
0b4282f72b
commit
603c62165e
2 changed files with 133 additions and 31 deletions
|
@ -167,13 +167,17 @@ func NewLocalClient(f *flags) (*LocalClient, error) {
|
||||||
if f.Method == shadowsocks.MethodNone {
|
if f.Method == shadowsocks.MethodNone {
|
||||||
client.method = shadowsocks.NewNone()
|
client.method = shadowsocks.NewNone()
|
||||||
} else {
|
} else {
|
||||||
var key []byte
|
var pskList [][]byte
|
||||||
if f.Key != "" {
|
if f.Key != "" {
|
||||||
decoded, err := base64.StdEncoding.DecodeString(f.Key)
|
keyStrList := strings.Split(f.Key, ":")
|
||||||
if err != nil {
|
pskList = make([][]byte, len(keyStrList))
|
||||||
return nil, E.Cause(err, "decode key")
|
for i, keyStr := range keyStrList {
|
||||||
|
key, err := base64.StdEncoding.DecodeString(keyStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode key")
|
||||||
|
}
|
||||||
|
pskList[i] = key
|
||||||
}
|
}
|
||||||
key = decoded
|
|
||||||
}
|
}
|
||||||
var rng io.Reader
|
var rng io.Reader
|
||||||
if f.UseSystemRNG {
|
if f.UseSystemRNG {
|
||||||
|
@ -185,13 +189,16 @@ func NewLocalClient(f *flags) (*LocalClient, error) {
|
||||||
rng = &shadowsocks.ReducedEntropyReader{Reader: rng}
|
rng = &shadowsocks.ReducedEntropyReader{Reader: rng}
|
||||||
}
|
}
|
||||||
if common.Contains(shadowaead.List, f.Method) {
|
if common.Contains(shadowaead.List, f.Method) {
|
||||||
method, err := shadowaead.New(f.Method, key, []byte(f.Password), rng, false)
|
if len(pskList) > 1 {
|
||||||
|
return nil, shadowaead.ErrBadKey
|
||||||
|
}
|
||||||
|
method, err := shadowaead.New(f.Method, pskList[0], []byte(f.Password), rng, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
client.method = method
|
client.method = method
|
||||||
} else if common.Contains(shadowaead_2022.List, f.Method) {
|
} else if common.Contains(shadowaead_2022.List, f.Method) {
|
||||||
method, err := shadowaead_2022.New(f.Method, key, rng)
|
method, err := shadowaead_2022.New(f.Method, pskList, rng)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/sagernet/sing/protocol/shadowsocks"
|
"github.com/sagernet/sing/protocol/shadowsocks"
|
||||||
"github.com/sagernet/sing/protocol/shadowsocks/shadowaead"
|
"github.com/sagernet/sing/protocol/shadowsocks/shadowaead"
|
||||||
"github.com/sagernet/sing/protocol/socks"
|
"github.com/sagernet/sing/protocol/socks"
|
||||||
|
"golang.org/x/crypto/chacha20"
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
wgReplay "golang.zx2c4.com/wireguard/replay"
|
wgReplay "golang.zx2c4.com/wireguard/replay"
|
||||||
"lukechampine.com/blake3"
|
"lukechampine.com/blake3"
|
||||||
|
@ -64,39 +65,57 @@ var List = []string{
|
||||||
"2022-blake3-chacha20-poly1305",
|
"2022-blake3-chacha20-poly1305",
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(method string, psk []byte, secureRNG io.Reader) (shadowsocks.Method, error) {
|
func New(method string, pskList [][]byte, secureRNG io.Reader) (shadowsocks.Method, error) {
|
||||||
m := &Method{
|
m := &Method{
|
||||||
name: method,
|
name: method,
|
||||||
key: psk,
|
psk: pskList[len(pskList)-1],
|
||||||
|
pskList: pskList,
|
||||||
secureRNG: secureRNG,
|
secureRNG: secureRNG,
|
||||||
replayFilter: replay.NewCuckoo(60),
|
replayFilter: replay.NewCuckoo(60),
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(psk) != KeySaltSize {
|
for _, psk := range pskList {
|
||||||
return nil, shadowaead.ErrBadKey
|
if len(psk) != KeySaltSize {
|
||||||
|
return nil, shadowaead.ErrBadKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pskList) > 1 {
|
||||||
|
pskHash := make([]byte, len(pskList)-1*aes.BlockSize)
|
||||||
|
for i, psk := range pskList {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hash := blake3.Sum512(psk)
|
||||||
|
copy(pskHash[aes.BlockSize*(i-1):aes.BlockSize*i], hash[:aes.BlockSize])
|
||||||
|
}
|
||||||
|
m.pskHash = pskHash
|
||||||
}
|
}
|
||||||
|
|
||||||
switch method {
|
switch method {
|
||||||
case "2022-blake3-aes-128-gcm":
|
case "2022-blake3-aes-128-gcm":
|
||||||
m.keyLength = 16
|
m.keyLength = 16
|
||||||
m.constructor = newAESGCM
|
m.constructor = newAESGCM
|
||||||
m.udpBlockCipher = newAES(psk)
|
m.blockConstructor = newAES
|
||||||
|
m.udpBlockCipher = newAES(m.psk)
|
||||||
case "2022-blake3-aes-256-gcm":
|
case "2022-blake3-aes-256-gcm":
|
||||||
m.keyLength = 32
|
m.keyLength = 32
|
||||||
m.constructor = newAESGCM
|
m.constructor = newAESGCM
|
||||||
m.udpBlockCipher = newAES(psk)
|
m.blockConstructor = newAES
|
||||||
|
m.udpBlockCipher = newAES(m.psk)
|
||||||
case "2022-blake3-chacha20-poly1305":
|
case "2022-blake3-chacha20-poly1305":
|
||||||
m.keyLength = 32
|
m.keyLength = 32
|
||||||
m.constructor = newChacha20Poly1305
|
m.constructor = newChacha20Poly1305
|
||||||
m.udpCipher = newXChacha20Poly1305(psk)
|
m.streamConstructor = newChacha20
|
||||||
|
m.udpCipher = newXChacha20Poly1305(m.psk)
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Blake3DeriveKey(secret, salt []byte, keyLength int) []byte {
|
func Blake3DeriveKey(psk, salt []byte, keyLength int) []byte {
|
||||||
sessionKey := make([]byte, 2*KeySaltSize)
|
sessionKey := make([]byte, 2*KeySaltSize)
|
||||||
copy(sessionKey, secret)
|
copy(sessionKey, psk)
|
||||||
copy(sessionKey[len(secret):], salt)
|
copy(sessionKey[KeySaltSize:], salt)
|
||||||
outKey := buf.Make(keyLength)
|
outKey := buf.Make(keyLength)
|
||||||
blake3.DeriveKey(outKey, "shadowsocks 2022 session subkey", sessionKey)
|
blake3.DeriveKey(outKey, "shadowsocks 2022 session subkey", sessionKey)
|
||||||
return outKey
|
return outKey
|
||||||
|
@ -116,6 +135,12 @@ func newAESGCM(key []byte) cipher.AEAD {
|
||||||
return aead
|
return aead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newChacha20(key []byte) cipher.Stream {
|
||||||
|
_nonce := make([]byte, chacha20.NonceSize)
|
||||||
|
stream, _ := chacha20.NewUnauthenticatedCipher(key, common.Dup(_nonce))
|
||||||
|
return stream
|
||||||
|
}
|
||||||
|
|
||||||
func newChacha20Poly1305(key []byte) cipher.AEAD {
|
func newChacha20Poly1305(key []byte) cipher.AEAD {
|
||||||
cipher, err := chacha20poly1305.New(key)
|
cipher, err := chacha20poly1305.New(key)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
@ -129,14 +154,18 @@ func newXChacha20Poly1305(key []byte) cipher.AEAD {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Method struct {
|
type Method struct {
|
||||||
name string
|
name string
|
||||||
keyLength int
|
keyLength int
|
||||||
constructor func(key []byte) cipher.AEAD
|
constructor func(key []byte) cipher.AEAD
|
||||||
udpCipher cipher.AEAD
|
blockConstructor func(key []byte) cipher.Block
|
||||||
udpBlockCipher cipher.Block
|
streamConstructor func(key []byte) cipher.Stream
|
||||||
key []byte
|
udpCipher cipher.AEAD
|
||||||
secureRNG io.Reader
|
udpBlockCipher cipher.Block
|
||||||
replayFilter replay.Filter
|
psk []byte
|
||||||
|
pskList [][]byte
|
||||||
|
pskHash []byte
|
||||||
|
secureRNG io.Reader
|
||||||
|
replayFilter replay.Filter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Method) Name() string {
|
func (m *Method) Name() string {
|
||||||
|
@ -147,6 +176,30 @@ func (m *Method) KeyLength() int {
|
||||||
return m.keyLength
|
return m.keyLength
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Method) WriteExtendedIdentityHeaders(request *buf.Buffer, salt []byte) {
|
||||||
|
pskLen := len(m.pskList)
|
||||||
|
if pskLen < 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i, psk := range m.pskList {
|
||||||
|
keyMaterial := make([]byte, 2*KeySaltSize)
|
||||||
|
copy(keyMaterial, psk)
|
||||||
|
copy(keyMaterial[KeySaltSize:], salt)
|
||||||
|
_identitySubkey := buf.Make(m.keyLength)
|
||||||
|
identitySubkey := common.Dup(_identitySubkey)
|
||||||
|
blake3.DeriveKey(identitySubkey, "shadowsocks 2022 identity subkey", keyMaterial)
|
||||||
|
pskHash := m.pskHash[aes.BlockSize*i : aes.BlockSize*(i+1)]
|
||||||
|
if m.blockConstructor != nil {
|
||||||
|
m.blockConstructor(identitySubkey).Encrypt(request.Extend(16), pskHash)
|
||||||
|
} else {
|
||||||
|
m.streamConstructor(identitySubkey).XORKeyStream(request.Extend(16), pskHash)
|
||||||
|
}
|
||||||
|
if i == pskLen-2 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Method) DialConn(conn net.Conn, destination *M.AddrPort) (net.Conn, error) {
|
func (m *Method) DialConn(conn net.Conn, destination *M.AddrPort) (net.Conn, error) {
|
||||||
shadowsocksConn := &clientConn{
|
shadowsocksConn := &clientConn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
|
@ -190,15 +243,18 @@ func (c *clientConn) writeRequest(payload []byte) error {
|
||||||
salt := make([]byte, KeySaltSize)
|
salt := make([]byte, KeySaltSize)
|
||||||
common.Must1(io.ReadFull(c.method.secureRNG, salt))
|
common.Must1(io.ReadFull(c.method.secureRNG, salt))
|
||||||
common.Must1(request.Write(salt))
|
common.Must1(request.Write(salt))
|
||||||
|
c.method.WriteExtendedIdentityHeaders(request, salt)
|
||||||
|
|
||||||
var writer io.Writer = c.Conn
|
var writer io.Writer = c.Conn
|
||||||
writer = &buf.BufferedWriter{
|
writer = &buf.BufferedWriter{
|
||||||
Writer: writer,
|
Writer: writer,
|
||||||
Buffer: request,
|
Buffer: request,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
key := Blake3DeriveKey(c.method.psk, salt, c.method.keyLength)
|
||||||
writer = shadowaead.NewWriter(
|
writer = shadowaead.NewWriter(
|
||||||
writer,
|
writer,
|
||||||
c.method.constructor(Blake3DeriveKey(c.method.key, salt, c.method.keyLength)),
|
c.method.constructor(common.Dup(key)),
|
||||||
MaxPacketSize,
|
MaxPacketSize,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -271,9 +327,10 @@ func (c *clientConn) readResponse() error {
|
||||||
return E.New("salt not unique")
|
return E.New("salt not unique")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
key := Blake3DeriveKey(c.method.psk, salt, c.method.keyLength)
|
||||||
reader := shadowaead.NewReader(
|
reader := shadowaead.NewReader(
|
||||||
c.Conn,
|
c.Conn,
|
||||||
c.method.constructor(Blake3DeriveKey(c.method.key, salt, c.method.keyLength)),
|
c.method.constructor(common.Dup(key)),
|
||||||
MaxPacketSize,
|
MaxPacketSize,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -361,12 +418,48 @@ type clientPacketConn struct {
|
||||||
func (c *clientPacketConn) WritePacket(buffer *buf.Buffer, destination *M.AddrPort) error {
|
func (c *clientPacketConn) WritePacket(buffer *buf.Buffer, destination *M.AddrPort) error {
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
header := buf.New()
|
header := buf.New()
|
||||||
|
pskLen := len(c.method.pskList)
|
||||||
|
var dataIndex int
|
||||||
if c.method.udpCipher != nil {
|
if c.method.udpCipher != nil {
|
||||||
common.Must1(header.ReadFullFrom(c.method.secureRNG, PacketNonceSize))
|
common.Must1(header.ReadFullFrom(c.method.secureRNG, PacketNonceSize))
|
||||||
|
if pskLen > 1 {
|
||||||
|
for i, psk := range c.method.pskList {
|
||||||
|
pskHash := c.method.pskHash[aes.BlockSize*i : aes.BlockSize*(i+1)]
|
||||||
|
identityHeader := header.Extend(aes.BlockSize)
|
||||||
|
for textI := 0; textI < aes.BlockSize; textI++ {
|
||||||
|
identityHeader[textI] = pskHash[textI] ^ header.Byte(textI)
|
||||||
|
}
|
||||||
|
c.method.streamConstructor(psk).XORKeyStream(identityHeader, identityHeader)
|
||||||
|
if i == pskLen-2 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataIndex = buffer.Len()
|
||||||
|
} else {
|
||||||
|
dataIndex = aes.BlockSize
|
||||||
}
|
}
|
||||||
|
|
||||||
common.Must(
|
common.Must(
|
||||||
binary.Write(header, binary.BigEndian, c.session.sessionId),
|
binary.Write(header, binary.BigEndian, c.session.sessionId),
|
||||||
binary.Write(header, binary.BigEndian, c.session.nextPacketId()),
|
binary.Write(header, binary.BigEndian, c.session.nextPacketId()),
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.method.udpCipher == nil && pskLen > 1 {
|
||||||
|
for i, psk := range c.method.pskList {
|
||||||
|
dataIndex += aes.BlockSize
|
||||||
|
pskHash := c.method.pskHash[aes.BlockSize*i : aes.BlockSize*(i+1)]
|
||||||
|
identityHeader := header.Extend(aes.BlockSize)
|
||||||
|
for textI := 0; textI < aes.BlockSize; textI++ {
|
||||||
|
identityHeader[textI] = pskHash[textI] ^ header.Byte(textI)
|
||||||
|
}
|
||||||
|
c.method.blockConstructor(psk).Encrypt(identityHeader, identityHeader)
|
||||||
|
if i == pskLen-2 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
common.Must(
|
||||||
header.WriteByte(HeaderTypeClient),
|
header.WriteByte(HeaderTypeClient),
|
||||||
binary.Write(header, binary.BigEndian, uint64(time.Now().Unix())),
|
binary.Write(header, binary.BigEndian, uint64(time.Now().Unix())),
|
||||||
binary.Write(header, binary.BigEndian, uint16(0)), // padding length
|
binary.Write(header, binary.BigEndian, uint16(0)), // padding length
|
||||||
|
@ -377,11 +470,11 @@ func (c *clientPacketConn) WritePacket(buffer *buf.Buffer, destination *M.AddrPo
|
||||||
}
|
}
|
||||||
buffer = buffer.WriteBufferAtFirst(header)
|
buffer = buffer.WriteBufferAtFirst(header)
|
||||||
if c.method.udpCipher != nil {
|
if c.method.udpCipher != nil {
|
||||||
c.method.udpCipher.Seal(buffer.Index(PacketNonceSize), buffer.To(PacketNonceSize), buffer.From(PacketNonceSize), nil)
|
c.method.udpCipher.Seal(buffer.Index(dataIndex), buffer.To(dataIndex), buffer.From(dataIndex), nil)
|
||||||
buffer.Extend(c.method.udpCipher.Overhead())
|
buffer.Extend(c.method.udpCipher.Overhead())
|
||||||
} else {
|
} else {
|
||||||
packetHeader := buffer.To(aes.BlockSize)
|
packetHeader := buffer.To(aes.BlockSize)
|
||||||
c.session.cipher.Seal(buffer.Index(aes.BlockSize), packetHeader[4:16], buffer.From(aes.BlockSize), nil)
|
c.session.cipher.Seal(buffer.Index(dataIndex), packetHeader[4:16], buffer.From(dataIndex), nil)
|
||||||
buffer.Extend(c.session.cipher.Overhead())
|
buffer.Extend(c.session.cipher.Overhead())
|
||||||
c.method.udpBlockCipher.Encrypt(packetHeader, packetHeader)
|
c.method.udpBlockCipher.Encrypt(packetHeader, packetHeader)
|
||||||
}
|
}
|
||||||
|
@ -424,7 +517,8 @@ func (c *clientPacketConn) ReadPacket(buffer *buf.Buffer) (*M.AddrPort, error) {
|
||||||
} else if sessionId == c.session.lastRemoteSessionId {
|
} else if sessionId == c.session.lastRemoteSessionId {
|
||||||
remoteCipher = c.session.lastRemoteCipher
|
remoteCipher = c.session.lastRemoteCipher
|
||||||
} else {
|
} else {
|
||||||
remoteCipher = c.method.constructor(Blake3DeriveKey(c.method.key, packetHeader[:8], c.method.keyLength))
|
key := Blake3DeriveKey(c.method.psk, packetHeader[:8], c.method.keyLength)
|
||||||
|
remoteCipher = c.method.constructor(common.Dup(key))
|
||||||
}
|
}
|
||||||
_, err = remoteCipher.Open(buffer.Index(0), packetHeader[4:16], buffer.Bytes(), nil)
|
_, err = remoteCipher.Open(buffer.Index(0), packetHeader[4:16], buffer.Bytes(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -522,7 +616,8 @@ func (m *Method) newUDPSession() *udpSession {
|
||||||
if m.udpCipher == nil {
|
if m.udpCipher == nil {
|
||||||
sessionId := make([]byte, 8)
|
sessionId := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(sessionId, session.sessionId)
|
binary.BigEndian.PutUint64(sessionId, session.sessionId)
|
||||||
session.cipher = m.constructor(Blake3DeriveKey(m.key, sessionId, m.keyLength))
|
key := Blake3DeriveKey(m.psk, sessionId, m.keyLength)
|
||||||
|
session.cipher = m.constructor(common.Dup(key))
|
||||||
}
|
}
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue