dograma
Version:
NodeJS/Browser MTProto API Telegram client library,
194 lines (193 loc) • 9.43 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.doAuthentication = void 0;
const Helpers_1 = require("../Helpers");
const tl_1 = require("../tl");
const errors_1 = require("../errors");
const Factorizator_1 = require("../crypto/Factorizator");
const RSA_1 = require("../crypto/RSA");
const IGE_1 = require("../crypto/IGE");
const big_integer_1 = __importDefault(require("big-integer"));
const extensions_1 = require("../extensions");
const AuthKey_1 = require("../crypto/AuthKey");
const RETRIES = 20;
async function doAuthentication(sender, log) {
// Step 1 sending: PQ Request, endianness doesn't matter since it's random
let bytes = (0, Helpers_1.generateRandomBytes)(16);
const nonce = (0, Helpers_1.readBigIntFromBuffer)(bytes, false, true);
const resPQ = await sender.send(new tl_1.Api.ReqPqMulti({ nonce }));
log.debug("Starting authKey generation step 1");
if (!(resPQ instanceof tl_1.Api.ResPQ)) {
throw new errors_1.SecurityError(`Step 1 answer was ${resPQ}`);
}
if (resPQ.nonce.neq(nonce)) {
throw new errors_1.SecurityError("Step 1 invalid nonce from server");
}
const pq = (0, Helpers_1.readBigIntFromBuffer)(resPQ.pq, false, true);
log.debug("Finished authKey generation step 1");
// Step 2 sending: DH Exchange
const { p, q } = Factorizator_1.Factorizator.factorize(pq);
const pBuffer = (0, Helpers_1.getByteArray)(p);
const qBuffer = (0, Helpers_1.getByteArray)(q);
bytes = (0, Helpers_1.generateRandomBytes)(32);
const newNonce = (0, Helpers_1.readBigIntFromBuffer)(bytes, true, true);
const pqInnerData = new tl_1.Api.PQInnerData({
pq: (0, Helpers_1.getByteArray)(pq),
p: pBuffer,
q: qBuffer,
nonce: resPQ.nonce,
serverNonce: resPQ.serverNonce,
newNonce,
}).getBytes();
if (pqInnerData.length > 144) {
throw new errors_1.SecurityError("Step 1 invalid nonce from server");
}
let targetFingerprint;
let targetKey;
for (const fingerprint of resPQ.serverPublicKeyFingerprints) {
targetKey = RSA_1._serverKeys.get(fingerprint.toString());
if (targetKey !== undefined) {
targetFingerprint = fingerprint;
break;
}
}
if (targetFingerprint === undefined || targetKey === undefined) {
throw new errors_1.SecurityError("Step 2 could not find a valid key for fingerprints");
}
// Value should be padded to be made 192 exactly
const padding = (0, Helpers_1.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 = (0, Helpers_1.generateRandomBytes)(32);
const shaDigestKeyWithData = await (0, Helpers_1.sha256)(Buffer.concat([tempKey, dataWithPadding]));
const dataWithHash = Buffer.concat([
dataPadReversed,
shaDigestKeyWithData,
]);
const ige = new IGE_1.IGE(tempKey, Buffer.alloc(32));
const aesEncrypted = ige.encryptIge(dataWithHash);
const tempKeyXor = (0, Helpers_1.bufferXor)(tempKey, await (0, Helpers_1.sha256)(aesEncrypted));
const keyAesEncrypted = Buffer.concat([tempKeyXor, aesEncrypted]);
const keyAesEncryptedInt = (0, Helpers_1.readBigIntFromBuffer)(keyAesEncrypted, false, false);
if (keyAesEncryptedInt.greaterOrEquals(targetKey.n)) {
log.debug("Aes key greater than RSA. retrying");
continue;
}
const encryptedDataBuffer = (0, Helpers_1.modExp)(keyAesEncryptedInt, (0, big_integer_1.default)(targetKey.e), targetKey.n);
encryptedData = (0, Helpers_1.readBufferFromBigInt)(encryptedDataBuffer, 256, false, false);
break;
}
if (encryptedData === undefined) {
throw new errors_1.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 tl_1.Api.ReqDHParams({
nonce: resPQ.nonce,
serverNonce: resPQ.serverNonce,
p: pBuffer,
q: qBuffer,
publicKeyFingerprint: targetFingerprint,
encryptedData,
}));
if (!(serverDhParams instanceof tl_1.Api.ServerDHParamsOk ||
serverDhParams instanceof tl_1.Api.ServerDHParamsFail)) {
throw new Error(`Step 2.1 answer was ${serverDhParams}`);
}
if (serverDhParams.nonce.neq(resPQ.nonce)) {
throw new errors_1.SecurityError("Step 2 invalid nonce from server");
}
if (serverDhParams.serverNonce.neq(resPQ.serverNonce)) {
throw new errors_1.SecurityError("Step 2 invalid server nonce from server");
}
if (serverDhParams instanceof tl_1.Api.ServerDHParamsFail) {
const sh = await (0, Helpers_1.sha1)((0, Helpers_1.toSignedLittleBuffer)(newNonce, 32).slice(4, 20));
const nnh = (0, Helpers_1.readBigIntFromBuffer)(sh, true, true);
if (serverDhParams.newNonceHash.neq(nnh)) {
throw new errors_1.SecurityError("Step 2 invalid DH fail nonce from server");
}
}
if (!(serverDhParams instanceof tl_1.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 (0, Helpers_1.generateKeyDataFromNonce)(resPQ.serverNonce, newNonce);
if (serverDhParams.encryptedAnswer.length % 16 !== 0) {
// See PR#453
throw new errors_1.SecurityError("Step 3 AES block size mismatch");
}
const ige = new IGE_1.IGE(key, iv);
const plainTextAnswer = ige.decryptIge(serverDhParams.encryptedAnswer);
const reader = new extensions_1.BinaryReader(plainTextAnswer);
reader.read(20); // hash sum
const serverDhInner = reader.tgReadObject();
if (!(serverDhInner instanceof tl_1.Api.ServerDHInnerData)) {
throw new Error(`Step 3 answer was ${serverDhInner}`);
}
if (serverDhInner.nonce.neq(resPQ.nonce)) {
throw new errors_1.SecurityError("Step 3 Invalid nonce in encrypted answer");
}
if (serverDhInner.serverNonce.neq(resPQ.serverNonce)) {
throw new errors_1.SecurityError("Step 3 Invalid server nonce in encrypted answer");
}
const dhPrime = (0, Helpers_1.readBigIntFromBuffer)(serverDhInner.dhPrime, false, false);
const ga = (0, Helpers_1.readBigIntFromBuffer)(serverDhInner.gA, false, false);
const timeOffset = serverDhInner.serverTime - Math.floor(new Date().getTime() / 1000);
const b = (0, Helpers_1.readBigIntFromBuffer)((0, Helpers_1.generateRandomBytes)(256), false, false);
const gb = (0, Helpers_1.modExp)((0, big_integer_1.default)(serverDhInner.g), b, dhPrime);
const gab = (0, Helpers_1.modExp)(ga, b, dhPrime);
// Prepare client DH Inner Data
const clientDhInner = new tl_1.Api.ClientDHInnerData({
nonce: resPQ.nonce,
serverNonce: resPQ.serverNonce,
retryId: big_integer_1.default.zero,
gB: (0, Helpers_1.getByteArray)(gb, false),
}).getBytes();
const clientDdhInnerHashed = Buffer.concat([
await (0, Helpers_1.sha1)(clientDhInner),
clientDhInner,
]);
// Encryption
const clientDhEncrypted = ige.encryptIge(clientDdhInnerHashed);
const dhGen = await sender.send(new tl_1.Api.SetClientDHParams({
nonce: resPQ.nonce,
serverNonce: resPQ.serverNonce,
encryptedData: clientDhEncrypted,
}));
const nonceTypes = [tl_1.Api.DhGenOk, tl_1.Api.DhGenRetry, tl_1.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 errors_1.SecurityError(`Step 3 invalid ${name} nonce from server`);
}
if (dhGen.serverNonce.neq(resPQ.serverNonce)) {
throw new errors_1.SecurityError(`Step 3 invalid ${name} server nonce from server`);
}
const authKey = new AuthKey_1.AuthKey();
await authKey.setKey((0, Helpers_1.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 errors_1.SecurityError("Step 3 invalid new nonce hash");
}
if (!(dhGen instanceof tl_1.Api.DhGenOk)) {
throw new Error(`Step 3.2 answer was ${dhGen}`);
}
log.debug("Finished authKey generation step 3");
return { authKey, timeOffset };
}
exports.doAuthentication = doAuthentication;