162 lines
5.1 KiB
Go
162 lines
5.1 KiB
Go
package faketls
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// Extensions inside ClientHello must total exactly 403 bytes (TelethonFakeTLS / MTProxy expectation).
|
|
const extensionsTotal = 403
|
|
|
|
// Fixed TLS 1.2 cipher list from telethon-faketls.
|
|
var cipherSuites = []byte{
|
|
0xfa, 0xfa, 0x13, 0x01, 0x13, 0x02, 0x13, 0x03, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
|
|
0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35,
|
|
}
|
|
|
|
var extBeforeKeyShare = []byte{
|
|
0x00, 0x17, 0x00, 0x00,
|
|
0xff, 0x01, 0x00, 0x01, 0x00,
|
|
0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0xba, 0xba, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18,
|
|
0x00, 0x0b, 0x00, 0x02, 0x01, 0x00,
|
|
0x00, 0x23, 0x00, 0x00,
|
|
0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31,
|
|
0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x0d, 0x00, 0x12, 0x00, 0x10, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01,
|
|
0x00, 0x12, 0x00, 0x00,
|
|
}
|
|
|
|
var keyShareHeader = []byte{0x00, 0x33, 0x00, 0x2b, 0x00, 0x29, 0xba, 0xba, 0x00, 0x01, 0x00, 0x00, 0x1d, 0x00, 0x20}
|
|
|
|
var extAfterKeyShare = []byte{
|
|
0x00, 0x2d, 0x00, 0x02, 0x01, 0x01,
|
|
0x00, 0x2b, 0x00, 0x0b, 0x0a, 0x9a, 0x9a, 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01,
|
|
0x00, 0x1b, 0x00, 0x03, 0x02, 0x00, 0x02,
|
|
0x1a, 0x1a, 0x00, 0x01, 0x00,
|
|
}
|
|
|
|
func sniExtension(domain []byte) []byte {
|
|
entry := make([]byte, 0, 3+len(domain))
|
|
entry = append(entry, 0x00)
|
|
entry = append(entry, byte(len(domain)>>8), byte(len(domain)))
|
|
entry = append(entry, domain...)
|
|
list := make([]byte, 0, 2+len(entry))
|
|
list = append(list, byte(len(entry)>>8), byte(len(entry)))
|
|
list = append(list, entry...)
|
|
out := make([]byte, 0, 4+len(list))
|
|
out = append(out, 0x00, 0x00)
|
|
out = append(out, byte(len(list)>>8), byte(len(list)))
|
|
out = append(out, list...)
|
|
return out
|
|
}
|
|
|
|
func buildExtensions(domain []byte, pubKey []byte) ([]byte, error) {
|
|
if len(pubKey) != 32 {
|
|
return nil, fmt.Errorf("key share pubkey: need 32 bytes")
|
|
}
|
|
var exts bytes.Buffer
|
|
exts.Write([]byte{0x4a, 0x4a, 0x00, 0x00})
|
|
exts.Write(sniExtension(domain))
|
|
exts.Write(extBeforeKeyShare)
|
|
exts.Write(keyShareHeader)
|
|
exts.Write(pubKey)
|
|
exts.Write(extAfterKeyShare)
|
|
|
|
core := exts.Len()
|
|
// padding extension: 00 15 + u16(len) + zeros; total extensions must be 403
|
|
padHdr := 4
|
|
padData := extensionsTotal - core - padHdr
|
|
if padData < 0 {
|
|
return nil, fmt.Errorf("mtproxy fake-tls: domain too long for fixed 403-byte extension block (core=%d)", core)
|
|
}
|
|
exts.Write([]byte{0x00, 0x15})
|
|
exts.Write([]byte{byte(padData >> 8), byte(padData)})
|
|
for i := 0; i < padData; i++ {
|
|
exts.WriteByte(0x00)
|
|
}
|
|
if exts.Len() != extensionsTotal {
|
|
return nil, fmt.Errorf("internal: extensions len %d != %d", exts.Len(), extensionsTotal)
|
|
}
|
|
return exts.Bytes(), nil
|
|
}
|
|
|
|
// ClientHello is the initial raw TLS record and the 32-byte ClientHello.random field (post-HMAC).
|
|
type ClientHello struct {
|
|
Record []byte
|
|
RandomField []byte
|
|
SessionID []byte
|
|
}
|
|
|
|
// BuildClientHello constructs the first wire record (517 bytes) compatible with telethon-faketls.
|
|
func BuildClientHello(secretKey []byte, domain []byte) (*ClientHello, error) {
|
|
if len(secretKey) != 16 {
|
|
return nil, fmt.Errorf("secret key must be 16 bytes")
|
|
}
|
|
pub, err := GenX25519PublicKey()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sessionID, err := Rand32()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
exts, err := buildExtensions(domain, pub)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// ClientHello body = 508 bytes
|
|
var body bytes.Buffer
|
|
body.Write([]byte{0x03, 0x03})
|
|
randomZero := make([]byte, 32)
|
|
body.Write(randomZero)
|
|
body.WriteByte(32)
|
|
body.Write(sessionID)
|
|
body.Write([]byte{0x00, 0x20})
|
|
body.Write(cipherSuites)
|
|
body.WriteByte(0x01)
|
|
body.WriteByte(0x00)
|
|
body.Write([]byte{byte(extensionsTotal >> 8), byte(extensionsTotal & 0xff)})
|
|
body.Write(exts)
|
|
if body.Len() != 508 {
|
|
return nil, fmt.Errorf("internal: client hello body %d != 508", body.Len())
|
|
}
|
|
|
|
handshake := make([]byte, 0, 4+508)
|
|
handshake = append(handshake, 0x01)
|
|
handshake = append(handshake, byte(508>>16), byte((508>>8)&0xff), byte(508&0xff))
|
|
handshake = append(handshake, body.Bytes()...)
|
|
|
|
record := make([]byte, 0, 5+len(handshake))
|
|
record = append(record, 0x16, 0x03, 0x01)
|
|
record = binary.BigEndian.AppendUint16(record, uint16(len(handshake)))
|
|
record = append(record, handshake...)
|
|
|
|
randomField := computeRandomField(secretKey, record)
|
|
copy(record[11:43], randomField)
|
|
|
|
return &ClientHello{Record: record, RandomField: randomField, SessionID: sessionID}, nil
|
|
}
|
|
|
|
func computeRandomField(secretKey []byte, record []byte) []byte {
|
|
msg := make([]byte, 0, len(record))
|
|
msg = append(msg, record[:11]...)
|
|
msg = append(msg, make([]byte, 32)...)
|
|
msg = append(msg, record[43:]...)
|
|
mac := hmac.New(sha256.New, secretKey)
|
|
mac.Write(msg)
|
|
digest := mac.Sum(nil)
|
|
ts := uint32(time.Now().Unix())
|
|
tsb := []byte{byte(ts), byte(ts >> 8), byte(ts >> 16), byte(ts >> 24)}
|
|
out := make([]byte, 32)
|
|
copy(out[:28], digest[:28])
|
|
for i := 0; i < 4; i++ {
|
|
out[28+i] = digest[28+i] ^ tsb[i]
|
|
}
|
|
return out
|
|
}
|