349 lines
8.9 KiB
TypeScript
349 lines
8.9 KiB
TypeScript
/* eslint-disable no-restricted-globals */
|
|
import { IPacketCodec, WrappedCodec } from '@mtcute/node'
|
|
import { bigIntModInv, bigIntModPow, bigIntToBuffer, bufferToBigInt, ICryptoProvider } from '@mtcute/node/utils.js'
|
|
|
|
const MAX_TLS_PACKET_LENGTH = 2878
|
|
const TLS_FIRST_PREFIX = Buffer.from('140303000101', 'hex')
|
|
|
|
// ref: https://github.com/tdlib/td/blob/master/td/mtproto/TlsInit.cpp
|
|
const KEY_MOD = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn
|
|
// 2^255 - 19
|
|
const QUAD_RES_MOD = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn
|
|
// (mod - 1) / 2 = 2^254 - 10
|
|
const QUAD_RES_POW = 0x3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6n
|
|
|
|
function _getY2(x: bigint, mod: bigint): bigint {
|
|
// returns y = x^3 + x^2 * 486662 + x
|
|
let y = x
|
|
y = (y + 486662n) % mod
|
|
y = (y * x) % mod
|
|
y = (y + 1n) % mod
|
|
y = (y * x) % mod
|
|
|
|
return y
|
|
}
|
|
|
|
function _getDoubleX(x: bigint, mod: bigint): bigint {
|
|
// returns x_2 = (x^2 - 1)^2/(4*y^2)
|
|
let denominator = _getY2(x, mod)
|
|
denominator = (denominator * 4n) % mod
|
|
|
|
let numerator = (x * x) % mod
|
|
numerator = (numerator - 1n) % mod
|
|
numerator = (numerator * numerator) % mod
|
|
|
|
denominator = bigIntModInv(denominator, mod)
|
|
numerator = (numerator * denominator) % mod
|
|
|
|
return numerator
|
|
}
|
|
|
|
function _isQuadraticResidue(a: bigint): boolean {
|
|
const r = bigIntModPow(a, QUAD_RES_POW, QUAD_RES_MOD)
|
|
|
|
return r === 1n
|
|
}
|
|
|
|
interface TlsOperationHandler {
|
|
string(buf: Buffer): void
|
|
zero(size: number): void
|
|
random(size: number): void
|
|
domain(): void
|
|
grease(seed: number): void
|
|
beginScope(): void
|
|
endScope(): void
|
|
key(): void
|
|
}
|
|
|
|
function executeTlsOperations(h: TlsOperationHandler): void {
|
|
h.string(Buffer.from('1603010200010001fc0303', 'hex'))
|
|
h.zero(32)
|
|
h.string(Buffer.from('20', 'hex'))
|
|
h.random(32)
|
|
h.string(Buffer.from('0020', 'hex'))
|
|
h.grease(0)
|
|
h.string(Buffer.from('130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f003501000193', 'hex'))
|
|
h.grease(2)
|
|
h.string(Buffer.from('00000000', 'hex'))
|
|
h.beginScope()
|
|
h.beginScope()
|
|
h.string(Buffer.from('00', 'hex'))
|
|
h.beginScope()
|
|
h.domain()
|
|
h.endScope()
|
|
h.endScope()
|
|
h.endScope()
|
|
h.string(Buffer.from('00170000ff01000100000a000a0008', 'hex'))
|
|
h.grease(4)
|
|
h.string(
|
|
Buffer.from(
|
|
'001d00170018000b00020100002300000010000e000c02683208687474702f312e31000500050100000000000d0012001004030804040105030805050108060601001200000033002b0029',
|
|
'hex',
|
|
),
|
|
)
|
|
h.grease(4)
|
|
h.string(Buffer.from('000100001d0020', 'hex'))
|
|
h.key()
|
|
h.string(Buffer.from('002d00020101002b000b0a', 'hex'))
|
|
h.grease(6)
|
|
h.string(Buffer.from('0304030303020301001b0003020002', 'hex'))
|
|
h.grease(3)
|
|
h.string(Buffer.from('0001000015', 'hex'))
|
|
}
|
|
|
|
// i dont know why is this needed, since it is always padded to 517 bytes
|
|
// this was in tdlib sources, so whatever. not used here though, and works just fine
|
|
// class TlsHelloCounter implements TlsOperationHandler {
|
|
// size = 0
|
|
//
|
|
// private _domain: Buffer
|
|
//
|
|
// constructor(domain: Buffer) {
|
|
// this._domain = domain
|
|
// }
|
|
//
|
|
// string(buf: Buffer) {
|
|
// this.size += buf.length
|
|
// }
|
|
//
|
|
// random(size: number) {
|
|
// this.size += size
|
|
// }
|
|
//
|
|
// zero(size: number) {
|
|
// this.size += size
|
|
// }
|
|
//
|
|
// domain() {
|
|
// this.size += this._domain.length
|
|
// }
|
|
//
|
|
// grease() {
|
|
// this.size += 2
|
|
// }
|
|
//
|
|
// key() {
|
|
// this.size += 32
|
|
// }
|
|
//
|
|
// beginScope() {
|
|
// this.size += 2
|
|
// }
|
|
//
|
|
// endScope() {
|
|
// // no-op, since this does not affect size
|
|
// }
|
|
//
|
|
// finish(): number {
|
|
// const zeroPad = 515 - this.size
|
|
// this.beginScope()
|
|
// this.zero(zeroPad)
|
|
// this.endScope()
|
|
//
|
|
// return this.size
|
|
// }
|
|
// }
|
|
|
|
function initGrease(crypto: ICryptoProvider, size: number): Buffer {
|
|
const buf = crypto.randomBytes(size)
|
|
|
|
for (let i = 0; i < size; i++) {
|
|
buf[i] = (buf[i] & 0xf0) + 0x0a
|
|
}
|
|
|
|
for (let i = 1; i < size; i += 2) {
|
|
if (buf[i] === buf[i - 1]) {
|
|
buf[i] ^= 0x10
|
|
}
|
|
}
|
|
|
|
return Buffer.from(buf)
|
|
}
|
|
|
|
class TlsHelloWriter implements TlsOperationHandler {
|
|
buf: Buffer
|
|
pos = 0
|
|
|
|
private _domain: Buffer
|
|
private _grease
|
|
private _scopes: number[] = []
|
|
|
|
constructor(
|
|
readonly crypto: ICryptoProvider,
|
|
size: number,
|
|
domain: Buffer,
|
|
) {
|
|
this._domain = domain
|
|
this.buf = Buffer.allocUnsafe(size)
|
|
this._grease = initGrease(this.crypto, 7)
|
|
}
|
|
|
|
string(buf: Buffer) {
|
|
buf.copy(this.buf, this.pos)
|
|
this.pos += buf.length
|
|
}
|
|
|
|
random(size: number) {
|
|
this.string(Buffer.from(this.crypto.randomBytes(size)))
|
|
}
|
|
|
|
zero(size: number) {
|
|
this.string(Buffer.alloc(size, 0))
|
|
}
|
|
|
|
domain() {
|
|
this.string(this._domain)
|
|
}
|
|
|
|
grease(seed: number) {
|
|
this.buf[this.pos] = this.buf[this.pos + 1] = this._grease[seed]
|
|
this.pos += 2
|
|
}
|
|
|
|
key() {
|
|
for (;;) {
|
|
const key = this.crypto.randomBytes(32)
|
|
key[31] &= 127
|
|
|
|
let x = bufferToBigInt(key)
|
|
const y = _getY2(x, KEY_MOD)
|
|
|
|
if (_isQuadraticResidue(y)) {
|
|
for (let i = 0; i < 3; i++) {
|
|
x = _getDoubleX(x, KEY_MOD)
|
|
}
|
|
|
|
const key = bigIntToBuffer(x, 32, true)
|
|
this.string(Buffer.from(key))
|
|
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
beginScope() {
|
|
this._scopes.push(this.pos)
|
|
this.pos += 2
|
|
}
|
|
|
|
endScope() {
|
|
const begin = this._scopes.pop()
|
|
|
|
if (begin === undefined) {
|
|
throw new Error('endScope called without beginScope')
|
|
}
|
|
|
|
const end = this.pos
|
|
const size = end - begin - 2
|
|
|
|
this.buf.writeUInt16BE(size, begin)
|
|
}
|
|
|
|
async finish(secret: Buffer): Promise<Buffer> {
|
|
const padSize = 515 - this.pos
|
|
const unixTime = ~~(Date.now() / 1000)
|
|
|
|
this.beginScope()
|
|
this.zero(padSize)
|
|
this.endScope()
|
|
|
|
const hash = Buffer.from(await this.crypto.hmacSha256(this.buf, secret))
|
|
|
|
const old = hash.readInt32LE(28)
|
|
hash.writeInt32LE(old ^ unixTime, 28)
|
|
|
|
hash.copy(this.buf, 11)
|
|
|
|
return this.buf
|
|
}
|
|
}
|
|
|
|
/** @internal */
|
|
export async function generateFakeTlsHeader(domain: string, secret: Buffer, crypto: ICryptoProvider): Promise<Buffer> {
|
|
const domainBuf = Buffer.from(domain)
|
|
|
|
const writer = new TlsHelloWriter(crypto, 517, domainBuf)
|
|
executeTlsOperations(writer)
|
|
|
|
return writer.finish(secret)
|
|
}
|
|
|
|
/**
|
|
* Fake TLS packet codec, used for some MTProxies.
|
|
*
|
|
* Must only be used inside {@link MtProxyTcpTransport}
|
|
* @internal
|
|
*/
|
|
export class FakeTlsPacketCodec extends WrappedCodec implements IPacketCodec {
|
|
protected _stream = Buffer.alloc(0)
|
|
|
|
private _header!: Buffer
|
|
private _isFirstTls = true
|
|
|
|
async tag(): Promise<Buffer> {
|
|
this._header = Buffer.from(await this._inner.tag())
|
|
|
|
return Buffer.alloc(0)
|
|
}
|
|
|
|
private _encodeTls(packet: Buffer): Buffer {
|
|
if (this._header.length) {
|
|
packet = Buffer.concat([this._header, packet])
|
|
this._header = Buffer.alloc(0)
|
|
}
|
|
|
|
const header = Buffer.from([0x17, 0x03, 0x03, 0x00, 0x00])
|
|
header.writeUInt16BE(packet.length, 3)
|
|
|
|
if (this._isFirstTls) {
|
|
this._isFirstTls = false
|
|
|
|
return Buffer.concat([TLS_FIRST_PREFIX, header, packet])
|
|
}
|
|
|
|
return Buffer.concat([header, packet])
|
|
}
|
|
|
|
async encode(packet: Buffer): Promise<Buffer> {
|
|
packet = Buffer.from(await this._inner.encode(packet))
|
|
|
|
if (packet.length + this._header.length > MAX_TLS_PACKET_LENGTH) {
|
|
const ret: Buffer[] = []
|
|
|
|
while (packet.length) {
|
|
const buf = packet.slice(0, MAX_TLS_PACKET_LENGTH - this._header.length)
|
|
packet = packet.slice(buf.length)
|
|
ret.push(this._encodeTls(buf))
|
|
}
|
|
|
|
return Buffer.concat(ret)
|
|
}
|
|
|
|
return this._encodeTls(packet)
|
|
}
|
|
|
|
feed(data: Buffer): void {
|
|
this._stream = Buffer.concat([this._stream, data])
|
|
|
|
for (;;) {
|
|
if (this._stream.length < 5) return
|
|
|
|
if (!(this._stream[0] === 0x17 && this._stream[1] === 0x03 && this._stream[2] === 0x03)) {
|
|
this.emit('error', new Error('Invalid TLS header'))
|
|
|
|
return
|
|
}
|
|
|
|
const length = this._stream.readUInt16BE(3)
|
|
if (length < this._stream.length - 5) return
|
|
|
|
this._inner.feed(this._stream.slice(5, length + 5))
|
|
this._stream = this._stream.slice(length + 5)
|
|
}
|
|
}
|
|
|
|
reset(): void {
|
|
this._stream = Buffer.alloc(0)
|
|
this._isFirstTls = true
|
|
}
|
|
}
|