@tgsnake/core
Version:
Pure Telegram MTProto library for nodejs
255 lines (254 loc) • 13.8 kB
JavaScript
;
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;