Authenticator.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. /**
  2. * Executes the authentication process with the Telegram servers.
  3. * @param sender a connected {MTProtoPlainSender}.
  4. * @param log
  5. * @returns {Promise<{authKey: *, timeOffset: *}>}
  6. */
  7. import {Api} from "../tl";
  8. import {SecurityError} from "../errors";
  9. import {Factorizator} from "../crypto/Factorizator";
  10. import {IGE} from "../crypto/IGE";
  11. import {BinaryReader} from "../extensions";
  12. import {AuthKey} from "../crypto/AuthKey";
  13. import {helpers} from "../";
  14. import {encrypt} from "../crypto/RSA";
  15. import bigInt from 'big-integer';
  16. import type {MTProtoPlainSender} from "./MTProtoPlainSender";
  17. export async function doAuthentication(sender: MTProtoPlainSender, log: any) {
  18. // Step 1 sending: PQ Request, endianness doesn't matter since it's random
  19. let bytes = helpers.generateRandomBytes(16);
  20. const nonce = helpers.readBigIntFromBuffer(bytes, false, true);
  21. const resPQ = await sender.send(new Api.ReqPqMulti({nonce: nonce}));
  22. log.debug('Starting authKey generation step 1');
  23. if (!(resPQ instanceof Api.ResPQ)) {
  24. throw new Error(`Step 1 answer was ${resPQ}`)
  25. }
  26. if (resPQ.nonce.neq(nonce)) {
  27. throw new SecurityError('Step 1 invalid nonce from server')
  28. }
  29. const pq = helpers.readBigIntFromBuffer(resPQ.pq, false, true);
  30. log.debug('Finished authKey generation step 1');
  31. log.debug('Starting authKey generation step 2');
  32. // Step 2 sending: DH Exchange
  33. let {p, q} = Factorizator.factorize(pq);
  34. const pBuffer = helpers.getByteArray(p);
  35. const qBuffer = helpers.getByteArray(q);
  36. bytes = helpers.generateRandomBytes(32);
  37. const newNonce = helpers.readBigIntFromBuffer(bytes, true, true);
  38. const pqInnerData = new Api.PQInnerData({
  39. pq: helpers.getByteArray(pq), // unsigned
  40. p: pBuffer,
  41. q: qBuffer,
  42. nonce: resPQ.nonce,
  43. serverNonce: resPQ.serverNonce,
  44. newNonce: newNonce,
  45. });
  46. // sha_digest + data + random_bytes
  47. let cipherText = undefined;
  48. let targetFingerprint = undefined;
  49. for (const fingerprint of resPQ.serverPublicKeyFingerprints) {
  50. cipherText = await encrypt(fingerprint, pqInnerData.getBytes());
  51. if (cipherText !== undefined) {
  52. targetFingerprint = fingerprint;
  53. break
  54. }
  55. }
  56. if (cipherText === undefined) {
  57. throw new SecurityError('Step 2 could not find a valid key for fingerprints')
  58. }
  59. const serverDhParams = await sender.send(
  60. new Api.ReqDHParams({
  61. nonce: resPQ.nonce,
  62. serverNonce: resPQ.serverNonce,
  63. p: pBuffer,
  64. q: qBuffer,
  65. publicKeyFingerprint: targetFingerprint,
  66. encryptedData: cipherText,
  67. }),
  68. );
  69. if (!(serverDhParams instanceof Api.ServerDHParamsOk || serverDhParams instanceof Api.ServerDHParamsFail)) {
  70. throw new Error(`Step 2.1 answer was ${serverDhParams}`)
  71. }
  72. if (serverDhParams.nonce.neq(resPQ.nonce)) {
  73. throw new SecurityError('Step 2 invalid nonce from server')
  74. }
  75. if (serverDhParams.serverNonce.neq(resPQ.serverNonce)) {
  76. throw new SecurityError('Step 2 invalid server nonce from server')
  77. }
  78. if (serverDhParams instanceof Api.ServerDHParamsFail) {
  79. const sh = await helpers.sha1(helpers.toSignedLittleBuffer(newNonce, 32).slice(4, 20));
  80. const nnh = helpers.readBigIntFromBuffer(sh, true, true);
  81. if (serverDhParams.newNonceHash.neq(nnh)) {
  82. throw new SecurityError('Step 2 invalid DH fail nonce from server')
  83. }
  84. }
  85. if (!(serverDhParams instanceof Api.ServerDHParamsOk)) {
  86. throw new Error(`Step 2.2 answer was ${serverDhParams}`)
  87. }
  88. log.debug('Finished authKey generation step 2');
  89. log.debug('Starting authKey generation step 3');
  90. // Step 3 sending: Complete DH Exchange
  91. const {key, iv} = await helpers.generateKeyDataFromNonce(resPQ.serverNonce, newNonce);
  92. if (serverDhParams.encryptedAnswer.length % 16 !== 0) {
  93. // See PR#453
  94. throw new SecurityError('Step 3 AES block size mismatch')
  95. }
  96. const ige = new IGE(key, iv);
  97. const plainTextAnswer = ige.decryptIge(serverDhParams.encryptedAnswer);
  98. const reader = new BinaryReader(plainTextAnswer);
  99. reader.read(20); // hash sum
  100. const serverDhInner = reader.tgReadObject();
  101. if (!(serverDhInner instanceof Api.ServerDHInnerData)) {
  102. throw new Error(`Step 3 answer was ${serverDhInner}`)
  103. }
  104. if (serverDhInner.nonce.neq(resPQ.nonce)) {
  105. throw new SecurityError('Step 3 Invalid nonce in encrypted answer')
  106. }
  107. if (serverDhInner.serverNonce.neq(resPQ.serverNonce)) {
  108. throw new SecurityError('Step 3 Invalid server nonce in encrypted answer')
  109. }
  110. const dhPrime = helpers.readBigIntFromBuffer(serverDhInner.dhPrime, false, false);
  111. const ga = helpers.readBigIntFromBuffer(serverDhInner.gA, false, false);
  112. const timeOffset = serverDhInner.serverTime - Math.floor(new Date().getTime() / 1000);
  113. const b = helpers.readBigIntFromBuffer(helpers.generateRandomBytes(256), false, false);
  114. const gb = helpers.modExp(bigInt(serverDhInner.g), b, dhPrime);
  115. const gab = helpers.modExp(ga, b, dhPrime);
  116. // Prepare client DH Inner Data
  117. const clientDhInner = new Api.ClientDHInnerData({
  118. nonce: resPQ.nonce,
  119. serverNonce: resPQ.serverNonce,
  120. retryId: bigInt.zero, // TODO Actual retry ID
  121. gB: helpers.getByteArray(gb, false),
  122. }).getBytes();
  123. const clientDdhInnerHashed = Buffer.concat([await helpers.sha1(clientDhInner), clientDhInner]);
  124. // Encryption
  125. const clientDhEncrypted = ige.encryptIge(clientDdhInnerHashed);
  126. const dhGen = await sender.send(
  127. new Api.SetClientDHParams({
  128. nonce: resPQ.nonce,
  129. serverNonce: resPQ.serverNonce,
  130. encryptedData: clientDhEncrypted,
  131. }),
  132. );
  133. const nonceTypes = [Api.DhGenOk, Api.DhGenRetry, Api.DhGenFail];
  134. // TS being weird again.
  135. const nonceTypesString = ['DhGenOk', 'DhGenRetry', 'DhGenFail'];
  136. if (!(dhGen instanceof nonceTypes[0] || dhGen instanceof nonceTypes[1] || dhGen instanceof nonceTypes[2])) {
  137. throw new Error(`Step 3.1 answer was ${dhGen}`)
  138. }
  139. const {name} = dhGen.constructor;
  140. if (dhGen.nonce.neq(resPQ.nonce)) {
  141. throw new SecurityError(`Step 3 invalid ${name} nonce from server`)
  142. }
  143. if (dhGen.serverNonce.neq(resPQ.serverNonce)) {
  144. throw new SecurityError(`Step 3 invalid ${name} server nonce from server`)
  145. }
  146. const authKey = new AuthKey();
  147. await authKey.setKey(helpers.getByteArray(gab));
  148. const nonceNumber = 1 + nonceTypesString.indexOf(dhGen.className);
  149. const newNonceHash = await authKey.calcNewNonceHash(newNonce, nonceNumber);
  150. // @ts-ignore
  151. const dhHash = dhGen[`newNonceHash${nonceNumber}`];
  152. if (dhHash.neq(newNonceHash)) {
  153. throw new SecurityError('Step 3 invalid new nonce hash')
  154. }
  155. if (!(dhGen instanceof Api.DhGenOk)) {
  156. throw new Error(`Step 3.2 answer was ${dhGen}`)
  157. }
  158. log.debug('Finished authKey generation step 3');
  159. return {authKey, timeOffset}
  160. }