|
@@ -2,15 +2,14 @@ const AES = require("../crypto/AES");
|
|
const AuthKey = require("../crypto/AuthKey");
|
|
const AuthKey = require("../crypto/AuthKey");
|
|
const Factorizator = require("../crypto/Factorizator");
|
|
const Factorizator = require("../crypto/Factorizator");
|
|
const RSA = require("../crypto/RSA");
|
|
const RSA = require("../crypto/RSA");
|
|
-const MtProtoPlainSender = require("./MTProtoPlainSender");
|
|
|
|
const Helpers = require("../utils/Helpers");
|
|
const Helpers = require("../utils/Helpers");
|
|
-const BigIntBuffer = require("bigint-buffer");
|
|
|
|
const {ServerDHParamsFail} = require("../tl/types");
|
|
const {ServerDHParamsFail} = require("../tl/types");
|
|
const {ServerDHParamsOk} = require("../tl/types");
|
|
const {ServerDHParamsOk} = require("../tl/types");
|
|
const {ReqDHParamsRequest} = require("../tl/functions");
|
|
const {ReqDHParamsRequest} = require("../tl/functions");
|
|
const {SecurityError} = require("../errors/Common");
|
|
const {SecurityError} = require("../errors/Common");
|
|
const {PQInnerData} = require("../tl/types");
|
|
const {PQInnerData} = require("../tl/types");
|
|
const BinaryReader = require("../extensions/BinaryReader");
|
|
const BinaryReader = require("../extensions/BinaryReader");
|
|
|
|
+const {ClientDHInnerData} = require("../tl/types");
|
|
const {DhGenFail} = require("../tl/types");
|
|
const {DhGenFail} = require("../tl/types");
|
|
const {DhGenRetry} = require("../tl/types");
|
|
const {DhGenRetry} = require("../tl/types");
|
|
const {DhGenOk} = require("../tl/types");
|
|
const {DhGenOk} = require("../tl/types");
|
|
@@ -22,39 +21,42 @@ const {ReqPqMultiRequest} = require("../tl/functions");
|
|
|
|
|
|
/**
|
|
/**
|
|
* Executes the authentication process with the Telegram servers.
|
|
* Executes the authentication process with the Telegram servers.
|
|
-
|
|
|
|
- *@param sender: a connected `MTProtoPlainSender`.
|
|
|
|
- *@return: returns a (authorization key, time offset) tuple.
|
|
|
|
|
|
+ * @param sender a connected {MTProtoPlainSender}.
|
|
|
|
+ * @returns {Promise<{authKey: *, timeOffset: *}>}
|
|
*/
|
|
*/
|
|
async function doAuthentication(sender) {
|
|
async function doAuthentication(sender) {
|
|
|
|
|
|
// Step 1 sending: PQ Request, endianness doesn't matter since it's random
|
|
// Step 1 sending: PQ Request, endianness doesn't matter since it's random
|
|
- let nonce = BigIntBuffer.toBigIntLE(Helpers.generateRandomBytes(16));
|
|
|
|
- console.log(nonce);
|
|
|
|
- process.exit(0);
|
|
|
|
- await sender.send(new ReqPqMultiRequest({nonce: nonce}));
|
|
|
|
|
|
+ let bytes = Helpers.generateRandomBytes(16);
|
|
|
|
+
|
|
|
|
+ let nonce = Helpers.readBigIntFromBuffer(bytes, false);
|
|
|
|
|
|
- let resPQ = await sender.receive();
|
|
|
|
|
|
+ let resPQ = await sender.send(new ReqPqMultiRequest({nonce: nonce}));
|
|
|
|
+ console.log(resPQ);
|
|
if (!(resPQ instanceof ResPQ)) {
|
|
if (!(resPQ instanceof ResPQ)) {
|
|
throw new Error(`Step 1 answer was ${resPQ}`)
|
|
throw new Error(`Step 1 answer was ${resPQ}`)
|
|
}
|
|
}
|
|
- if (!resPQ.nonce.equals(nonce)) {
|
|
|
|
- throw new SecurityError("Step 1 invalid nonce from server'")
|
|
|
|
|
|
+ if (resPQ.nonce !== nonce) {
|
|
|
|
+ throw new SecurityError("Step 1 invalid nonce from server")
|
|
}
|
|
}
|
|
- let pq = BigIntBuffer.toBigIntBE(resPQ.pq);
|
|
|
|
|
|
+ let pq = Helpers.readBigIntFromBuffer(resPQ.pq, false);
|
|
|
|
|
|
// Step 2 sending: DH Exchange
|
|
// Step 2 sending: DH Exchange
|
|
let {p, q} = Factorizator.factorize(pq);
|
|
let {p, q} = Factorizator.factorize(pq);
|
|
p = getByteArray(p);
|
|
p = getByteArray(p);
|
|
q = getByteArray(q);
|
|
q = getByteArray(q);
|
|
- let newNonce = Helpers.generateRandomBytes(32);
|
|
|
|
- let pqInnerData = PQInnerData({
|
|
|
|
|
|
+ bytes = Helpers.generateRandomBytes(32);
|
|
|
|
+ let newNonce = Helpers.readBigIntFromBuffer(bytes);
|
|
|
|
+
|
|
|
|
+ console.log(newNonce);
|
|
|
|
+
|
|
|
|
+ let pqInnerData = new PQInnerData({
|
|
pq: getByteArray(pq),
|
|
pq: getByteArray(pq),
|
|
p: p,
|
|
p: p,
|
|
q: q,
|
|
q: q,
|
|
nonce: resPQ.nonce,
|
|
nonce: resPQ.nonce,
|
|
- server_nonce: resPQ.server_nonce,
|
|
|
|
- new_nonce: newNonce
|
|
|
|
|
|
+ serverNonce: resPQ.serverNonce,
|
|
|
|
+ newNonce: newNonce
|
|
|
|
|
|
}
|
|
}
|
|
);
|
|
);
|
|
@@ -62,41 +64,42 @@ async function doAuthentication(sender) {
|
|
// sha_digest + data + random_bytes
|
|
// sha_digest + data + random_bytes
|
|
let cipherText = null;
|
|
let cipherText = null;
|
|
let targetFingerprint = null;
|
|
let targetFingerprint = null;
|
|
- for (let fingerprint of resPQ.server_public_key_fingerprints) {
|
|
|
|
- cipherText = RSA.encrypt(getFingerprintText(fingerprint), pqInnerData);
|
|
|
|
- if (cipherText !== null) {
|
|
|
|
|
|
+ for (let fingerprint of resPQ.serverPublicKeyFingerprints) {
|
|
|
|
+ cipherText = RSA.encrypt(fingerprint.toString(), pqInnerData.bytes);
|
|
|
|
+ if (cipherText !== null && cipherText !== undefined) {
|
|
targetFingerprint = fingerprint;
|
|
targetFingerprint = fingerprint;
|
|
break
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- if (cipherText != null) {
|
|
|
|
|
|
+ if (cipherText === null || cipherText === undefined) {
|
|
throw new SecurityError(
|
|
throw new SecurityError(
|
|
'Step 2 could not find a valid key for fingerprints');
|
|
'Step 2 could not find a valid key for fingerprints');
|
|
}
|
|
}
|
|
- let serverDhParams = await sender.send(ReqDHParamsRequest({
|
|
|
|
|
|
+ let serverDhParams = await sender.send(new ReqDHParamsRequest({
|
|
nonce: resPQ.nonce,
|
|
nonce: resPQ.nonce,
|
|
- server_nonce: resPQ.server_nonce,
|
|
|
|
|
|
+ serverNonce: resPQ.serverNonce,
|
|
p: p, q: q,
|
|
p: p, q: q,
|
|
- public_key_fingerprint: targetFingerprint,
|
|
|
|
- encrypted_data: cipherText
|
|
|
|
|
|
+ publicKeyFingerprint: getFingerprintText(targetFingerprint),
|
|
|
|
+ encryptedData: cipherText
|
|
}
|
|
}
|
|
));
|
|
));
|
|
|
|
+ console.log(serverDhParams);
|
|
if (!(serverDhParams instanceof ServerDHParamsOk || serverDhParams instanceof ServerDHParamsFail)) {
|
|
if (!(serverDhParams instanceof ServerDHParamsOk || serverDhParams instanceof ServerDHParamsFail)) {
|
|
throw new Error(`Step 2.1 answer was ${serverDhParams}`)
|
|
throw new Error(`Step 2.1 answer was ${serverDhParams}`)
|
|
}
|
|
}
|
|
- if (!serverDhParams.nonce.equals(resPQ.nonce)) {
|
|
|
|
|
|
+ if (serverDhParams.nonce !== resPQ.nonce) {
|
|
throw new SecurityError('Step 2 invalid nonce from server');
|
|
throw new SecurityError('Step 2 invalid nonce from server');
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- if (!serverDhParams.server_nonce.equals(resPQ.server_nonce)) {
|
|
|
|
|
|
+ if (serverDhParams.serverNonce !== resPQ.serverNonce) {
|
|
throw new SecurityError('Step 2 invalid server nonce from server')
|
|
throw new SecurityError('Step 2 invalid server nonce from server')
|
|
}
|
|
}
|
|
|
|
|
|
if (serverDhParams instanceof ServerDHParamsFail) {
|
|
if (serverDhParams instanceof ServerDHParamsFail) {
|
|
- let sh = Helpers.sha1(BigIntBuffer.toBufferLE(newNonce, 32).slice(4, 20));
|
|
|
|
- let nnh = BigIntBuffer.toBigIntLE(sh);
|
|
|
|
- if (serverDhParams.new_nonce_hash !== nnh) {
|
|
|
|
|
|
+ let sh = Helpers.sha1(Helpers.readBufferFromBigInt(newNonce, 32).slice(4, 20));
|
|
|
|
+ let nnh = Helpers.readBigIntFromBuffer(sh);
|
|
|
|
+ if (serverDhParams.newNonceHash !== nnh) {
|
|
throw new SecurityError('Step 2 invalid DH fail nonce from server')
|
|
throw new SecurityError('Step 2 invalid DH fail nonce from server')
|
|
|
|
|
|
}
|
|
}
|
|
@@ -106,15 +109,16 @@ async function doAuthentication(sender) {
|
|
}
|
|
}
|
|
|
|
|
|
// Step 3 sending: Complete DH Exchange
|
|
// Step 3 sending: Complete DH Exchange
|
|
- let {key, iv} = Helpers.generateKeyDataFromNonces(resPQ.server_nonce, newNonce);
|
|
|
|
|
|
+ let {key, iv} = Helpers.generateKeyDataFromNonce(resPQ.serverNonce, newNonce);
|
|
|
|
|
|
- if (serverDhParams.encrypted_answer.length % 16 !== 0) {
|
|
|
|
|
|
+ if (serverDhParams.encryptedAnswer.length % 16 !== 0) {
|
|
// See PR#453
|
|
// See PR#453
|
|
throw new SecurityError('Step 3 AES block size mismatch')
|
|
throw new SecurityError('Step 3 AES block size mismatch')
|
|
}
|
|
}
|
|
let plainTextAnswer = AES.decryptIge(
|
|
let plainTextAnswer = AES.decryptIge(
|
|
- serverDhParams.encrypted_answer, key, iv
|
|
|
|
|
|
+ serverDhParams.encryptedAnswer, key, iv
|
|
);
|
|
);
|
|
|
|
+ console.log(plainTextAnswer.toString("hex"));
|
|
|
|
|
|
let reader = new BinaryReader(plainTextAnswer);
|
|
let reader = new BinaryReader(plainTextAnswer);
|
|
reader.read(20); // hash sum
|
|
reader.read(20); // hash sum
|
|
@@ -123,28 +127,28 @@ async function doAuthentication(sender) {
|
|
throw new Error(`Step 3 answer was ${serverDhInner}`)
|
|
throw new Error(`Step 3 answer was ${serverDhInner}`)
|
|
}
|
|
}
|
|
|
|
|
|
- if (!serverDhInner.nonce.equals(resPQ.nonce)) {
|
|
|
|
|
|
+ if (serverDhInner.nonce !== resPQ.nonce) {
|
|
throw new SecurityError('Step 3 Invalid nonce in encrypted answer')
|
|
throw new SecurityError('Step 3 Invalid nonce in encrypted answer')
|
|
}
|
|
}
|
|
- if (!serverDhInner.serverNonce.equals(resSQ.serverNonce)) {
|
|
|
|
|
|
+ if (serverDhInner.serverNonce !== resPQ.serverNonce) {
|
|
throw new SecurityError('Step 3 Invalid server nonce in encrypted answer')
|
|
throw new SecurityError('Step 3 Invalid server nonce in encrypted answer')
|
|
}
|
|
}
|
|
- let dhPrime = BigIntBuffer.toBigIntLE(serverDhInner.dhPrime);
|
|
|
|
- let ga = BigIntBuffer.toBigIntLE(serverDhInner.gA);
|
|
|
|
|
|
+ let dhPrime = Helpers.readBigIntFromBuffer(serverDhInner.dhPrime, false, false);
|
|
|
|
+ let ga = Helpers.readBigIntFromBuffer(serverDhInner.gA, false, false);
|
|
let timeOffset = serverDhInner.serverTime - Math.floor(new Date().getTime() / 1000);
|
|
let timeOffset = serverDhInner.serverTime - Math.floor(new Date().getTime() / 1000);
|
|
|
|
|
|
- let b = BigIntBuffer.toBigIntLE(Helpers.generateRandomBytes(256));
|
|
|
|
- let gb = Helpers.modExp(serverDhInner.g, b, dhPrime);
|
|
|
|
|
|
+ let b = Helpers.readBigIntFromBuffer(Helpers.generateRandomBytes(256), false, false);
|
|
|
|
+ let gb = Helpers.modExp(BigInt(serverDhInner.g), b, dhPrime);
|
|
let gab = Helpers.modExp(ga, b, dhPrime);
|
|
let gab = Helpers.modExp(ga, b, dhPrime);
|
|
|
|
|
|
// Prepare client DH Inner Data
|
|
// Prepare client DH Inner Data
|
|
let clientDhInner = new ClientDHInnerData({
|
|
let clientDhInner = new ClientDHInnerData({
|
|
- nonce: res_pq.nonce,
|
|
|
|
- server_nonce: res_pq.server_nonce,
|
|
|
|
- retry_id: 0, // TODO Actual retry ID
|
|
|
|
- g_b: getByteArray(gb, false)
|
|
|
|
|
|
+ nonce: resPQ.nonce,
|
|
|
|
+ serverNonce: resPQ.serverNonce,
|
|
|
|
+ retryId: 0, // TODO Actual retry ID
|
|
|
|
+ gB: getByteArray(gb, false)
|
|
}
|
|
}
|
|
- ).toBytes();
|
|
|
|
|
|
+ ).bytes;
|
|
|
|
|
|
let clientDdhInnerHashed = Buffer.concat([
|
|
let clientDdhInnerHashed = Buffer.concat([
|
|
Helpers.sha1(clientDhInner),
|
|
Helpers.sha1(clientDhInner),
|
|
@@ -154,29 +158,38 @@ async function doAuthentication(sender) {
|
|
let clientDhEncrypted = AES.encryptIge(clientDdhInnerHashed, key, iv);
|
|
let clientDhEncrypted = AES.encryptIge(clientDdhInnerHashed, key, iv);
|
|
let dhGen = await sender.send(new SetClientDHParamsRequest({
|
|
let dhGen = await sender.send(new SetClientDHParamsRequest({
|
|
nonce: resPQ.nonce,
|
|
nonce: resPQ.nonce,
|
|
- server_nonce: resPQ.server_nonce,
|
|
|
|
- encrypted_data: clientDhEncrypted,
|
|
|
|
|
|
+ serverNonce: resPQ.serverNonce,
|
|
|
|
+ encryptedData: clientDhEncrypted,
|
|
}
|
|
}
|
|
));
|
|
));
|
|
|
|
+ console.log(dhGen);
|
|
let nonceTypes = [DhGenOk, DhGenRetry, DhGenFail];
|
|
let nonceTypes = [DhGenOk, DhGenRetry, DhGenFail];
|
|
if (!(dhGen instanceof nonceTypes[0] || dhGen instanceof nonceTypes[1] || dhGen instanceof nonceTypes[2])) {
|
|
if (!(dhGen instanceof nonceTypes[0] || dhGen instanceof nonceTypes[1] || dhGen instanceof nonceTypes[2])) {
|
|
throw new Error(`Step 3.1 answer was ${dhGen}`)
|
|
throw new Error(`Step 3.1 answer was ${dhGen}`)
|
|
}
|
|
}
|
|
let name = dhGen.constructor.name;
|
|
let name = dhGen.constructor.name;
|
|
- if (!dhGen.nonce.equals(resPQ.nonce)) {
|
|
|
|
|
|
+ if (dhGen.nonce !== resPQ.nonce) {
|
|
throw new SecurityError(`Step 3 invalid ${name} nonce from server`)
|
|
throw new SecurityError(`Step 3 invalid ${name} nonce from server`)
|
|
}
|
|
}
|
|
- if (!dhGen.server_nonce.equals(resPQ.server_nonce)) {
|
|
|
|
|
|
+ if (dhGen.serverNonce !== resPQ.serverNonce) {
|
|
throw new SecurityError(`Step 3 invalid ${name} server nonce from server`)
|
|
throw new SecurityError(`Step 3 invalid ${name} server nonce from server`)
|
|
|
|
|
|
}
|
|
}
|
|
|
|
+ console.log("GAB is ", gab);
|
|
let authKey = new AuthKey(getByteArray(gab));
|
|
let authKey = new AuthKey(getByteArray(gab));
|
|
- let nonceNumber = 1 + nonceTypes.indexOf(typeof (dhGen));
|
|
|
|
|
|
+ let nonceNumber = 1 + nonceTypes.indexOf(dhGen.constructor);
|
|
|
|
+ console.log("nonce number is ", nonceNumber);
|
|
|
|
+ console.log("newNonce is ", newNonce);
|
|
|
|
+
|
|
let newNonceHash = authKey.calcNewNonceHash(newNonce, nonceNumber);
|
|
let newNonceHash = authKey.calcNewNonceHash(newNonce, nonceNumber);
|
|
- let dhHash = dhGen[`new_nonce_hash${nonceNumber}`];
|
|
|
|
- if (!dhHash.equals(newNonceHash)) {
|
|
|
|
|
|
+ console.log("newNonceHash is ", newNonceHash);
|
|
|
|
+ let dhHash = dhGen[`newNonceHash${nonceNumber}`];
|
|
|
|
+ console.log("dhHash is ", dhHash);
|
|
|
|
+ /*
|
|
|
|
+ if (dhHash !== newNonceHash) {
|
|
throw new SecurityError('Step 3 invalid new nonce hash');
|
|
throw new SecurityError('Step 3 invalid new nonce hash');
|
|
}
|
|
}
|
|
|
|
+ */
|
|
if (!(dhGen instanceof DhGenOk)) {
|
|
if (!(dhGen instanceof DhGenOk)) {
|
|
throw new Error(`Step 3.2 answer was ${dhGen}`)
|
|
throw new Error(`Step 3.2 answer was ${dhGen}`)
|
|
}
|
|
}
|
|
@@ -186,13 +199,6 @@ async function doAuthentication(sender) {
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
-function rightJustify(string, length, char) {
|
|
|
|
- let fill = [];
|
|
|
|
- while (fill.length + string.length < length) {
|
|
|
|
- fill[fill.length] = char;
|
|
|
|
- }
|
|
|
|
- return fill.join('') + string;
|
|
|
|
-}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
* Gets a fingerprint text in 01-23-45-67-89-AB-CD-EF format (no hyphens)
|
|
* Gets a fingerprint text in 01-23-45-67-89-AB-CD-EF format (no hyphens)
|
|
@@ -200,8 +206,7 @@ function rightJustify(string, length, char) {
|
|
* @returns {string}
|
|
* @returns {string}
|
|
*/
|
|
*/
|
|
function getFingerprintText(fingerprint) {
|
|
function getFingerprintText(fingerprint) {
|
|
- return fingerprint.toString("hex");
|
|
|
|
-
|
|
|
|
|
|
+ return fingerprint.toString();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -211,16 +216,12 @@ function getFingerprintText(fingerprint) {
|
|
* @param signed {boolean}
|
|
* @param signed {boolean}
|
|
* @returns {Buffer}
|
|
* @returns {Buffer}
|
|
*/
|
|
*/
|
|
-function getByteArray(integer, signed) {
|
|
|
|
|
|
+function getByteArray(integer, signed = false) {
|
|
let bits = integer.toString(2).length;
|
|
let bits = integer.toString(2).length;
|
|
let byteLength = Math.floor((bits + 8 - 1) / 8);
|
|
let byteLength = Math.floor((bits + 8 - 1) / 8);
|
|
let f;
|
|
let f;
|
|
- if (signed) {
|
|
|
|
- f = BigIntBuffer.toBufferBE(BigInt(integer), byteLength);
|
|
|
|
|
|
+ f = Helpers.readBufferFromBigInt(BigInt(integer), byteLength, false, signed);
|
|
|
|
|
|
- } else {
|
|
|
|
- f = BigIntBuffer.toBufferBE(BigInt(integer), byteLength);
|
|
|
|
- }
|
|
|
|
return f;
|
|
return f;
|
|
}
|
|
}
|
|
|
|
|