소스 검색

Update Authenticator

painor 5 년 전
부모
커밋
d9fcd203a2
4개의 변경된 파일213개의 추가작업 그리고 279개의 파일을 삭제
  1. 209 0
      gramjs/network/Authenticator.js
  2. 0 275
      gramjs/network/connection/Authenticator.js
  3. 1 1
      gramjs/tl/TelegramClient.js
  4. 3 3
      gramjs/utils/Helpers.js

+ 209 - 0
gramjs/network/Authenticator.js

@@ -0,0 +1,209 @@
+const AES = require("../crypto/AES");
+const AuthKey = require("../crypto/AuthKey");
+const Factorizator = require("../crypto/Factorizator");
+const RSA = require("../crypto/RSA");
+const MtProtoPlainSender = require("./MTProtoPlainSender");
+const Helpers = require("../utils/Helpers");
+const BigIntBuffer = require("bigint-buffer");
+
+/**
+ * Executes the authentication process with the Telegram servers.
+
+ *@param sender: a connected `MTProtoPlainSender`.
+ *@return: returns a (authorization key, time offset) tuple.
+ */
+async function doAuthentication(sender) {
+
+    // Step 1 sending: PQ Request, endianness doesn't matter since it's random
+    let nonce = Helpers.generateRandomBytes(16);
+    let resPQ = await sender.send(ReqPqMultiRequest(nonce));
+    if (!(resPQ instanceof ResPQ)) {
+        throw new Error(`Step 1 answer was ${resPQ}`)
+    }
+    if (!resPQ.nonce.equals(nonce)) {
+        throw new SecurityError("Step 1 invalid nonce from server'")
+    }
+    pq = BigIntBuffer.toBigIntBE(resPQ.pq);
+
+    // Step 2 sending: DH Exchange
+    let {p, q} = Factorizator.factorize(pq);
+    p = getByteArray(p);
+    q = getByteArray(q);
+    let newNonce = Helpers.generateRandomBytes(32);
+    let pqInnerData = PQInnerData({
+            pq: rsa.get_byte_array(pq),
+            p: p,
+            q: q,
+            nonce: resPQ.nonce,
+            server_nonce: resPQ.server_nonce,
+            new_nonce: newNonce
+
+        }
+    );
+
+    // sha_digest + data + random_bytes
+    let cipherText = null;
+    let targetFingerprint = null;
+    for (let fingerprint of resPQ.serverPublicKeyFingerprints) {
+        cipherText = RSA.encrypt(getFingerprintText(fingerprint), pqInnerData);
+        if (cipherText !== null) {
+            targetFingerprint = fingerprint;
+            break
+        }
+    }
+    if (cipherText != null) {
+        throw new SecurityError(
+            'Step 2 could not find a valid key for fingerprints');
+    }
+    let serverDhParams = await sender.send(ReqDHParamsRequest({
+            nonce: resPQ.nonce,
+            server_nonce: resPQ.server_nonce,
+            p: p, q: q,
+            public_key_fingerprint: targetFingerprint,
+            encrypted_data: cipherText
+        }
+    ));
+    if (!(serverDhParams instanceof ServerDHParamsOk || serverDhParams instanceof ServerDHParamsFail)) {
+        throw new Error(`Step 2.1 answer was ${serverDhParams}`)
+    }
+    if (!serverDhParams.nonce.equals(resPQ.nonce)) {
+        throw new SecurityError('Step 2 invalid nonce from server');
+
+    }
+
+    if (!serverDhParams.server_nonce.equals(resPQ.server_nonce)) {
+        throw new SecurityError('Step 2 invalid server nonce from server')
+    }
+
+    if (serverDhParams instanceof ServerDHParamsFail) {
+        let sh = Helpers.sha1(BigIntBuffer.toBufferLE(newNonce, 32).slice(4, 20));
+        let nnh = BigIntBuffer.toBigIntLE(sh);
+        if (serverDhParams.newNonceHash !== nnh) {
+            throw new SecurityError('Step 2 invalid DH fail nonce from server')
+
+        }
+    }
+    if (!(serverDhParams instanceof ServerDHParamsOk)) {
+        console.log(`Step 2.2 answer was ${serverDhParams}`);
+    }
+
+    // Step 3 sending: Complete DH Exchange
+    let {key, iv} = Helpers.generateKeyDataFromNonces(resPQ.server_nonce, newNonce);
+
+    if (serverDhParams.encryptedAnswer.length % 16 !== 0) {
+        // See PR#453
+        throw new SecurityError('Step 3 AES block size mismatch')
+    }
+    let plainTextAnswer = AES.decryptIge(
+        serverDhParams.encryptedAnswer, key, iv
+    );
+
+    let reader = new BinaryReader(plainTextAnswer);
+    reader.read(20); // hash sum
+    let serverDhInner = reader.tgReadObject();
+    if (!(serverDhInner instanceof ServerDHInnerData)) {
+        throw new Error(`Step 3 answer was ${serverDhInner}`)
+    }
+
+    if (!serverDhInner.nonce.equals(resPQ.nonce)) {
+        throw new SecurityError('Step 3 Invalid nonce in encrypted answer')
+    }
+    if (!serverDhInner.serverNonce.equals(resSQ.serverNonce)) {
+        throw new SecurityError('Step 3 Invalid server nonce in encrypted answer')
+    }
+    let dhPrime = BigIntBuffer.toBigIntLE(serverDhInner.dhPrime);
+    let ga = BigIntBuffer.toBigIntLE(serverDhInner.ga);
+    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 gab = Helpers.modExp(ga, b, dhPrime);
+
+    // Prepare client DH Inner Data
+    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)
+        }
+    ).toBytes();
+
+    let clientDdhInnerHashed = Buffer.concat([
+        Helpers.sha1(clientDhInner),
+        clientDhInner
+    ]);
+    // Encryption
+    let clientDhEncrypted = AES.encryptIge(clientDdhInnerHashed, key, iv);
+    let dhGen = await sender.send(new SetClientDHParamsRequest({
+            nonce: resPQ.nonce,
+            server_nonce: resPQ.server_nonce,
+            encrypted_data: clientDhEncrypted,
+        }
+    ));
+    let nonceTypes = [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}`)
+    }
+    let name = dhGen.constructor.name;
+    if (!dhGen.nonce.equals(resPQ.nonce)) {
+        throw new SecurityError(`Step 3 invalid ${name} nonce from server`)
+    }
+    if (!dhGen.server_nonce.equals(resPQ.server_nonce)) {
+        throw new SecurityError(`Step 3 invalid ${name} server nonce from server`)
+
+    }
+    let authKey = new AuthKey(getByteArray(gab));
+    let nonceNumber = 1 + nonceTypes.indexOf(typeof (dhGen));
+    let newNonceHash = authKey.calcNewNonceHash(newNonce, nonceNumber);
+    let dhHash = dhGen[`new_nonce_hash${nonceNumber}`];
+    if (!dhHash.equals(newNonceHash)) {
+        throw new SecurityError('Step 3 invalid new nonce hash');
+    }
+    if (!(dhGen instanceof DhGenOk)) {
+        throw new Error(`Step 3.2 answer was ${dhGen}`)
+    }
+
+    return {authKey, timeOffset};
+
+
+}
+
+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)
+ * @param fingerprint {Array}
+ * @returns {string}
+ */
+function getFingerprintText(fingerprint) {
+    return fingerprint.toString("hex");
+
+}
+
+
+/**
+ * Gets the arbitrary-length byte array corresponding to the given integer
+ * @param integer {number,BigInt}
+ * @param signed {boolean}
+ * @returns {Buffer}
+ */
+function getByteArray(integer, signed) {
+    let bits = integer.toString(2).length;
+    let byteLength = Math.floor((bits + 8 - 1) / 8);
+    let f;
+    if (signed) {
+        f = BigIntBuffer.toBufferBE(BigInt(integer), byteLength);
+
+    } else {
+        f = BigIntBuffer.toBufferBE(BigInt(integer), byteLength);
+    }
+    return f;
+}
+
+module.exports = doAuthentication;

+ 0 - 275
gramjs/network/connection/Authenticator.js

@@ -1,275 +0,0 @@
-const AES = require("../../crypto/AES");
-const AuthKey = require("../../crypto/AuthKey");
-const Factorizator = require("../../crypto/Factorizator");
-const RSA = require("../../crypto/RSA");
-const MtProtoPlainSender = require("../MTProtoPlainSender");
-const Helpers = require("../../utils/Helpers");
-const BigIntBuffer = require("bigint-buffer");
-
-/**
- *
- * @param transport
- * @returns {Promise<{authKey: AuthKey, timeOffset: BigInt}>}
- */
-async function doAuthentication(transport) {
-    let sender = new MtProtoPlainSender(transport);
-
-    // Step 1 sending: PQ request
-    let nonce = Helpers.generateRandomBytes(16);
-    let buffer = Buffer.alloc(4);
-    buffer.writeUInt32LE(0x60469778, 0);
-    buffer = Buffer.concat([buffer, nonce]);
-    await sender.send(buffer);
-    let offset = 0;
-    // Step 1 response: PQ request
-    let pq;
-    let serverNonce;
-    let fingerprints = Array();
-    buffer = await sender.receive();
-
-    let responseCode = buffer.readUInt32LE(offset);
-    offset += 4;
-    if (responseCode !== 0x05162463) {
-        throw Error("invalid response code");
-    }
-
-    let nonceFromServer = buffer.slice(offset, offset + 16);
-    offset += 16;
-    if (!nonce.equals(nonceFromServer)) {
-        throw Error("Invalid nonce from server");
-    }
-    serverNonce = buffer.slice(offset, offset + 16);
-    offset += 16;
-    let res = Helpers.tgReadByte(buffer, offset);
-    let pqBytes = res.data;
-
-    let newOffset = res.offset;
-    pq = BigIntBuffer.toBigIntBE(pqBytes);
-
-    let vectorId = buffer.readInt32LE(newOffset);
-    newOffset += 4;
-    if (vectorId !== 0x1cb5c415) {
-        throw Error("vector error");
-    }
-    let fingerprints_count = buffer.readInt32LE(newOffset);
-    newOffset += 4;
-    for (let i = 0; i < fingerprints_count; i++) {
-        fingerprints.push(buffer.slice(newOffset, newOffset + 8));
-        newOffset += 8;
-    }
-    // Step 2 sending: DH Exchange
-    let newNonce = Helpers.generateRandomBytes(32);
-    let {p, q} = Factorizator.factorize(pq);
-    let min = p < q ? p : q;
-    let max = p > q ? p : q;
-
-    let tempBuffer = Buffer.alloc(4);
-    tempBuffer.writeUInt32LE(0x83c95aec, 0);
-    let pqInnerData = Buffer.concat([
-        tempBuffer,
-        Helpers.tgWriteBytes(getByteArray(pq, false)),
-        Helpers.tgWriteBytes(getByteArray(min, false)),
-        Helpers.tgWriteBytes(getByteArray(max, false)),
-        nonce,
-        serverNonce,
-        newNonce,
-    ]);
-    let cipherText, targetFingerprint;
-    for (let fingerprint of fingerprints) {
-        cipherText = RSA.encrypt(getFingerprintText(fingerprint), pqInnerData);
-        if (cipherText !== undefined) {
-            targetFingerprint = fingerprint;
-            break;
-        }
-    }
-    if (cipherText === undefined) {
-        throw Error("Could not find a valid key for fingerprints");
-    }
-
-    tempBuffer = Buffer.alloc(4);
-    tempBuffer.writeUInt32LE(0xd712e4be, 0);
-
-
-    let reqDhParams = Buffer.concat([
-        tempBuffer,
-        nonce,
-        serverNonce,
-        Helpers.tgWriteBytes(getByteArray(min, false)),
-        Helpers.tgWriteBytes(getByteArray(max, false)),
-        targetFingerprint,
-        Helpers.tgWriteBytes(cipherText)
-    ]);
-
-    await sender.send(reqDhParams);
-    // Step 2 response: DH Exchange
-    newOffset = 0;
-    let reader = await sender.receive();
-    responseCode = reader.readUInt32LE(newOffset);
-    newOffset += 4;
-    if (responseCode === 0x79cb045d) {
-        throw Error("Server DH params fail: TODO ");
-    }
-    if (responseCode !== 0xd0e8075c) {
-        throw Error("Invalid response code: TODO ");
-    }
-    nonceFromServer = reader.slice(newOffset, newOffset + 16);
-    newOffset += 16;
-    if (!nonceFromServer.equals(nonce)) {
-        throw Error("Invalid nonce from server");
-    }
-    let serverNonceFromServer = reader.slice(newOffset, newOffset + 16);
-
-    if (!serverNonceFromServer.equals(serverNonce)) {
-        throw Error("Invalid server nonce from server");
-    }
-
-    newOffset += 16;
-
-    let encryptedAnswer = Helpers.tgReadByte(reader, newOffset).data;
-    // Step 3 sending: Complete DH Exchange
-
-    res = Helpers.generateKeyDataFromNonces(serverNonce, newNonce);
-    let key = res.keyBuffer;
-    let iv = res.ivBuffer;
-    let plainTextAnswer = AES.decryptIge(encryptedAnswer, key, iv);
-    let g, dhPrime, ga, timeOffset;
-    let dhInnerData = plainTextAnswer;
-    newOffset = 20;
-    let code = dhInnerData.readUInt32LE(newOffset);
-    if (code !== 0xb5890dba) {
-        throw Error("Invalid DH Inner Data code:")
-    }
-    newOffset += 4;
-    let nonceFromServer1 = dhInnerData.slice(newOffset, newOffset + 16);
-    if (!nonceFromServer1.equals(nonceFromServer)) {
-        throw Error("Invalid nonce in encrypted answer");
-    }
-    newOffset += 16;
-    let serverNonceFromServer1 = dhInnerData.slice(newOffset, newOffset + 16);
-    if (!serverNonceFromServer1.equals(serverNonce)) {
-        throw Error("Invalid server nonce in encrypted answer");
-    }
-    newOffset += 16;
-    g = BigInt(dhInnerData.readInt32LE(newOffset));
-    newOffset += 4;
-
-    let temp = Helpers.tgReadByte(dhInnerData, newOffset);
-    newOffset = temp.offset;
-
-    dhPrime = BigIntBuffer.toBigIntBE(temp.data);
-    temp = Helpers.tgReadByte(dhInnerData, newOffset);
-
-    newOffset = temp.offset;
-    ga = BigIntBuffer.toBigIntBE(temp.data);
-    let serverTime = dhInnerData.readInt32LE(newOffset);
-    timeOffset = serverTime - Math.floor(new Date().getTime() / 1000);
-    let b = BigIntBuffer.toBigIntBE(Helpers.generateRandomBytes(2048));
-    let gb = Helpers.modExp(g, b, dhPrime);
-    let gab = Helpers.modExp(ga, b, dhPrime);
-
-    // Prepare client DH Inner Data
-
-    tempBuffer = Buffer.alloc(4);
-    tempBuffer.writeUInt32LE(0x6643b654, 0);
-    let clientDHInnerData = Buffer.concat([
-        tempBuffer,
-        nonce,
-        serverNonce,
-        Buffer.alloc(8).fill(0),
-        Helpers.tgWriteBytes(getByteArray(gb, false)),
-    ]);
-    let clientDhInnerData = Buffer.concat([
-        Helpers.sha1(clientDHInnerData),
-        clientDHInnerData
-    ]);
-
-    // Encryption
-    let clientDhInnerDataEncrypted = AES.encryptIge(clientDhInnerData, key, iv);
-
-    // Prepare Set client DH params
-    tempBuffer = Buffer.alloc(4);
-    tempBuffer.writeUInt32LE(0xf5045f1f, 0);
-    let setClientDhParams = Buffer.concat([
-        tempBuffer,
-        nonce,
-        serverNonce,
-        Helpers.tgWriteBytes(clientDhInnerDataEncrypted),
-    ]);
-    await sender.send(setClientDhParams);
-
-    // Step 3 response: Complete DH Exchange
-    reader = await sender.receive();
-    newOffset = 0;
-    code = reader.readUInt32LE(newOffset);
-    newOffset += 4;
-    if (code === 0x3bcbf734) { //  DH Gen OK
-        nonceFromServer = reader.slice(newOffset, newOffset + 16);
-        newOffset += 16;
-        if (!nonceFromServer.equals(nonce)) {
-            throw Error("Invalid nonce from server");
-        }
-        serverNonceFromServer = reader.slice(newOffset, newOffset + 16);
-        newOffset += 16;
-        if (!serverNonceFromServer.equals(serverNonce)) {
-            throw Error("Invalid server nonce from server");
-        }
-        let newNonceHash1 = reader.slice(newOffset, newOffset + 16);
-        let authKey = new AuthKey(getByteArray(gab, false));
-        let newNonceHashCalculated = authKey.calcNewNonceHash(newNonce, 1);
-        if (!newNonceHash1.equals(newNonceHashCalculated)) {
-            throw Error("Invalid new nonce hash");
-        }
-        timeOffset = BigInt(timeOffset);
-        return {authKey, timeOffset};
-    } else if (code === 0x46dc1fb9) {
-        throw Error("dh_gen_retry");
-
-    } else if (code === 0x46dc1fb9) {
-        throw Error("dh_gen_fail");
-
-    } else {
-        throw Error("DH Gen unknown");
-
-    }
-
-}
-
-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)
- * @param fingerprint {Array}
- * @returns {string}
- */
-function getFingerprintText(fingerprint) {
-    return fingerprint.toString("hex");
-
-}
-
-
-/**
- * Gets the arbitrary-length byte array corresponding to the given integer
- * @param integer {number,BigInt}
- * @param signed {boolean}
- * @returns {Buffer}
- */
-function getByteArray(integer, signed) {
-    let bits = integer.toString(2).length;
-    let byteLength = Math.floor((bits + 8 - 1) / 8);
-    let f;
-    if (signed) {
-        f = BigIntBuffer.toBufferBE(BigInt(integer), byteLength);
-
-    } else {
-        f = BigIntBuffer.toBufferBE(BigInt(integer), byteLength);
-    }
-    return f;
-}
-
-module.exports = doAuthentication;

+ 1 - 1
gramjs/tl/TelegramClient.js

@@ -1,5 +1,5 @@
 const Session = require("./Session");
-const doAuthentication = require("../network/connection/Authenticator");
+const doAuthentication = require("../network/Authenticator");
 const MtProtoSender = require("../network/mtprotoSender");
 const MTProtoRequest = require("../tl/MTProtoRequest");
 const TcpTransport = require("../network/TCPTransport");

+ 3 - 3
gramjs/utils/Helpers.js

@@ -112,7 +112,7 @@ class Helpers {
      * Generates the key data corresponding to the given nonces
      * @param serverNonce
      * @param newNonce
-     * @returns {{ivBuffer: Buffer, keyBuffer: Buffer}}
+     * @returns {{key: Buffer, iv: Buffer}}
      */
     static generateKeyDataFromNonces(serverNonce, newNonce) {
         let hash1 = Helpers.sha1(Buffer.concat([newNonce, serverNonce]));
@@ -120,7 +120,7 @@ class Helpers {
         let hash3 = Helpers.sha1(Buffer.concat([newNonce, newNonce]));
         let keyBuffer = Buffer.concat([hash1, hash2.slice(0, 12)]);
         let ivBuffer = Buffer.concat([hash2.slice(12, 20), hash3, newNonce.slice(0, 4)]);
-        return {keyBuffer: keyBuffer, ivBuffer: ivBuffer}
+        return {key: keyBuffer, iv: ivBuffer}
     }
 
     /**
@@ -180,7 +180,7 @@ class Helpers {
             } else if (constructorId === 0xbc799737) {
                 return false
             }
-            throw Error("type not found "+ constructorId);
+            throw Error("type not found " + constructorId);
         }
         return undefined;
     }