UNPKG

@tgsnake/core

Version:

Pure Telegram MTProto library for nodejs

255 lines (254 loc) 13.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.Auth = void 0; const connection_js_1 = require("../connection/connection.js"); const AES = __importStar(require("../crypto/Aes.js")); const Prime = __importStar(require("../crypto/Prime.js")); const RSA = __importStar(require("../crypto/RSA.js")); const platform_node_js_1 = require("../platform.node.js"); const index_js_1 = require("../errors/index.js"); const index_js_2 = require("../raw/index.js"); const MsgId_js_1 = require("./internals/MsgId.js"); const helpers_js_1 = require("../helpers.js"); const Logger_js_1 = require("../Logger.js"); class Auth { MAX_RETRIES = 5; dcId; testMode; ipv6; connection; constructor(dcId, testMode, ipv6) { this.dcId = dcId; this.testMode = testMode; this.ipv6 = ipv6; } static pack(data) { return platform_node_js_1.Buffer.concat([ platform_node_js_1.Buffer.alloc(8), index_js_2.Primitive.Long.write(BigInt(new MsgId_js_1.MsgId().getMsgId())), index_js_2.Primitive.Int.write(platform_node_js_1.Buffer.byteLength(data.write())), data.write(), ]); } static async unpack(b) { b.seek(20, 1); return await index_js_2.TLObject.read(b); } async invoke(data) { const content = Auth.pack(data); await this.connection.send(content); const response = new index_js_2.BytesIO(await this.connection.recv()); return await Auth.unpack(response); } async create() { let retries = this.MAX_RETRIES; while (true) { this.connection = new connection_js_1.Connection(this.dcId, this.testMode, this.ipv6); try { Logger_js_1.Logger.debug(`[11] Start creating a new auth key on DC${this.dcId}`); await this.connection.connect(); const nonce = (0, helpers_js_1.bufferToBigint)(platform_node_js_1.Buffer.from(platform_node_js_1.crypto.randomBytes(16)), false, true); Logger_js_1.Logger.debug(`[12] Send ResPq: ${nonce}`); const resPq = await this.invoke(new index_js_2.Raw.ReqPqMulti({ nonce })); Logger_js_1.Logger.debug(`[13] Got ResPq: ${resPq.serverNonce}`); Logger_js_1.Logger.debug(`[14] Server public key fingerprints: ${resPq.serverPublicKeyFingerprints}`); let fingerprints; if (!resPq.serverPublicKeyFingerprints || !resPq.serverPublicKeyFingerprints.length) throw new Error('Public key not found'); for (const i of resPq.serverPublicKeyFingerprints) { if (RSA.PublicKey.get(BigInt(i))) { Logger_js_1.Logger.debug(`[15] Using fingerprint: ${i}`); fingerprints = BigInt(i); break; } else { Logger_js_1.Logger.debug(`[16] Fingerprint unknown: ${i}`); } } const pq = (0, helpers_js_1.bufferToBigint)(resPq.pq, false); Logger_js_1.Logger.debug(`[17] Start PQ factorization: ${pq}`); const start = Math.floor(Date.now() / 1000); const g = Prime.decompose(pq); const [p, q] = [BigInt(g), BigInt(pq / g)].sort((a, b) => { if (a > b) return 1; if (a < b) return -1; return 0; }); Logger_js_1.Logger.debug(`[18] Done PQ factorization (${Math.round(Math.floor(Date.now() / 1000) - start)}s): ${p} ${q}`); const newNonce = (0, helpers_js_1.bufferToBigint)(platform_node_js_1.Buffer.from(platform_node_js_1.crypto.randomBytes(32)), true, true); const pBytes = (0, helpers_js_1.bigintToBuffer)(BigInt(p), 4, false); const qBytes = (0, helpers_js_1.bigintToBuffer)(BigInt(q), 4, false); let data = new index_js_2.Raw.PQInnerData({ pq: resPq.pq, p: pBytes, q: qBytes, nonce: nonce, newNonce: newNonce, serverNonce: resPq.serverNonce, }).write(); let sha = platform_node_js_1.crypto.createHash('sha1').update(data).digest(); let padding = platform_node_js_1.Buffer.from(platform_node_js_1.crypto.randomBytes((0, helpers_js_1.mod)(-(platform_node_js_1.Buffer.byteLength(data) + platform_node_js_1.Buffer.byteLength(sha)), 255))); let hash = platform_node_js_1.Buffer.concat([ sha, data, padding, ]); let encryptedData = RSA.encrypt(hash, fingerprints); Logger_js_1.Logger.debug(`[19] Length of encrypted data: ${platform_node_js_1.Buffer.byteLength(encryptedData)}`); Logger_js_1.Logger.debug(`[20] Done encrypt data with RSA`); Logger_js_1.Logger.debug(`[21] Send ReqDhParams`); const serverDh = await this.invoke(new index_js_2.Raw.ReqDhParams({ nonce: nonce, serverNonce: resPq.serverNonce, encryptedData: encryptedData, p: pBytes, q: qBytes, publicKeyFingerprint: fingerprints, })); const tempAesKey = platform_node_js_1.Buffer.concat([ platform_node_js_1.crypto .createHash('sha1') .update(platform_node_js_1.Buffer.concat([ index_js_2.Primitive.Int256.write(newNonce), index_js_2.Primitive.Int128.write(resPq.serverNonce), ])) .digest(), platform_node_js_1.crypto .createHash('sha1') .update(platform_node_js_1.Buffer.concat([ index_js_2.Primitive.Int128.write(resPq.serverNonce), index_js_2.Primitive.Int256.write(newNonce), ])) .digest() .subarray(0, 12), ]); const tempAesIv = platform_node_js_1.Buffer.concat([ platform_node_js_1.crypto .createHash('sha1') .update(platform_node_js_1.Buffer.concat([ index_js_2.Primitive.Int128.write(resPq.serverNonce), index_js_2.Primitive.Int256.write(newNonce), ])) .digest() .subarray(12), platform_node_js_1.crypto .createHash('sha1') .update(platform_node_js_1.Buffer.concat([ index_js_2.Primitive.Int256.write(newNonce), index_js_2.Primitive.Int256.write(newNonce), ])) .digest(), index_js_2.Primitive.Int256.write(newNonce).subarray(0, 4), ]); const answerWithHash = AES.ige256Decrypt(serverDh.encryptedAnswer, tempAesKey, tempAesIv); const answer = new index_js_2.BytesIO(answerWithHash); answer.seek(20, 1); const serverDhInnerData = await index_js_2.TLObject.read(answer); Logger_js_1.Logger.debug('[22] Done decrypting answer'); const dhPrime = (0, helpers_js_1.bufferToBigint)(serverDhInnerData.dhPrime, false); const deltaTime = serverDhInnerData.serverTime - Math.floor(Date.now() / 1000); Logger_js_1.Logger.debug(`[23] Delta time: ${deltaTime}`); const b = (0, helpers_js_1.bufferToBigint)(platform_node_js_1.Buffer.from(platform_node_js_1.crypto.randomBytes(256)), false); const gB = (0, helpers_js_1.bigIntPow)(BigInt(serverDhInnerData.g), b, dhPrime); data = new index_js_2.Raw.ClientDhInnerData({ nonce: resPq.nonce, serverNonce: resPq.serverNonce, retryId: BigInt(0), gB: (0, helpers_js_1.bigintToBuffer)(gB, 256, false), }).write(); sha = platform_node_js_1.crypto.createHash('sha1').update(data).digest(); padding = platform_node_js_1.Buffer.from(platform_node_js_1.crypto.randomBytes((0, helpers_js_1.mod)(-(platform_node_js_1.Buffer.byteLength(data) + platform_node_js_1.Buffer.byteLength(sha)), 16))); hash = platform_node_js_1.Buffer.concat([ sha, data, padding, ]); encryptedData = AES.ige256Encrypt(hash, tempAesKey, tempAesIv); Logger_js_1.Logger.debug(`[24] Length of encrypted data: ${platform_node_js_1.Buffer.byteLength(encryptedData)}`); Logger_js_1.Logger.debug(`[25] Send SetClientDhParams`); const setClientDhParamsAnswer = await this.invoke(new index_js_2.Raw.SetClientDhParams({ nonce: resPq.nonce, serverNonce: resPq.serverNonce, encryptedData: encryptedData, })); const gA = (0, helpers_js_1.bufferToBigint)(serverDhInnerData.gA, false); const authKey = (0, helpers_js_1.bigintToBuffer)((0, helpers_js_1.bigIntPow)(gA, b, dhPrime), 256, false); index_js_1.SecurityCheckMismatch.check(dhPrime === Prime.CURRENT_DH_PRIME); Logger_js_1.Logger.debug('[26] DH parameters check: OK'); index_js_1.SecurityCheckMismatch.check(BigInt(1) < g && g < dhPrime - BigInt(1)); index_js_1.SecurityCheckMismatch.check(BigInt(1) < gA && gA < dhPrime - BigInt(1)); index_js_1.SecurityCheckMismatch.check(BigInt(1) < gB && gB < dhPrime - BigInt(1)); index_js_1.SecurityCheckMismatch.check(BigInt(2) ** BigInt(2048 - 64) < gA && gA < dhPrime - BigInt(2) ** BigInt(2048 - 64)); index_js_1.SecurityCheckMismatch.check(BigInt(2) ** BigInt(2048 - 64) < gB && gB < dhPrime - BigInt(2) ** BigInt(2048 - 64)); Logger_js_1.Logger.debug('[27] gA and gB validation: OK'); index_js_1.SecurityCheckMismatch.check(answerWithHash .subarray(0, 20) .equals(platform_node_js_1.crypto .createHash('sha1') .update(serverDhInnerData.write()) .digest())); Logger_js_1.Logger.debug('[28] SHA1 hash values check: OK'); index_js_1.SecurityCheckMismatch.check(nonce === resPq.nonce); index_js_1.SecurityCheckMismatch.check(resPq.nonce === serverDh.nonce); index_js_1.SecurityCheckMismatch.check(resPq.serverNonce === serverDh.serverNonce); index_js_1.SecurityCheckMismatch.check(resPq.nonce === setClientDhParamsAnswer.nonce); index_js_1.SecurityCheckMismatch.check(resPq.serverNonce === setClientDhParamsAnswer.serverNonce); Logger_js_1.Logger.debug('[29] Nonce fields check: OK'); const serverSalt = AES.xor((0, helpers_js_1.bigintToBuffer)(newNonce, 32, true, true).subarray(0, 8), (0, helpers_js_1.bigintToBuffer)(resPq.serverNonce, 16, true, true).subarray(0, 8)); Logger_js_1.Logger.debug(`[30] Server salt: ${(0, helpers_js_1.bufferToBigint)(serverSalt, true)}`); Logger_js_1.Logger.debug(`[31] Done auth key exchange: ${setClientDhParamsAnswer.className}`); return authKey; } catch (error) { Logger_js_1.Logger.error('[32] Error when trying to make auth key: ', error); if (retries > 0) { retries--; } else { throw error; } await (0, helpers_js_1.sleep)(1000); continue; } finally { this.connection.close(); } } } } exports.Auth = Auth;