GramJs: New authorization algorithm, new RSA keys, message queues with invokeAfter (#1258)
This commit is contained in:
parent
3aba9fa599
commit
388143a057
@ -41,7 +41,7 @@ function toSignedLittleBuffer(big, number = 8) {
|
||||
|
||||
/**
|
||||
* converts a big int to a buffer
|
||||
* @param bigInt {BigInteger}
|
||||
* @param bigInt {bigInt.BigInteger}
|
||||
* @param bytesNumber
|
||||
* @param little
|
||||
* @param signed
|
||||
@ -273,6 +273,19 @@ function getRandomInt(min, max) {
|
||||
*/
|
||||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
/**
|
||||
* Helper to export two buffers of same length
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
|
||||
function bufferXor(a, b) {
|
||||
const res = [];
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
res.push(a[i] ^ b[i]);
|
||||
}
|
||||
return Buffer.from(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the obj is an array
|
||||
* @param obj
|
||||
@ -328,6 +341,22 @@ function crc32(buf) {
|
||||
return (crc ^ (-1)) >>> 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a deferred object
|
||||
* @return {Deferred}
|
||||
*/
|
||||
function createDeferred() {
|
||||
let resolve;
|
||||
const promise = new Promise((_resolve) => {
|
||||
resolve = _resolve;
|
||||
});
|
||||
|
||||
return {
|
||||
promise,
|
||||
resolve,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
readBigIntFromBuffer,
|
||||
readBufferFromBigInt,
|
||||
@ -347,5 +376,6 @@ module.exports = {
|
||||
// isArrayLike,
|
||||
toSignedLittleBuffer,
|
||||
convertToLittle,
|
||||
|
||||
bufferXor,
|
||||
createDeferred,
|
||||
};
|
||||
|
||||
@ -592,11 +592,9 @@ class TelegramClient {
|
||||
this._lastRequest = new Date().getTime();
|
||||
let attempt = 0;
|
||||
for (attempt = 0; attempt < this._requestRetries; attempt++) {
|
||||
const promise = this._sender.sendWithInvokeSupport(request);
|
||||
try {
|
||||
const promise = this._sender.send(request);
|
||||
const result = await promise;
|
||||
// this.session.processEntities(result)
|
||||
// this._entityCache.add(result)
|
||||
const result = await promise.promise;
|
||||
return result;
|
||||
} catch (e) {
|
||||
if (e instanceof errors.ServerError || e.message === 'RPC_CALL_FAIL'
|
||||
@ -619,6 +617,9 @@ class TelegramClient {
|
||||
throw e;
|
||||
}
|
||||
await this._switchDC(e.newDc);
|
||||
} else if (e instanceof errors.MsgWaitError) {
|
||||
// we need to resend this after the old one was confirmed.
|
||||
await promise.isReady();
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import { default as Api } from '../tl/api';
|
||||
import TelegramClient from './TelegramClient';
|
||||
import { getAppropriatedPartSize } from '../Utils';
|
||||
import { sleep } from '../Helpers';
|
||||
import { sleep, createDeferred } from '../Helpers';
|
||||
|
||||
export interface progressCallback {
|
||||
isCanceled?: boolean;
|
||||
@ -49,7 +49,7 @@ class Foreman {
|
||||
|
||||
if (this.activeWorkers > this.maxWorkers) {
|
||||
this.deferred = createDeferred();
|
||||
return this.deferred.promise;
|
||||
return this.deferred!.promise;
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
@ -185,16 +185,3 @@ export async function downloadFile(
|
||||
const totalLength = end ? (end + 1) - start : undefined;
|
||||
return Buffer.concat(buffers, totalLength);
|
||||
}
|
||||
|
||||
|
||||
function createDeferred(): Deferred {
|
||||
let resolve: Deferred['resolve'];
|
||||
const promise = new Promise((_resolve) => {
|
||||
resolve = _resolve;
|
||||
});
|
||||
|
||||
return {
|
||||
promise,
|
||||
resolve: resolve!,
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
/* eslint-disable max-len */
|
||||
const BigInt = require('big-integer');
|
||||
const {
|
||||
readBigIntFromBuffer,
|
||||
readBufferFromBigInt,
|
||||
sha1,
|
||||
generateRandomBytes,
|
||||
modExp,
|
||||
} = require('../Helpers');
|
||||
|
||||
const PUBLIC_KEYS = [{
|
||||
fingerprint: [40, 85, 94, 156, 117, 240, 61, 22, 65, 244, 169, 2, 33, 107, 232, 108, 2, 43, 180, 195],
|
||||
n: BigInt('24403446649145068056824081744112065346446136066297307473868293895086332508101251964919587745984311372853053253457835208829824428441874946556659953519213382748319518214765985662663680818277989736779506318868003755216402538945900388706898101286548187286716959100102939636333452457308619454821845196109544157601096359148241435922125602449263164512290854366930013825808102403072317738266383237191313714482187326643144603633877219028262697593882410403273959074350849923041765639673335775605842311578109726403165298875058941765362622936097839775380070572921007586266115476975819175319995527916042178582540628652481530373407'),
|
||||
e: 65537,
|
||||
}, {
|
||||
fingerprint: [140, 171, 9, 34, 146, 246, 166, 50, 10, 170, 229, 247, 155, 114, 28, 177, 29, 106, 153, 154],
|
||||
n: BigInt('25081407810410225030931722734886059247598515157516470397242545867550116598436968553551465554653745201634977779380884774534457386795922003815072071558370597290368737862981871277312823942822144802509055492512145589734772907225259038113414940384446493111736999668652848440655603157665903721517224934142301456312994547591626081517162758808439979745328030376796953660042629868902013177751703385501412640560275067171555763725421377065095231095517201241069856888933358280729674273422117201596511978645878544308102076746465468955910659145532699238576978901011112475698963666091510778777356966351191806495199073754705289253783'),
|
||||
e: 65537,
|
||||
}, {
|
||||
fingerprint: [243, 218, 109, 239, 16, 202, 176, 78, 167, 8, 255, 209, 120, 234, 205, 112, 111, 42, 91, 176],
|
||||
n: BigInt('22347337644621997830323797217583448833849627595286505527328214795712874535417149457567295215523199212899872122674023936713124024124676488204889357563104452250187725437815819680799441376434162907889288526863223004380906766451781702435861040049293189979755757428366240570457372226323943522935844086838355728767565415115131238950994049041950699006558441163206523696546297006014416576123345545601004508537089192869558480948139679182328810531942418921113328804749485349441503927570568778905918696883174575510385552845625481490900659718413892216221539684717773483326240872061786759868040623935592404144262688161923519030977'),
|
||||
e: 65537,
|
||||
}, {
|
||||
fingerprint: [128, 80, 214, 72, 77, 244, 98, 7, 201, 250, 37, 244, 227, 51, 96, 199, 182, 37, 224, 113],
|
||||
n: BigInt('24573455207957565047870011785254215390918912369814947541785386299516827003508659346069416840622922416779652050319196701077275060353178142796963682024347858398319926119639265555410256455471016400261630917813337515247954638555325280392998950756512879748873422896798579889820248358636937659872379948616822902110696986481638776226860777480684653756042166610633513404129518040549077551227082262066602286208338952016035637334787564972991208252928951876463555456715923743181359826124083963758009484867346318483872552977652588089928761806897223231500970500186019991032176060579816348322451864584743414550721639495547636008351'),
|
||||
e: 65537,
|
||||
}];
|
||||
|
||||
const _serverKeys = {};
|
||||
|
||||
PUBLIC_KEYS.forEach(({
|
||||
fingerprint,
|
||||
...keyInfo
|
||||
}) => {
|
||||
_serverKeys[readBigIntFromBuffer(fingerprint.slice(-8), true, true)] = keyInfo;
|
||||
});
|
||||
|
||||
/**
|
||||
* Encrypts the given data known the fingerprint to be used
|
||||
* in the way Telegram requires us to do so (sha1(data) + data + padding)
|
||||
|
||||
* @param fingerprint the fingerprint of the RSA key.
|
||||
* @param data the data to be encrypted.
|
||||
* @returns {Buffer|*|undefined} the cipher text, or None if no key matching this fingerprint is found.
|
||||
*/
|
||||
async function encrypt(fingerprint, data) {
|
||||
const key = _serverKeys[fingerprint];
|
||||
if (!key) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// len(sha1.digest) is always 20, so we're left with 255 - 20 - x padding
|
||||
const rand = generateRandomBytes(235 - data.length);
|
||||
|
||||
const toEncrypt = Buffer.concat([await sha1(data), data, rand]);
|
||||
|
||||
// rsa module rsa.encrypt adds 11 bits for padding which we don't want
|
||||
// rsa module uses rsa.transform.bytes2int(to_encrypt), easier way:
|
||||
const payload = readBigIntFromBuffer(toEncrypt, false);
|
||||
const encrypted = modExp(payload, BigInt(key.e), key.n);
|
||||
// rsa module uses transform.int2bytes(encrypted, keylength), easier:
|
||||
return readBufferFromBigInt(encrypted, 256, false);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
encrypt,
|
||||
};
|
||||
57
src/lib/gramjs/crypto/RSA.ts
Normal file
57
src/lib/gramjs/crypto/RSA.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import bigInt from 'big-integer';
|
||||
import {
|
||||
generateRandomBytes,
|
||||
modExp,
|
||||
readBigIntFromBuffer,
|
||||
readBufferFromBigInt,
|
||||
sha1,
|
||||
} from '../Helpers';
|
||||
|
||||
const PUBLIC_KEYS = [
|
||||
{
|
||||
fingerprint: bigInt('-3414540481677951611'),
|
||||
n: bigInt(
|
||||
'2937959817066933702298617714945612856538843112005886376816255642404751219133084745514657634448776440866'
|
||||
+ '1701890505066208632169112269581063774293102577308490531282748465986139880977280302242772832972539403531'
|
||||
+ '3160108704012876427630091361567343395380424193887227773571344877461690935390938502512438971889287359033'
|
||||
+ '8945177273024525306296338410881284207988753897636046529094613963869149149606209957083647645485599631919'
|
||||
+ '2747663615955633778034897140982517446405334423701359108810182097749467210509584293428076654573384828809'
|
||||
+ '574217079944388301239431309115013843331317877374435868468779972014486325557807783825502498215169806323',
|
||||
),
|
||||
e: 65537,
|
||||
},
|
||||
];
|
||||
|
||||
export const _serverKeys = new Map<string, { n: bigInt.BigInteger; e: number }>();
|
||||
|
||||
PUBLIC_KEYS.forEach(({ fingerprint, ...keyInfo }) => {
|
||||
_serverKeys.set(fingerprint.toString(),
|
||||
keyInfo);
|
||||
});
|
||||
|
||||
/**
|
||||
* Encrypts the given data known the fingerprint to be used
|
||||
* in the way Telegram requires us to do so (sha1(data) + data + padding)
|
||||
|
||||
* @param fingerprint the fingerprint of the RSA key.
|
||||
* @param data the data to be encrypted.
|
||||
* @returns {Buffer|*|undefined} the cipher text, or undefined if no key matching this fingerprint is found.
|
||||
*/
|
||||
export async function encrypt(fingerprint: bigInt.BigInteger, data: Buffer) {
|
||||
const key = _serverKeys.get(fingerprint.toString());
|
||||
if (!key) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// len(sha1.digest) is always 20, so we're left with 255 - 20 - x padding
|
||||
const rand = generateRandomBytes(235 - data.length);
|
||||
|
||||
const toEncrypt = Buffer.concat([await sha1(data), data, rand]);
|
||||
|
||||
// rsa module rsa.encrypt adds 11 bits for padding which we don't want
|
||||
// rsa module uses rsa.transform.bytes2int(to_encrypt), easier way:
|
||||
const payload = readBigIntFromBuffer(toEncrypt, false);
|
||||
const encrypted = modExp(payload, bigInt(key.e), key.n);
|
||||
// rsa module uses transform.int2bytes(encrypted, keylength), easier:
|
||||
return readBufferFromBigInt(encrypted, 256, false);
|
||||
}
|
||||
@ -48,6 +48,12 @@ class FloodWaitError extends FloodError {
|
||||
this.seconds = seconds;
|
||||
}
|
||||
}
|
||||
class MsgWaitError extends FloodError {
|
||||
constructor(args) {
|
||||
super(`Message failed to be sent.${RPCError._fmtRequest(args.request)}`);
|
||||
this.message = `Message failed to be sent.${RPCError._fmtRequest(args.request)}`;
|
||||
}
|
||||
}
|
||||
|
||||
class FloodTestPhoneWaitError extends FloodError {
|
||||
constructor(args) {
|
||||
@ -92,6 +98,7 @@ const rpcErrorRe = [
|
||||
[/FILE_MIGRATE_(\d+)/, FileMigrateError],
|
||||
[/FLOOD_TEST_PHONE_WAIT_(\d+)/, FloodTestPhoneWaitError],
|
||||
[/FLOOD_WAIT_(\d+)/, FloodWaitError],
|
||||
[/MSG_WAIT_(.*)/, MsgWaitError],
|
||||
[/PHONE_MIGRATE_(\d+)/, PhoneMigrateError],
|
||||
[/SLOWMODE_WAIT_(\d+)/, SlowModeWaitError],
|
||||
[/USER_MIGRATE_(\d+)/, UserMigrateError],
|
||||
@ -108,5 +115,6 @@ module.exports = {
|
||||
SlowModeWaitError,
|
||||
UserMigrateError,
|
||||
NetworkMigrateError,
|
||||
MsgWaitError,
|
||||
EmailUnconfirmedError,
|
||||
};
|
||||
|
||||
@ -2,6 +2,11 @@ const MessageContainer = require('../tl/core/MessageContainer');
|
||||
const TLMessage = require('../tl/core/TLMessage');
|
||||
const BinaryWriter = require('../extensions/BinaryWriter');
|
||||
|
||||
const USE_INVOKE_AFTER_WITH = [
|
||||
'messages.SendMessage', 'messages.SendMedia', 'messages.SendMultiMedia',
|
||||
'messages.ForwardMessages', 'messages.SendInlineBotResult',
|
||||
];
|
||||
|
||||
class MessagePacker {
|
||||
constructor(state, logger) {
|
||||
this._state = state;
|
||||
@ -18,6 +23,19 @@ class MessagePacker {
|
||||
}
|
||||
|
||||
append(state) {
|
||||
// we need to check if there is already a request with the same name that we should send after.
|
||||
if (USE_INVOKE_AFTER_WITH.includes(state.request.className)) {
|
||||
// we now need to check if there is any request in queue already.
|
||||
// we loop backwards since the latest request is the most recent
|
||||
for (let i = this._queue.length - 1; i >= 0; i--) {
|
||||
if (USE_INVOKE_AFTER_WITH.includes(this._queue[i].request.className)) {
|
||||
state.after = this._queue[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this._queue.push(state);
|
||||
this.setReady(true);
|
||||
|
||||
|
||||
@ -1,195 +0,0 @@
|
||||
const BigInt = require('big-integer');
|
||||
const IGE = require('../crypto/IGE');
|
||||
const AuthKey = require('../crypto/AuthKey');
|
||||
const Factorizator = require('../crypto/Factorizator');
|
||||
const RSA = require('../crypto/RSA');
|
||||
const Helpers = require('../Helpers');
|
||||
const {
|
||||
constructors,
|
||||
requests,
|
||||
} = require('../tl');
|
||||
const BinaryReader = require('../extensions/BinaryReader');
|
||||
const { SecurityError } = require('../errors/Common');
|
||||
|
||||
/**
|
||||
* Executes the authentication process with the Telegram servers.
|
||||
* @param sender a connected {MTProtoPlainSender}.
|
||||
* @param log
|
||||
* @returns {Promise<{authKey: *, timeOffset: *}>}
|
||||
*/
|
||||
async function doAuthentication(sender, log) {
|
||||
// Step 1 sending: PQ Request, endianness doesn't matter since it's random
|
||||
let bytes = Helpers.generateRandomBytes(16);
|
||||
|
||||
const nonce = Helpers.readBigIntFromBuffer(bytes, false, true);
|
||||
|
||||
const resPQ = await sender.send(new requests.ReqPqMulti({ nonce }));
|
||||
log.debug('Starting authKey generation step 1');
|
||||
|
||||
if (!(resPQ instanceof constructors.ResPQ)) {
|
||||
throw new Error(`Step 1 answer was ${resPQ}`);
|
||||
}
|
||||
if (resPQ.nonce.neq(nonce)) {
|
||||
throw new SecurityError('Step 1 invalid nonce from server');
|
||||
}
|
||||
const pq = Helpers.readBigIntFromBuffer(resPQ.pq, false, true);
|
||||
log.debug('Finished authKey generation step 1');
|
||||
log.debug('Starting authKey generation step 2');
|
||||
// Step 2 sending: DH Exchange
|
||||
let {
|
||||
p,
|
||||
q,
|
||||
} = Factorizator.factorize(pq);
|
||||
|
||||
// TODO Bring back after `Factorizator` fix.
|
||||
p = Helpers.getByteArray(p);
|
||||
q = Helpers.getByteArray(q);
|
||||
|
||||
bytes = Helpers.generateRandomBytes(32);
|
||||
const newNonce = Helpers.readBigIntFromBuffer(bytes, true, true);
|
||||
|
||||
const pqInnerData = new constructors.PQInnerData({
|
||||
pq: Helpers.getByteArray(pq), // unsigned
|
||||
p,
|
||||
q,
|
||||
nonce: resPQ.nonce,
|
||||
serverNonce: resPQ.serverNonce,
|
||||
newNonce,
|
||||
});
|
||||
|
||||
// sha_digest + data + random_bytes
|
||||
let cipherText;
|
||||
let targetFingerprint;
|
||||
for (const fingerprint of resPQ.serverPublicKeyFingerprints) {
|
||||
cipherText = await RSA.encrypt(fingerprint.toString(), pqInnerData.getBytes());
|
||||
if (cipherText !== undefined) {
|
||||
targetFingerprint = fingerprint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cipherText === undefined) {
|
||||
throw new SecurityError('Step 2 could not find a valid key for fingerprints');
|
||||
}
|
||||
|
||||
const serverDhParams = await sender.send(
|
||||
new requests.ReqDHParams({
|
||||
nonce: resPQ.nonce,
|
||||
serverNonce: resPQ.serverNonce,
|
||||
p,
|
||||
q,
|
||||
publicKeyFingerprint: targetFingerprint,
|
||||
encryptedData: cipherText,
|
||||
}),
|
||||
);
|
||||
if (!(serverDhParams instanceof constructors.ServerDHParamsOk
|
||||
|| serverDhParams instanceof constructors.ServerDHParamsFail)) {
|
||||
throw new Error(`Step 2.1 answer was ${serverDhParams}`);
|
||||
}
|
||||
if (serverDhParams.nonce.neq(resPQ.nonce)) {
|
||||
throw new SecurityError('Step 2 invalid nonce from server');
|
||||
}
|
||||
|
||||
if (serverDhParams.serverNonce.neq(resPQ.serverNonce)) {
|
||||
throw new SecurityError('Step 2 invalid server nonce from server');
|
||||
}
|
||||
|
||||
if (serverDhParams instanceof constructors.ServerDHParamsFail) {
|
||||
const sh = await Helpers.sha1(Helpers.toSignedLittleBuffer(newNonce, 32)
|
||||
.slice(4, 20));
|
||||
const nnh = Helpers.readBigIntFromBuffer(sh, true, true);
|
||||
if (serverDhParams.newNonceHash.neq(nnh)) {
|
||||
throw new SecurityError('Step 2 invalid DH fail nonce from server');
|
||||
}
|
||||
}
|
||||
if (!(serverDhParams instanceof constructors.ServerDHParamsOk)) {
|
||||
throw new Error(`Step 2.2 answer was ${serverDhParams}`);
|
||||
}
|
||||
log.debug('Finished authKey generation step 2');
|
||||
log.debug('Starting authKey generation step 3');
|
||||
|
||||
// Step 3 sending: Complete DH Exchange
|
||||
const {
|
||||
key,
|
||||
iv,
|
||||
} = await Helpers.generateKeyDataFromNonce(resPQ.serverNonce, newNonce);
|
||||
if (serverDhParams.encryptedAnswer.length % 16 !== 0) {
|
||||
// See PR#453
|
||||
throw new SecurityError('Step 3 AES block size mismatch');
|
||||
}
|
||||
const ige = new IGE(key, iv);
|
||||
const plainTextAnswer = ige.decryptIge(serverDhParams.encryptedAnswer);
|
||||
const reader = new BinaryReader(plainTextAnswer);
|
||||
reader.read(20); // hash sum
|
||||
const serverDhInner = reader.tgReadObject();
|
||||
if (!(serverDhInner instanceof constructors.ServerDHInnerData)) {
|
||||
throw new Error(`Step 3 answer was ${serverDhInner}`);
|
||||
}
|
||||
|
||||
if (serverDhInner.nonce.neq(resPQ.nonce)) {
|
||||
throw new SecurityError('Step 3 Invalid nonce in encrypted answer');
|
||||
}
|
||||
if (serverDhInner.serverNonce.neq(resPQ.serverNonce)) {
|
||||
throw new SecurityError('Step 3 Invalid server nonce in encrypted answer');
|
||||
}
|
||||
const dhPrime = Helpers.readBigIntFromBuffer(serverDhInner.dhPrime, false, false);
|
||||
const ga = Helpers.readBigIntFromBuffer(serverDhInner.gA, false, false);
|
||||
const timeOffset = serverDhInner.serverTime - Math.floor(new Date().getTime() / 1000);
|
||||
const b = Helpers.readBigIntFromBuffer(Helpers.generateRandomBytes(256), false, false);
|
||||
const gb = Helpers.modExp(BigInt(serverDhInner.g), b, dhPrime);
|
||||
const gab = Helpers.modExp(ga, b, dhPrime);
|
||||
|
||||
// Prepare client DH Inner Data
|
||||
const clientDhInner = new constructors.ClientDHInnerData({
|
||||
nonce: resPQ.nonce,
|
||||
serverNonce: resPQ.serverNonce,
|
||||
retryId: 0, // TODO Actual retry ID
|
||||
gB: Helpers.getByteArray(gb, false),
|
||||
}).getBytes();
|
||||
|
||||
const clientDdhInnerHashed = Buffer.concat([await Helpers.sha1(clientDhInner), clientDhInner]);
|
||||
// Encryption
|
||||
|
||||
const clientDhEncrypted = ige.encryptIge(clientDdhInnerHashed);
|
||||
const dhGen = await sender.send(
|
||||
new requests.SetClientDHParams({
|
||||
nonce: resPQ.nonce,
|
||||
serverNonce: resPQ.serverNonce,
|
||||
encryptedData: clientDhEncrypted,
|
||||
}),
|
||||
);
|
||||
const nonceTypes = [constructors.DhGenOk, constructors.DhGenRetry, constructors.DhGenFail];
|
||||
if (!(dhGen instanceof nonceTypes[0] || dhGen instanceof nonceTypes[1] || dhGen instanceof nonceTypes[2])) {
|
||||
throw new Error(`Step 3.1 answer was ${dhGen}`);
|
||||
}
|
||||
const { name } = dhGen.constructor;
|
||||
if (dhGen.nonce.neq(resPQ.nonce)) {
|
||||
throw new SecurityError(`Step 3 invalid ${name} nonce from server`);
|
||||
}
|
||||
if (dhGen.serverNonce.neq(resPQ.serverNonce)) {
|
||||
throw new SecurityError(`Step 3 invalid ${name} server nonce from server`);
|
||||
}
|
||||
const authKey = new AuthKey();
|
||||
await authKey.setKey(Helpers.getByteArray(gab));
|
||||
|
||||
const nonceNumber = 1 + nonceTypes.indexOf(dhGen.constructor);
|
||||
|
||||
const newNonceHash = await authKey.calcNewNonceHash(newNonce, nonceNumber);
|
||||
const dhHash = dhGen[`newNonceHash${nonceNumber}`];
|
||||
|
||||
if (dhHash.neq(newNonceHash)) {
|
||||
throw new SecurityError('Step 3 invalid new nonce hash');
|
||||
}
|
||||
|
||||
if (!(dhGen instanceof constructors.DhGenOk)) {
|
||||
throw new Error(`Step 3.2 answer was ${dhGen}`);
|
||||
}
|
||||
log.debug('Finished authKey generation step 3');
|
||||
|
||||
return {
|
||||
authKey,
|
||||
timeOffset,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
module.exports = doAuthentication;
|
||||
250
src/lib/gramjs/network/Authenticator.ts
Normal file
250
src/lib/gramjs/network/Authenticator.ts
Normal file
@ -0,0 +1,250 @@
|
||||
/**
|
||||
* Executes the authentication process with the Telegram servers.
|
||||
* @param sender a connected {MTProtoPlainSender}.
|
||||
* @param log
|
||||
* @returns {Promise<{authKey: *, timeOffset: *}>}
|
||||
*/
|
||||
// eslint-disable-next-line import/no-named-default
|
||||
import { default as Api } from '../tl/api';
|
||||
import { SecurityError } from '../errors';
|
||||
// eslint-disable-next-line import/no-named-default
|
||||
import { default as MTProtoPlainSender } from './MTProtoPlainSender';
|
||||
import { _serverKeys } from '../crypto/RSA';
|
||||
|
||||
const bigInt = require('big-integer');
|
||||
const IGE = require('../crypto/IGE');
|
||||
const AuthKey = require('../crypto/AuthKey');
|
||||
const Factorizator = require('../crypto/Factorizator');
|
||||
const Helpers = require('../Helpers');
|
||||
const BinaryReader = require('../extensions/BinaryReader');
|
||||
|
||||
const RETRIES = 20;
|
||||
|
||||
export async function doAuthentication(sender: MTProtoPlainSender, log: any) {
|
||||
// Step 1 sending: PQ Request, endianness doesn't matter since it's random
|
||||
let bytes = Helpers.generateRandomBytes(16);
|
||||
|
||||
const nonce = Helpers.readBigIntFromBuffer(bytes, false, true);
|
||||
const resPQ = await sender.send(new Api.ReqPqMulti({ nonce }));
|
||||
log.debug('Starting authKey generation step 1');
|
||||
|
||||
if (!(resPQ instanceof Api.ResPQ)) {
|
||||
throw new SecurityError(`Step 1 answer was ${resPQ}`);
|
||||
}
|
||||
if (resPQ.nonce.neq(nonce)) {
|
||||
throw new SecurityError('Step 1 invalid nonce from server');
|
||||
}
|
||||
const pq = Helpers.readBigIntFromBuffer(resPQ.pq, false, true);
|
||||
log.debug('Finished authKey generation step 1');
|
||||
// Step 2 sending: DH Exchange
|
||||
const { p, q } = Factorizator.factorize(pq);
|
||||
|
||||
const pBuffer = Helpers.getByteArray(p);
|
||||
const qBuffer = Helpers.getByteArray(q);
|
||||
|
||||
bytes = Helpers.generateRandomBytes(32);
|
||||
const newNonce = Helpers.readBigIntFromBuffer(bytes, true, true);
|
||||
const pqInnerData = new Api.PQInnerData({
|
||||
pq: Helpers.getByteArray(pq), // unsigned
|
||||
p: pBuffer,
|
||||
q: qBuffer,
|
||||
nonce: resPQ.nonce,
|
||||
serverNonce: resPQ.serverNonce,
|
||||
newNonce,
|
||||
}).getBytes();
|
||||
if (pqInnerData.length > 144) {
|
||||
throw new SecurityError('Step 1 invalid nonce from server');
|
||||
}
|
||||
let targetFingerprint;
|
||||
let targetKey;
|
||||
for (const fingerprint of resPQ.serverPublicKeyFingerprints) {
|
||||
targetKey = _serverKeys.get(fingerprint.toString());
|
||||
if (targetKey !== undefined) {
|
||||
targetFingerprint = fingerprint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetFingerprint === undefined || targetKey === undefined) {
|
||||
throw new SecurityError(
|
||||
'Step 2 could not find a valid key for fingerprints',
|
||||
);
|
||||
}
|
||||
// Value should be padded to be made 192 exactly
|
||||
const padding = Helpers.generateRandomBytes(192 - pqInnerData.length);
|
||||
const dataWithPadding = Buffer.concat([pqInnerData, padding]);
|
||||
const dataPadReversed = Buffer.from(dataWithPadding).reverse();
|
||||
|
||||
let encryptedData;
|
||||
for (let i = 0; i < RETRIES; i++) {
|
||||
const tempKey = Helpers.generateRandomBytes(32);
|
||||
const shaDigestKeyWithData = await Helpers.sha256(Buffer.concat([tempKey, dataWithPadding]));
|
||||
const dataWithHash = Buffer.concat([dataPadReversed, shaDigestKeyWithData]);
|
||||
|
||||
const ige = new IGE(tempKey, Buffer.alloc(32));
|
||||
const aesEncrypted = ige.encryptIge(dataWithHash);
|
||||
const tempKeyXor = Helpers.bufferXor(tempKey, await Helpers.sha256(aesEncrypted));
|
||||
|
||||
const keyAesEncrypted = Buffer.concat([tempKeyXor, aesEncrypted]);
|
||||
const keyAesEncryptedInt = Helpers.readBigIntFromBuffer(keyAesEncrypted, false, false);
|
||||
if (keyAesEncryptedInt.greaterOrEquals(targetKey.n)) {
|
||||
log.debug('Aes key greater than RSA. retrying');
|
||||
continue;
|
||||
}
|
||||
const encryptedDataBuffer = Helpers.modExp(keyAesEncryptedInt, bigInt(targetKey.e), targetKey.n);
|
||||
encryptedData = Helpers.readBufferFromBigInt(encryptedDataBuffer, 256, false, false);
|
||||
|
||||
break;
|
||||
}
|
||||
if (encryptedData === undefined) {
|
||||
throw new SecurityError(
|
||||
'Step 2 could create a secure encrypted key',
|
||||
);
|
||||
}
|
||||
log.debug('Step 2 : Generated a secure aes encrypted data');
|
||||
|
||||
const serverDhParams = await sender.send(
|
||||
new Api.ReqDHParams({
|
||||
nonce: resPQ.nonce,
|
||||
serverNonce: resPQ.serverNonce,
|
||||
p: pBuffer,
|
||||
q: qBuffer,
|
||||
publicKeyFingerprint: targetFingerprint,
|
||||
encryptedData,
|
||||
}),
|
||||
);
|
||||
|
||||
if (
|
||||
!(
|
||||
serverDhParams instanceof Api.ServerDHParamsOk
|
||||
|| serverDhParams instanceof Api.ServerDHParamsFail
|
||||
)
|
||||
) {
|
||||
throw new Error(`Step 2.1 answer was ${serverDhParams}`);
|
||||
}
|
||||
if (serverDhParams.nonce.neq(resPQ.nonce)) {
|
||||
throw new SecurityError('Step 2 invalid nonce from server');
|
||||
}
|
||||
|
||||
if (serverDhParams.serverNonce.neq(resPQ.serverNonce)) {
|
||||
throw new SecurityError('Step 2 invalid server nonce from server');
|
||||
}
|
||||
|
||||
if (serverDhParams instanceof Api.ServerDHParamsFail) {
|
||||
const sh = await Helpers.sha1(
|
||||
Helpers.toSignedLittleBuffer(newNonce, 32).slice(4, 20),
|
||||
);
|
||||
const nnh = Helpers.readBigIntFromBuffer(sh, true, true);
|
||||
if (serverDhParams.newNonceHash.neq(nnh)) {
|
||||
throw new SecurityError('Step 2 invalid DH fail nonce from server');
|
||||
}
|
||||
}
|
||||
if (!(serverDhParams instanceof Api.ServerDHParamsOk)) {
|
||||
throw new Error(`Step 2.2 answer was ${serverDhParams}`);
|
||||
}
|
||||
log.debug('Finished authKey generation step 2');
|
||||
log.debug('Starting authKey generation step 3');
|
||||
|
||||
// Step 3 sending: Complete DH Exchange
|
||||
const { key, iv } = await Helpers.generateKeyDataFromNonce(
|
||||
resPQ.serverNonce,
|
||||
newNonce,
|
||||
);
|
||||
if (serverDhParams.encryptedAnswer.length % 16 !== 0) {
|
||||
// See PR#453
|
||||
throw new SecurityError('Step 3 AES block size mismatch');
|
||||
}
|
||||
const ige = new IGE(key, iv);
|
||||
const plainTextAnswer = ige.decryptIge(serverDhParams.encryptedAnswer);
|
||||
const reader = new BinaryReader(plainTextAnswer);
|
||||
reader.read(20); // hash sum
|
||||
const serverDhInner = reader.tgReadObject();
|
||||
if (!(serverDhInner instanceof Api.ServerDHInnerData)) {
|
||||
throw new Error(`Step 3 answer was ${serverDhInner}`);
|
||||
}
|
||||
|
||||
if (serverDhInner.nonce.neq(resPQ.nonce)) {
|
||||
throw new SecurityError('Step 3 Invalid nonce in encrypted answer');
|
||||
}
|
||||
if (serverDhInner.serverNonce.neq(resPQ.serverNonce)) {
|
||||
throw new SecurityError(
|
||||
'Step 3 Invalid server nonce in encrypted answer',
|
||||
);
|
||||
}
|
||||
const dhPrime = Helpers.readBigIntFromBuffer(
|
||||
serverDhInner.dhPrime,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
const ga = Helpers.readBigIntFromBuffer(serverDhInner.gA, false, false);
|
||||
const timeOffset = serverDhInner.serverTime - Math.floor(new Date().getTime() / 1000);
|
||||
const b = Helpers.readBigIntFromBuffer(
|
||||
Helpers.generateRandomBytes(256),
|
||||
false,
|
||||
false,
|
||||
);
|
||||
const gb = Helpers.modExp(bigInt(serverDhInner.g), b, dhPrime);
|
||||
const gab = Helpers.modExp(ga, b, dhPrime);
|
||||
|
||||
// Prepare client DH Inner Data
|
||||
const clientDhInner = new Api.ClientDHInnerData({
|
||||
nonce: resPQ.nonce,
|
||||
serverNonce: resPQ.serverNonce,
|
||||
retryId: bigInt.zero, // TODO Actual retry ID
|
||||
gB: Helpers.getByteArray(gb, false),
|
||||
}).getBytes();
|
||||
|
||||
const clientDdhInnerHashed = Buffer.concat([
|
||||
await Helpers.sha1(clientDhInner),
|
||||
clientDhInner,
|
||||
]);
|
||||
// Encryption
|
||||
|
||||
const clientDhEncrypted = ige.encryptIge(clientDdhInnerHashed);
|
||||
const dhGen = await sender.send(
|
||||
new Api.SetClientDHParams({
|
||||
nonce: resPQ.nonce,
|
||||
serverNonce: resPQ.serverNonce,
|
||||
encryptedData: clientDhEncrypted,
|
||||
}),
|
||||
);
|
||||
const nonceTypes = [Api.DhGenOk, Api.DhGenRetry, Api.DhGenFail];
|
||||
// TS being weird again.
|
||||
const nonceTypesString = ['DhGenOk', 'DhGenRetry', 'DhGenFail'];
|
||||
if (
|
||||
!(
|
||||
dhGen instanceof nonceTypes[0]
|
||||
|| dhGen instanceof nonceTypes[1]
|
||||
|| dhGen instanceof nonceTypes[2]
|
||||
)
|
||||
) {
|
||||
throw new Error(`Step 3.1 answer was ${dhGen}`);
|
||||
}
|
||||
const { name } = dhGen.constructor;
|
||||
if (dhGen.nonce.neq(resPQ.nonce)) {
|
||||
throw new SecurityError(`Step 3 invalid ${name} nonce from server`);
|
||||
}
|
||||
if (dhGen.serverNonce.neq(resPQ.serverNonce)) {
|
||||
throw new SecurityError(
|
||||
`Step 3 invalid ${name} server nonce from server`,
|
||||
);
|
||||
}
|
||||
const authKey = new AuthKey();
|
||||
await authKey.setKey(Helpers.getByteArray(gab));
|
||||
|
||||
const nonceNumber = 1 + nonceTypesString.indexOf(dhGen.className);
|
||||
|
||||
const newNonceHash = await authKey.calcNewNonceHash(newNonce, nonceNumber);
|
||||
// @ts-ignore
|
||||
const dhHash = dhGen[`newNonceHash${nonceNumber}`];
|
||||
|
||||
if (dhHash.neq(newNonceHash)) {
|
||||
throw new SecurityError('Step 3 invalid new nonce hash');
|
||||
}
|
||||
|
||||
if (!(dhGen instanceof Api.DhGenOk)) {
|
||||
throw new Error(`Step 3.2 answer was ${dhGen}`);
|
||||
}
|
||||
log.debug('Finished authKey generation step 3');
|
||||
|
||||
return { authKey, timeOffset };
|
||||
}
|
||||
@ -2,7 +2,7 @@ const MtProtoPlainSender = require('./MTProtoPlainSender');
|
||||
const MTProtoState = require('./MTProtoState');
|
||||
const Helpers = require('../Helpers');
|
||||
const AuthKey = require('../crypto/AuthKey');
|
||||
const doAuthentication = require('./Authenticator');
|
||||
const { doAuthentication } = require('./Authenticator');
|
||||
const RPCResult = require('../tl/core/RPCResult');
|
||||
const MessageContainer = require('../tl/core/MessageContainer');
|
||||
const GZIPPacked = require('../tl/core/GZIPPacked');
|
||||
@ -231,18 +231,23 @@ class MTProtoSender {
|
||||
if (!this._user_connected) {
|
||||
throw new Error('Cannot send requests while disconnected');
|
||||
}
|
||||
// CONTEST
|
||||
const state = new RequestState(request);
|
||||
this._send_queue.append(state);
|
||||
return state.promise;
|
||||
/*
|
||||
if (!Helpers.isArrayLike(request)) {
|
||||
const state = new RequestState(request)
|
||||
this._send_queue.append(state)
|
||||
return state.promise
|
||||
} else {
|
||||
throw new Error('not supported')
|
||||
} */
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as send but returns the full state. usefull for invoke after logic
|
||||
* @param request
|
||||
* @return {RequestState}
|
||||
*/
|
||||
sendWithInvokeSupport(request) {
|
||||
if (!this._user_connected) {
|
||||
throw new Error('Cannot send requests while disconnected');
|
||||
}
|
||||
const state = new RequestState(request, undefined, this._pending_state);
|
||||
this._send_queue.append(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -264,10 +269,10 @@ class MTProtoSender {
|
||||
this._log.debug('Generated new auth_key successfully');
|
||||
await this.authKey.setKey(res.authKey);
|
||||
|
||||
this._state.time_offset = res.timeOffset;
|
||||
this._state.timeOffset = res.timeOffset;
|
||||
|
||||
if (this._updateCallback) {
|
||||
this._updateCallback(new UpdateServerTimeOffset(this._state.time_offset));
|
||||
this._updateCallback(new UpdateServerTimeOffset(this._state.timeOffset));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -471,6 +476,7 @@ class MTProtoSender {
|
||||
_popStates(msgId) {
|
||||
let state = this._pending_state[msgId];
|
||||
if (state) {
|
||||
this._pending_state[msgId].deferred.resolve();
|
||||
delete this._pending_state[msgId];
|
||||
return [state];
|
||||
}
|
||||
@ -487,6 +493,7 @@ class MTProtoSender {
|
||||
const temp = [];
|
||||
for (const x of toPop) {
|
||||
temp.push(this._pending_state[x]);
|
||||
this._pending_state[x].deferred.resolve();
|
||||
delete this._pending_state[x];
|
||||
}
|
||||
return temp;
|
||||
@ -513,6 +520,7 @@ class MTProtoSender {
|
||||
const result = message.obj;
|
||||
const state = this._pending_state[result.reqMsgId];
|
||||
if (state) {
|
||||
state.deferred.resolve();
|
||||
delete this._pending_state[result.reqMsgId];
|
||||
}
|
||||
this._log.debug(`Handling RPC result for message ${result.reqMsgId}`);
|
||||
@ -607,6 +615,7 @@ class MTProtoSender {
|
||||
|
||||
this._log.debug(`Handling pong for message ${pong.msgId}`);
|
||||
const state = this._pending_state[pong.msgId];
|
||||
this._pending_state[pong.msgId].deferred.resolve();
|
||||
delete this._pending_state[pong.msgId];
|
||||
|
||||
// Todo Check result
|
||||
@ -743,6 +752,7 @@ class MTProtoSender {
|
||||
for (const msgId of ack.msgIds) {
|
||||
const state = this._pending_state[msgId];
|
||||
if (state && state.request instanceof LogOut) {
|
||||
this._pending_state[msgId].deferred.resolve();
|
||||
delete this._pending_state[msgId];
|
||||
state.resolve(true);
|
||||
}
|
||||
@ -765,6 +775,7 @@ class MTProtoSender {
|
||||
const state = this._pending_state[message.msgId];
|
||||
|
||||
if (state) {
|
||||
this._pending_state[message].deferred.resolve();
|
||||
delete this._pending_state[message];
|
||||
state.resolve(message.obj);
|
||||
}
|
||||
@ -822,6 +833,9 @@ class MTProtoSender {
|
||||
this._reconnecting = false;
|
||||
// uncomment this if you want to resend
|
||||
// this._send_queue.extend(Object.values(this._pending_state))
|
||||
for (const state of this._pending_state) {
|
||||
state.deferred.resolve();
|
||||
}
|
||||
this._pending_state = {};
|
||||
if (this._autoReconnectCallback) {
|
||||
await this._autoReconnectCallback();
|
||||
|
||||
@ -107,7 +107,12 @@ class MTProtoState {
|
||||
if (!afterId) {
|
||||
body = await GZIPPacked.gzipIfSmaller(contentRelated, data);
|
||||
} else {
|
||||
body = await GZIPPacked.gzipIfSmaller(contentRelated, new InvokeAfterMsg(afterId, data).getBytes());
|
||||
// Invoke query expects a query with a getBytes func
|
||||
body = await GZIPPacked.gzipIfSmaller(contentRelated, new InvokeAfterMsg(afterId, {
|
||||
getBytes() {
|
||||
return data;
|
||||
},
|
||||
}).getBytes());
|
||||
}
|
||||
const s = Buffer.alloc(4);
|
||||
s.writeInt32LE(seqNo, 0);
|
||||
|
||||
@ -1,16 +1,28 @@
|
||||
const { createDeferred } = require('../Helpers');
|
||||
|
||||
class RequestState {
|
||||
constructor(request, after = undefined) {
|
||||
constructor(request, after = undefined, pending = {}) {
|
||||
this.containerId = undefined;
|
||||
this.msgId = undefined;
|
||||
this.request = request;
|
||||
this.data = request.getBytes();
|
||||
this.after = after;
|
||||
this.result = undefined;
|
||||
this.pending = pending;
|
||||
this.deferred = createDeferred();
|
||||
this.promise = new Promise((resolve, reject) => {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
});
|
||||
}
|
||||
|
||||
isReady() {
|
||||
const state = this.pending[this.after.id];
|
||||
if (!state) {
|
||||
return true;
|
||||
}
|
||||
return state.deferred.promise;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RequestState;
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
const MTProtoPlainSender = require('./MTProtoPlainSender');
|
||||
const doAuthentication = require('./Authenticator');
|
||||
const MTProtoSender = require('./MTProtoSender');
|
||||
|
||||
class UpdateConnectionState {
|
||||
@ -33,7 +32,6 @@ module.exports = {
|
||||
ConnectionTCPAbridged,
|
||||
ConnectionTCPObfuscated,
|
||||
MTProtoPlainSender,
|
||||
doAuthentication,
|
||||
MTProtoSender,
|
||||
UpdateConnectionState,
|
||||
UpdateServerTimeOffset,
|
||||
|
||||
12
src/lib/gramjs/tl/api.d.ts
vendored
12
src/lib/gramjs/tl/api.d.ts
vendored
@ -20,8 +20,8 @@ namespace Api {
|
||||
type Type = unknown;
|
||||
type Bool = boolean;
|
||||
type int = number;
|
||||
type int128 = number;
|
||||
type int256 = number;
|
||||
type int128 = BigInteger;
|
||||
type int256 = BigInteger;
|
||||
type long = BigInteger;
|
||||
type bytes = Buffer;
|
||||
|
||||
@ -35,6 +35,7 @@ namespace Api {
|
||||
static serializeDate(date: Date | number): Buffer;
|
||||
static fromReader(reader: Reader): VirtualClass<Args>;
|
||||
|
||||
getBytes(): Buffer;
|
||||
CONSTRUCTOR_ID: number;
|
||||
SUBCLASS_OF_ID: number;
|
||||
className: string;
|
||||
@ -8552,6 +8553,11 @@ namespace Api {
|
||||
}>, Api.TypeResPQ> {
|
||||
nonce: int128;
|
||||
};
|
||||
export class ReqPqMultiNew extends Request<Partial<{
|
||||
nonce: int128;
|
||||
}>, Api.TypeResPQ> {
|
||||
nonce: int128;
|
||||
};
|
||||
export class ReqDHParams extends Request<Partial<{
|
||||
nonce: int128;
|
||||
serverNonce: int128;
|
||||
@ -11707,7 +11713,7 @@ namespace Api {
|
||||
};
|
||||
}
|
||||
|
||||
export type AnyRequest = InvokeAfterMsg | InvokeAfterMsgs | InitConnection | InvokeWithLayer | InvokeWithoutUpdates | InvokeWithMessagesRange | InvokeWithTakeout | ReqPq | ReqPqMulti | ReqDHParams | SetClientDHParams | DestroyAuthKey | RpcDropAnswer | GetFutureSalts | Ping | PingDelayDisconnect | DestroySession
|
||||
export type AnyRequest = InvokeAfterMsg | InvokeAfterMsgs | InitConnection | InvokeWithLayer | InvokeWithoutUpdates | InvokeWithMessagesRange | InvokeWithTakeout | ReqPq | ReqPqMulti | ReqPqMultiNew | ReqDHParams | SetClientDHParams | DestroyAuthKey | RpcDropAnswer | GetFutureSalts | Ping | PingDelayDisconnect | DestroySession
|
||||
| auth.SendCode | auth.SignUp | auth.SignIn | auth.LogOut | auth.ResetAuthorizations | auth.ExportAuthorization | auth.ImportAuthorization | auth.BindTempAuthKey | auth.ImportBotAuthorization | auth.CheckPassword | auth.RequestPasswordRecovery | auth.RecoverPassword | auth.ResendCode | auth.CancelCode | auth.DropTempAuthKeys | auth.ExportLoginToken | auth.ImportLoginToken | auth.AcceptLoginToken | auth.CheckRecoveryPassword
|
||||
| account.RegisterDevice | account.UnregisterDevice | account.UpdateNotifySettings | account.GetNotifySettings | account.ResetNotifySettings | account.UpdateProfile | account.UpdateStatus | account.GetWallPapers | account.ReportPeer | account.CheckUsername | account.UpdateUsername | account.GetPrivacy | account.SetPrivacy | account.DeleteAccount | account.GetAccountTTL | account.SetAccountTTL | account.SendChangePhoneCode | account.ChangePhone | account.UpdateDeviceLocked | account.GetAuthorizations | account.ResetAuthorization | account.GetPassword | account.GetPasswordSettings | account.UpdatePasswordSettings | account.SendConfirmPhoneCode | account.ConfirmPhone | account.GetTmpPassword | account.GetWebAuthorizations | account.ResetWebAuthorization | account.ResetWebAuthorizations | account.GetAllSecureValues | account.GetSecureValue | account.SaveSecureValue | account.DeleteSecureValue | account.GetAuthorizationForm | account.AcceptAuthorization | account.SendVerifyPhoneCode | account.VerifyPhone | account.SendVerifyEmailCode | account.VerifyEmail | account.InitTakeoutSession | account.FinishTakeoutSession | account.ConfirmPasswordEmail | account.ResendPasswordEmail | account.CancelPasswordEmail | account.GetContactSignUpNotification | account.SetContactSignUpNotification | account.GetNotifyExceptions | account.GetWallPaper | account.UploadWallPaper | account.SaveWallPaper | account.InstallWallPaper | account.ResetWallPapers | account.GetAutoDownloadSettings | account.SaveAutoDownloadSettings | account.UploadTheme | account.CreateTheme | account.UpdateTheme | account.SaveTheme | account.InstallTheme | account.GetTheme | account.GetThemes | account.SetContentSettings | account.GetContentSettings | account.GetMultiWallPapers | account.GetGlobalPrivacySettings | account.SetGlobalPrivacySettings | account.ReportProfilePhoto | account.ResetPassword | account.DeclinePasswordReset
|
||||
| users.GetUsers | users.GetFullUser | users.SetSecureValueErrors
|
||||
|
||||
@ -13,6 +13,7 @@ destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes;
|
||||
---functions---
|
||||
req_pq#60469778 nonce:int128 = ResPQ;
|
||||
req_pq_multi#be7e8ef1 nonce:int128 = ResPQ;
|
||||
req_pq_multi_new#51b410fd nonce:int128 = ResPQ;
|
||||
req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:string q:string public_key_fingerprint:long encrypted_data:string = Server_DH_Params;
|
||||
set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:string = Set_client_DH_params_answer;
|
||||
destroy_auth_key#d1435160 = DestroyAuthKeyRes;
|
||||
|
||||
@ -13,6 +13,7 @@ destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes;
|
||||
---functions---
|
||||
req_pq#60469778 nonce:int128 = ResPQ;
|
||||
req_pq_multi#be7e8ef1 nonce:int128 = ResPQ;
|
||||
req_pq_multi_new#51b410fd nonce:int128 = ResPQ;
|
||||
req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:string q:string public_key_fingerprint:long encrypted_data:string = Server_DH_Params;
|
||||
set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:string = Set_client_DH_params_answer;
|
||||
destroy_auth_key#d1435160 = DestroyAuthKeyRes;
|
||||
|
||||
@ -32,6 +32,7 @@ destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes;
|
||||
|
||||
req_pq#60469778 nonce:int128 = ResPQ;
|
||||
req_pq_multi#be7e8ef1 nonce:int128 = ResPQ;
|
||||
req_pq_multi_new#51b410fd nonce:int128 = ResPQ;
|
||||
|
||||
req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:string q:string public_key_fingerprint:long encrypted_data:string = Server_DH_Params;
|
||||
|
||||
|
||||
@ -152,8 +152,8 @@ namespace Api {
|
||||
type Type = unknown;
|
||||
type Bool = boolean;
|
||||
type int = number;
|
||||
type int128 = number;
|
||||
type int256 = number;
|
||||
type int128 = BigInteger;
|
||||
type int256 = BigInteger;
|
||||
type long = BigInteger;
|
||||
type bytes = Buffer;
|
||||
|
||||
@ -167,6 +167,7 @@ namespace Api {
|
||||
static serializeDate(date: Date | number): Buffer;
|
||||
static fromReader(reader: Reader): VirtualClass<Args>;
|
||||
|
||||
getBytes(): Buffer;
|
||||
CONSTRUCTOR_ID: number;
|
||||
SUBCLASS_OF_ID: number;
|
||||
className: string;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user