Telegram Web K with changes to work inside I2P
https://web.telegram.i2p/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
271 lines
7.5 KiB
271 lines
7.5 KiB
import MTTransport from './transport'; |
|
|
|
//import aesjs from 'aes-js'; |
|
import {CTR} from '@cryptography/aes'; |
|
//import abridgetPacketCodec from './abridged'; |
|
import intermediatePacketCodec from './intermediate'; |
|
import {MTPNetworker} from '../networker'; |
|
import { logger, LogLevels } from '../../polyfill'; |
|
import { bytesFromWordss } from '../../bin_utils'; |
|
import { Codec } from './codec'; |
|
|
|
/* |
|
@cryptography/aes не работает с массивами которые не кратны 4, поэтому использую intermediate а не abridged |
|
*/ |
|
export class Obfuscation { |
|
/* public enc: aesjs.ModeOfOperation.ModeOfOperationCTR; |
|
public dec: aesjs.ModeOfOperation.ModeOfOperationCTR; */ |
|
|
|
public encNew: CTR; |
|
public decNew: CTR; |
|
|
|
public init(codec: Codec) { |
|
const initPayload = new Uint8Array(64); |
|
initPayload.randomize(); |
|
|
|
while(true) { |
|
let val = (initPayload[3] << 24) | (initPayload[2] << 16) | (initPayload[1] << 8) | (initPayload[0]); |
|
let val2 = (initPayload[7] << 24) | (initPayload[6] << 16) | (initPayload[5] << 8) | (initPayload[4]); |
|
if(initPayload[0] != 0xef && |
|
val != 0x44414548 && |
|
val != 0x54534f50 && |
|
val != 0x20544547 && |
|
val != 0x4954504f && |
|
val != 0xeeeeeeee && |
|
val != 0xdddddddd && |
|
val2 != 0x00000000) { |
|
//initPayload[56] = initPayload[57] = initPayload[58] = initPayload[59] = transport; |
|
break; |
|
} |
|
initPayload.randomize(); |
|
} |
|
|
|
////////////////////////initPayload.subarray(60, 62).hex = dcID; |
|
|
|
const reversedPayload = initPayload.slice().reverse(); |
|
|
|
let encKey = initPayload.slice(8, 40); |
|
let encIv = initPayload.slice(40, 56); |
|
let decKey = reversedPayload.slice(8, 40); |
|
let decIv = reversedPayload.slice(40, 56); |
|
|
|
/* this.enc = new aesjs.ModeOfOperation.ctr(encKey, new aesjs.Counter(encIv as any)); |
|
this.dec = new aesjs.ModeOfOperation.ctr(decKey, new aesjs.Counter(decIv as any)); */ |
|
|
|
this.encNew = new CTR(encKey, encIv); |
|
this.decNew = new CTR(decKey, decIv); |
|
|
|
initPayload.set(codec.obfuscateTag, 56); |
|
const encrypted = this.encode(initPayload); |
|
|
|
initPayload.set(encrypted.slice(56, 64), 56); |
|
|
|
return initPayload; |
|
} |
|
|
|
/* public encode(payload: Uint8Array) { |
|
let res = this.enc.encrypt(payload); |
|
|
|
try { |
|
let arr = this.encNew.encrypt(payload); |
|
//let resNew = bytesFromWords({words: arr, sigBytes: arr.length}); |
|
let resNew = new Uint8Array(bytesFromWordss(arr)); |
|
console.log('Obfuscation: encode comparison:', res, arr, resNew, res.hex == resNew.hex); |
|
} catch(err) { |
|
console.error('Obfuscation: error:', err); |
|
} |
|
|
|
return res; |
|
} |
|
|
|
public decode(payload: Uint8Array) { |
|
let res = this.dec.encrypt(payload); |
|
|
|
try { |
|
let arr = this.decNew.decrypt(payload); |
|
//let resNew = bytesFromWords({words: arr, sigBytes: arr.length}); |
|
let resNew = new Uint8Array(bytesFromWordss(arr)); |
|
console.log('Obfuscation: decode comparison:', res, arr, resNew, res.hex == resNew.hex); |
|
} catch(err) { |
|
console.error('Obfuscation: error:', err); |
|
} |
|
|
|
return res; |
|
} */ |
|
public encode(payload: Uint8Array) { |
|
let res = this.encNew.encrypt(payload); |
|
let bytes = new Uint8Array(bytesFromWordss(res)); |
|
|
|
return bytes; |
|
} |
|
|
|
public decode(payload: Uint8Array) { |
|
let res = this.decNew.decrypt(payload); |
|
let bytes = new Uint8Array(bytesFromWordss(res)); |
|
|
|
return bytes; |
|
} |
|
} |
|
|
|
export default class Socket extends MTTransport { |
|
ws: WebSocket; |
|
|
|
pending: Array<Partial<{ |
|
resolve: any, |
|
reject: any, |
|
body: Uint8Array, |
|
bodySent: boolean |
|
}>> = []; |
|
|
|
connected = false; |
|
|
|
transport = 'websocket'; |
|
|
|
obfuscation = new Obfuscation(); |
|
|
|
networker: MTPNetworker; |
|
|
|
log: ReturnType<typeof logger>; |
|
|
|
codec = intermediatePacketCodec; |
|
|
|
constructor(dcID: number, url: string) { |
|
super(dcID, url); |
|
|
|
this.log = logger(`WS-${dcID}`, LogLevels.log/* | LogLevels.error | LogLevels.debug */); |
|
this.log('constructor'); |
|
this.connect(); |
|
} |
|
|
|
connect = () => { |
|
if(this.ws) { |
|
this.ws.removeEventListener('open', this.handleOpen); |
|
this.ws.removeEventListener('close', this.handleClose); |
|
this.ws.removeEventListener('message', this.handleMessage); |
|
this.ws.close(1000); |
|
} |
|
|
|
this.ws = new WebSocket(this.url, 'binary'); |
|
this.ws.binaryType = 'arraybuffer'; |
|
this.ws.addEventListener('open', this.handleOpen); |
|
this.ws.addEventListener('close', this.handleClose); |
|
this.ws.addEventListener('message', this.handleMessage); |
|
}; |
|
|
|
handleOpen = () => { |
|
this.log('opened'); |
|
|
|
this.log.debug('sending init packet'); |
|
this.ws.send(this.obfuscation.init(this.codec)); |
|
|
|
//setTimeout(() => { |
|
this.connected = true; |
|
|
|
this.releasePending(); |
|
//}, 3e3); |
|
}; |
|
|
|
handleClose = (event: CloseEvent) => { |
|
this.log('closed', event, this.pending); |
|
this.connected = false; |
|
|
|
//this.pending.length = 0; |
|
/* if(this.networker) { |
|
this.networker.resend(); |
|
this.networker.cleanupSent(); |
|
} */ |
|
|
|
this.log('trying to reconnect...'); |
|
this.connect(); |
|
|
|
for(let pending of this.pending) { |
|
if(pending.bodySent) { |
|
pending.bodySent = false; |
|
} |
|
} |
|
|
|
if(this.networker) { |
|
this.ws.addEventListener('open', () => { |
|
this.networker.resend(); |
|
this.networker.cleanupSent(); |
|
}, {once: true}); |
|
} |
|
}; |
|
|
|
handleMessage = (event: MessageEvent) => { |
|
this.log.debug('<-', 'handleMessage', event); |
|
|
|
let data = this.obfuscation.decode(new Uint8Array(event.data)); |
|
data = this.codec.readPacket(data); |
|
|
|
if(this.networker) { // authenticated! |
|
//this.pending = this.pending.filter(p => p.body); // clear pending |
|
|
|
this.log.debug('redirecting to networker'); |
|
return this.networker.parseResponse(data).then(response => { |
|
this.log.debug('redirecting to networker response:', response); |
|
this.networker.processMessage(response.response, response.messageID, response.sessionID); |
|
}); |
|
} |
|
|
|
//console.log('got hex:', data.hex); |
|
let pending = this.pending.shift(); |
|
if(!pending) { |
|
return this.log.debug('no pending for res:', data.hex); |
|
} |
|
|
|
pending.resolve(data); |
|
}; |
|
|
|
send = (body: Uint8Array) => { |
|
this.log.debug('-> body length to pending:', body.length); |
|
|
|
//return; |
|
|
|
if(this.networker) { |
|
this.pending.push({body}); |
|
this.releasePending(); |
|
} else { |
|
let promise = new Promise<Uint8Array>((resolve, reject) => { |
|
this.pending.push({resolve, reject, body}); |
|
}); |
|
|
|
this.releasePending(); |
|
|
|
return promise; |
|
} |
|
} |
|
|
|
releasePending() { |
|
if(!this.connected) { |
|
//this.connect(); |
|
return; |
|
} |
|
|
|
//this.log.error('Pending length:', this.pending.length); |
|
const length = this.pending.length; |
|
for(let i = length - 1; i >= 0; --i) { |
|
const pending = this.pending[i]; |
|
const {body, bodySent} = pending; |
|
if(body && !bodySent) { |
|
const toEncode = this.codec.encodePacket(body); |
|
|
|
//this.log('send before obf:', /* body.hex, nonce.hex, */ toEncode.hex); |
|
const enc = this.obfuscation.encode(toEncode); |
|
//this.log('send after obf:', enc.hex); |
|
|
|
this.log.debug('-> body length to send:', enc.length); |
|
this.ws.send(enc); |
|
|
|
if(!pending.resolve) { // remove if no response needed |
|
this.pending.splice(i, 1); |
|
} else { |
|
pending.bodySent = true; |
|
} |
|
|
|
//delete pending.body; |
|
} |
|
} |
|
} |
|
} |
|
|