116 lines
4.4 KiB
Go
116 lines
4.4 KiB
Go
package faketls
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
// Маркеры и разбор длин — как в mtproto_tls_socket.cpp (checkHelloParts12 / checkHelloParts34),
|
|
// а не фиксированные 127+6+3+2 байта telethon + «http»-хвост (на части хостов это давало ложную длину хвоста).
|
|
var (
|
|
kServerHelloPart1 = []byte{0x16, 0x03, 0x03}
|
|
kServerHelloPart3 = []byte{0x14, 0x03, 0x03, 0x00, 0x01, 0x01, 0x17, 0x03, 0x03}
|
|
)
|
|
|
|
const kTLSLengthSize = 2
|
|
|
|
// ReadServerHello читает цепочку fake-TLS ServerHello до конца части, по которой считается digest (как в tdesktop).
|
|
func ReadServerHello(r io.Reader) ([]byte, error) {
|
|
parts1Size := len(kServerHelloPart1) + kTLSLengthSize
|
|
header := make([]byte, parts1Size)
|
|
if _, err := io.ReadFull(r, header); err != nil {
|
|
return nil, fmt.Errorf("read tls record header: %w", err)
|
|
}
|
|
part1Off := parts1Size - kTLSLengthSize - len(kServerHelloPart1)
|
|
if !bytes.Equal(header[part1Off:part1Off+len(kServerHelloPart1)], kServerHelloPart1) {
|
|
return nil, fmt.Errorf("server hello: bad tls prefix")
|
|
}
|
|
part2Size := int(binary.BigEndian.Uint16(header[parts1Size-kTLSLengthSize:]))
|
|
if part2Size <= 0 || part2Size > 1<<20 {
|
|
return nil, fmt.Errorf("invalid tls fragment length %d", part2Size)
|
|
}
|
|
midLen := part2Size + len(kServerHelloPart3) + kTLSLengthSize
|
|
mid := make([]byte, midLen)
|
|
if _, err := io.ReadFull(r, mid); err != nil {
|
|
return nil, fmt.Errorf("read server hello body: %w", err)
|
|
}
|
|
part3Off := part2Size
|
|
if !bytes.Equal(mid[part3Off:part3Off+len(kServerHelloPart3)], kServerHelloPart3) {
|
|
return nil, fmt.Errorf("server hello: unexpected middle tls markers")
|
|
}
|
|
part4Size := int(binary.BigEndian.Uint16(mid[len(mid)-kTLSLengthSize:]))
|
|
if part4Size < 0 || part4Size > 1<<20 {
|
|
return nil, fmt.Errorf("invalid final fragment length %d", part4Size)
|
|
}
|
|
tail := make([]byte, part4Size)
|
|
if _, err := io.ReadFull(r, tail); err != nil {
|
|
return nil, fmt.Errorf("read server hello final fragment: %w", err)
|
|
}
|
|
out := append(append(append([]byte{}, header...), mid...), tail...)
|
|
return out, nil
|
|
}
|
|
|
|
// VerifyServerHelloFull validates the proxy response (telethon-faketls rules).
|
|
func VerifyServerHelloFull(full []byte, secretKey []byte, clientRandom []byte, sessionID []byte) error {
|
|
if len(full) < 127+6+3+2 {
|
|
return fmt.Errorf("server hello too short")
|
|
}
|
|
if !bytes.HasPrefix(full, []byte{0x16, 0x03, 0x03}) {
|
|
return fmt.Errorf("server hello: bad tls prefix")
|
|
}
|
|
marker := []byte{0x14, 0x03, 0x03, 0x00, 0x01, 0x01, 0x17, 0x03, 0x03}
|
|
if len(full) < 127+len(marker) || !bytes.Equal(full[127:127+len(marker)], marker) {
|
|
return fmt.Errorf("server hello: unexpected middle tls markers")
|
|
}
|
|
sidStart := 11 + 32 + 1
|
|
if len(full) < sidStart+32 || !bytes.Equal(full[sidStart:sidStart+32], sessionID) {
|
|
return fmt.Errorf("server hello: session id mismatch")
|
|
}
|
|
patched := make([]byte, len(full))
|
|
copy(patched, full)
|
|
clear(patched[11:43])
|
|
mac := hmac.New(sha256.New, secretKey)
|
|
mac.Write(clientRandom)
|
|
mac.Write(patched)
|
|
want := mac.Sum(nil)
|
|
if !bytes.Equal(full[11:43], want) {
|
|
return fmt.Errorf("server hello: digest mismatch")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
const (
|
|
kServerHelloDigestPosition = 11
|
|
)
|
|
|
|
// VerifyServerHelloTdesktop проверяет ответ прокси по правилам mtproto_tls_socket.cpp (checkHelloDigest):
|
|
// HMAC-SHA256(key, client_digest||server_bytes) при обнулённом digest сервера в буфере.
|
|
func VerifyServerHelloTdesktop(serverResponse []byte, secretKey []byte, clientDigest []byte) error {
|
|
if len(secretKey) != 16 {
|
|
return fmt.Errorf("secret key must be 16 bytes")
|
|
}
|
|
if len(clientDigest) != 32 {
|
|
return fmt.Errorf("client digest must be 32 bytes")
|
|
}
|
|
if len(serverResponse) < kServerHelloDigestPosition+32 {
|
|
return fmt.Errorf("server hello too short for digest")
|
|
}
|
|
fulldata := make([]byte, 0, len(clientDigest)+len(serverResponse))
|
|
fulldata = append(fulldata, clientDigest...)
|
|
fulldata = append(fulldata, serverResponse...)
|
|
digestOff := len(clientDigest) + kServerHelloDigestPosition
|
|
digestCopy := append([]byte(nil), fulldata[digestOff:digestOff+32]...)
|
|
clear(fulldata[digestOff : digestOff+32])
|
|
mac := hmac.New(sha256.New, secretKey)
|
|
mac.Write(fulldata)
|
|
want := mac.Sum(nil)
|
|
if !bytes.Equal(digestCopy, want) {
|
|
return fmt.Errorf("server hello: digest mismatch (tdesktop)")
|
|
}
|
|
return nil
|
|
}
|