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 }