mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-28 17:51:50 +03:00
Add OpenVPN, TrustTunnel, Sudoku, inbound managers. Fixes
This commit is contained in:
250
transport/openvpn/keymethod.go
Normal file
250
transport/openvpn/keymethod.go
Normal file
@@ -0,0 +1,250 @@
|
||||
package openvpn
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
)
|
||||
|
||||
const (
|
||||
KeyMethod2 = 2
|
||||
|
||||
keySourcePreMasterSize = 48
|
||||
keySourceRandomSize = 32
|
||||
|
||||
maxCipherKeyLength = 64
|
||||
maxHMACKeyLength = 64
|
||||
keyBlockSize = 2 * (maxCipherKeyLength + maxHMACKeyLength)
|
||||
|
||||
keyExpansionID = "OpenVPN"
|
||||
)
|
||||
|
||||
type KeySource struct {
|
||||
PreMaster [keySourcePreMasterSize]byte
|
||||
Random1 [keySourceRandomSize]byte
|
||||
Random2 [keySourceRandomSize]byte
|
||||
}
|
||||
|
||||
type KeySource2 struct {
|
||||
Client KeySource
|
||||
Server KeySource
|
||||
}
|
||||
|
||||
type KeyMaterial struct {
|
||||
SendCipherKey []byte
|
||||
SendHMACKey []byte
|
||||
RecvCipherKey []byte
|
||||
RecvHMACKey []byte
|
||||
}
|
||||
|
||||
type KeyMethod2Record struct {
|
||||
Sources KeySource2
|
||||
Options string
|
||||
Username string
|
||||
Password string
|
||||
PeerInfo string
|
||||
}
|
||||
|
||||
func NewClientKeyMethod2Record(options, peerInfo, username, password string) (*KeyMethod2Record, error) {
|
||||
var record KeyMethod2Record
|
||||
if _, err := rand.Read(record.Sources.Client.PreMaster[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := rand.Read(record.Sources.Client.Random1[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := rand.Read(record.Sources.Client.Random2[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
record.Options = options
|
||||
record.PeerInfo = peerInfo
|
||||
record.Username = username
|
||||
record.Password = password
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
func (r *KeyMethod2Record) MarshalClient() ([]byte, error) {
|
||||
if r == nil {
|
||||
return nil, errors.New("nil key method 2 record")
|
||||
}
|
||||
out := make([]byte, 0, 4+1+keySourcePreMasterSize+keySourceRandomSize*2+len(r.Options)+16)
|
||||
out = binary.BigEndian.AppendUint32(out, 0)
|
||||
out = append(out, KeyMethod2)
|
||||
out = append(out, r.Sources.Client.PreMaster[:]...)
|
||||
out = append(out, r.Sources.Client.Random1[:]...)
|
||||
out = append(out, r.Sources.Client.Random2[:]...)
|
||||
out = appendOpenVPNString(out, r.Options)
|
||||
out = appendOpenVPNString(out, r.Username)
|
||||
out = appendOpenVPNString(out, r.Password)
|
||||
out = appendOpenVPNString(out, r.PeerInfo)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func ParseServerKeyMethod2Record(packet []byte) (*KeyMethod2Record, error) {
|
||||
if len(packet) < 4+1+keySourceRandomSize*2 {
|
||||
return nil, errors.New("key method 2 packet too short")
|
||||
}
|
||||
if binary.BigEndian.Uint32(packet[:4]) != 0 {
|
||||
return nil, errors.New("invalid key method 2 prefix")
|
||||
}
|
||||
if packet[4]&0x0f != KeyMethod2 {
|
||||
return nil, fmt.Errorf("unsupported key method %d", packet[4])
|
||||
}
|
||||
offset := 5
|
||||
record := &KeyMethod2Record{}
|
||||
copy(record.Sources.Server.Random1[:], packet[offset:offset+keySourceRandomSize])
|
||||
offset += keySourceRandomSize
|
||||
copy(record.Sources.Server.Random2[:], packet[offset:offset+keySourceRandomSize])
|
||||
offset += keySourceRandomSize
|
||||
|
||||
var err error
|
||||
record.Options, offset, err = readOpenVPNString(packet, offset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read options: %w", err)
|
||||
}
|
||||
record.Username, offset, _ = readOpenVPNString(packet, offset)
|
||||
record.Password, offset, _ = readOpenVPNString(packet, offset)
|
||||
record.PeerInfo, _, _ = readOpenVPNString(packet, offset)
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func DeriveClientKeyMaterial(sources KeySource2, clientSession, serverSession SessionID, cipherKeyLen int) (*KeyMaterial, error) {
|
||||
if cipherKeyLen != 16 && cipherKeyLen != 32 {
|
||||
return nil, fmt.Errorf("unsupported data cipher key length %d", cipherKeyLen)
|
||||
}
|
||||
var master [48]byte
|
||||
if err := openvpnPRF(
|
||||
sources.Client.PreMaster[:],
|
||||
keyExpansionID+" master secret",
|
||||
sources.Client.Random1[:],
|
||||
sources.Server.Random1[:],
|
||||
nil,
|
||||
nil,
|
||||
master[:],
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyBlock := make([]byte, keyBlockSize)
|
||||
if err := openvpnPRF(
|
||||
master[:],
|
||||
keyExpansionID+" key expansion",
|
||||
sources.Client.Random2[:],
|
||||
sources.Server.Random2[:],
|
||||
clientSession[:],
|
||||
serverSession[:],
|
||||
keyBlock,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientToServer := keyBlock[:maxCipherKeyLength+maxHMACKeyLength]
|
||||
serverToClient := keyBlock[maxCipherKeyLength+maxHMACKeyLength:]
|
||||
return &KeyMaterial{
|
||||
SendCipherKey: cloneBytes(clientToServer[:cipherKeyLen]),
|
||||
SendHMACKey: cloneBytes(clientToServer[maxCipherKeyLength : maxCipherKeyLength+maxHMACKeyLength]),
|
||||
RecvCipherKey: cloneBytes(serverToClient[:cipherKeyLen]),
|
||||
RecvHMACKey: cloneBytes(serverToClient[maxCipherKeyLength : maxCipherKeyLength+maxHMACKeyLength]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func InstallScriptOptionsString(proto, cipher, auth string) string {
|
||||
protoName := "UDPv4"
|
||||
if proto == ProtoTCP {
|
||||
protoName = "TCPv4_CLIENT"
|
||||
}
|
||||
keysize := "128"
|
||||
switch cipher {
|
||||
case CipherAES192GCM, CipherAES192CBC:
|
||||
keysize = "192"
|
||||
case CipherAES256GCM, CipherAES256CBC, CipherCHACHA20POLY:
|
||||
keysize = "256"
|
||||
}
|
||||
return fmt.Sprintf("V4,dev-type tun,link-mtu 1550,tun-mtu 1500,proto %s,cipher %s,auth %s,keysize %s,key-method 2,tls-client", protoName, cipher, auth, keysize)
|
||||
}
|
||||
|
||||
func InstallScriptPeerInfo(cipher string) string {
|
||||
if cipher != "" {
|
||||
return "IV_VER=sing-box-openvpn\nIV_PROTO=6\nIV_CIPHERS=" + cipher + "\n"
|
||||
}
|
||||
return "IV_VER=sing-box-openvpn\nIV_PROTO=6\nIV_CIPHERS=AES-256-GCM:AES-192-GCM:AES-128-GCM:AES-256-CBC:AES-192-CBC:AES-128-CBC:CHACHA20-POLY1305\n"
|
||||
}
|
||||
|
||||
func appendOpenVPNString(out []byte, s string) []byte {
|
||||
if s == "" {
|
||||
return binary.BigEndian.AppendUint16(out, 0)
|
||||
}
|
||||
if len(s)+1 > 0xffff {
|
||||
s = s[:0xfffe]
|
||||
}
|
||||
out = binary.BigEndian.AppendUint16(out, uint16(len(s)+1))
|
||||
out = append(out, s...)
|
||||
out = append(out, 0)
|
||||
return out
|
||||
}
|
||||
|
||||
func readOpenVPNString(packet []byte, offset int) (string, int, error) {
|
||||
if offset+2 > len(packet) {
|
||||
return "", offset, ioStringEOF
|
||||
}
|
||||
size := int(binary.BigEndian.Uint16(packet[offset : offset+2]))
|
||||
offset += 2
|
||||
if size == 0 {
|
||||
return "", offset, nil
|
||||
}
|
||||
if offset+size > len(packet) {
|
||||
return "", offset, ioStringEOF
|
||||
}
|
||||
raw := packet[offset : offset+size]
|
||||
offset += size
|
||||
if raw[len(raw)-1] == 0 {
|
||||
raw = raw[:len(raw)-1]
|
||||
}
|
||||
return string(raw), offset, nil
|
||||
}
|
||||
|
||||
var ioStringEOF = errors.New("openvpn string truncated")
|
||||
|
||||
func openvpnPRF(secret []byte, label string, clientSeed, serverSeed, clientSession, serverSession []byte, out []byte) error {
|
||||
seed := make([]byte, 0, len(label)+len(clientSeed)+len(serverSeed)+len(clientSession)+len(serverSession))
|
||||
seed = append(seed, label...)
|
||||
seed = append(seed, clientSeed...)
|
||||
seed = append(seed, serverSeed...)
|
||||
seed = append(seed, clientSession...)
|
||||
seed = append(seed, serverSession...)
|
||||
|
||||
split := (len(secret) + 1) / 2
|
||||
s1 := secret[:split]
|
||||
s2 := secret[len(secret)-split:]
|
||||
|
||||
md5Out := pHash(md5.New, s1, seed, len(out))
|
||||
sha1Out := pHash(sha1.New, s2, seed, len(out))
|
||||
for i := range out {
|
||||
out[i] = md5Out[i] ^ sha1Out[i]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pHash(newHash func() hash.Hash, secret, seed []byte, size int) []byte {
|
||||
out := make([]byte, 0, size)
|
||||
a := hmacSum(newHash, secret, seed)
|
||||
for len(out) < size {
|
||||
chunkInput := make([]byte, 0, len(a)+len(seed))
|
||||
chunkInput = append(chunkInput, a...)
|
||||
chunkInput = append(chunkInput, seed...)
|
||||
out = append(out, hmacSum(newHash, secret, chunkInput)...)
|
||||
a = hmacSum(newHash, secret, a)
|
||||
}
|
||||
return out[:size]
|
||||
}
|
||||
|
||||
func hmacSum(newHash func() hash.Hash, key, data []byte) []byte {
|
||||
mac := hmac.New(newHash, key)
|
||||
_, _ = mac.Write(data)
|
||||
return mac.Sum(nil)
|
||||
}
|
||||
Reference in New Issue
Block a user