UNPKG

telegram-mtproto

Version:
456 lines (363 loc) 17.3 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = Auth; var _fluture = require('fluture'); var _error = require('../../error'); var _configProvider = require('../../config-provider'); var _configProvider2 = _interopRequireDefault(_configProvider); var _action = require('../../state/action'); var _state = require('../../state'); var _tl = require('../../tl'); var _secureRandom = require('../secure-random'); var _secureRandom2 = _interopRequireDefault(_secureRandom); var _timeManager = require('../time-manager'); var _bin = require('../../bin'); var _leemon = require('../../vendor/leemon'); var _newtype = require('../../newtype.h'); require('./index.h'); var _fetchObject = require('./fetch-object'); var _primeHex = require('./prime-hex'); var _primeHex2 = _interopRequireDefault(_primeHex); var _sendPlainReq = require('./send-plain-req'); var _sendPlainReq2 = _interopRequireDefault(_sendPlainReq); var _mtprotoLogger = require('mtproto-logger'); var _mtprotoLogger2 = _interopRequireDefault(_mtprotoLogger); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } var log = _mtprotoLogger2.default`auth`; function Auth(uid, dc) { var savedReq = _configProvider2.default.authRequest.get(uid, dc); if (savedReq) { return savedReq; } var runThread = getUrl(uid, dc).chain(url => authFuture(uid, dc, url)).map(res => Object.assign({}, res, { uid })).chain(res => (0, _fluture.encaseP)((() => { var _ref = _asyncToGenerator(function* (res) { var setter = _configProvider2.default.storageAdapter.set; yield setter.salt(uid, dc, res.serverSalt); yield setter.authKey(uid, dc, res.authKey); yield setter.authID(uid, dc, res.authKeyID); return res; }); return function (_x) { return _ref.apply(this, arguments); }; })(), res)).map(res => ((0, _state.dispatch)(_action.MAIN.AUTH.RESOLVE(res), uid), res)); var future = (0, _fluture.cache)(runThread); _configProvider2.default.authRequest.set(uid, dc, future); return future; } var failureHandler = (uid, dc) => err => { log`authChain, error`(err); _configProvider2.default.authRequest.remove(uid, dc); return err; }; var modPowF = (0, _fluture.encaseP)(_configProvider2.default.common.Crypto.modPow); var modPowPartial = (b, dhPrime) => x => modPowF({ x, y: b, m: dhPrime }); /*::.mapRej(cryptoErr)*/ var factorize = (0, _fluture.encaseP)(_configProvider2.default.common.Crypto.factorize); var normalizeResPQ = res => Object.assign({ serverNonce: res.server_nonce, pq: res.pq, fingerprints: res.server_public_key_fingerprints }, res); var makePqBlock = uid => ctx => { var { serverNonce, fingerprints, pq, it } = ctx; log`PQ factorization done`(it); log`Got ResPQ`((0, _bin.bytesToHex)(serverNonce), (0, _bin.bytesToHex)(pq), fingerprints); try { var publicKey = _configProvider2.default.publicKeys.select(uid, fingerprints); return (0, _fluture.of)(Object.assign({}, ctx, { publicKey })); } catch (err) { console.trace('select'); return (0, _fluture.reject)(err); } }; function getUrl(uid, dc) { var url = _configProvider2.default.dcMap(uid, dc); if (typeof url !== 'string') return (0, _fluture.reject)(new _error.DcUrlError(dc, url)); log`mtpAuth, dc, url`(dc, url); return (0, _fluture.of)(url); } function authFuture(uid, dc, url) { var nonce = newNonce(); return (0, _fluture.of)((0, _fetchObject.writeReqPQ)(uid, nonce)).chain((0, _sendPlainReq2.default)(uid, url)).map(_fetchObject.fetchResPQ).chain(assertResPQ(nonce)).map(normalizeResPQ).chain(factorizePQInner).chain(makePqBlock(uid)).map(oneMoreNonce).chain(mtpSendReqDh(uid, url)).chain(authKeyFuture(uid, url)).map(data => Object.assign({}, data, { dc })).mapRej(failureHandler(uid, dc)); } function newNonce() { var nonce = (0, _bin.generateNonce)(); log`Send req_pq`((0, _bin.bytesToHex)(nonce)); return nonce; } function oneMoreNonce(auth) { var newNonce = (0, _secureRandom2.default)(new Array(32)); log`afterReqDH`('Send req_DH_params'); return Object.assign({}, auth, { newNonce }); } var factorizePQInner = ctx => factorize({ bytes: ctx.pq }).map(([p, q, it]) => Object.assign({}, ctx, { p, q, it })); /*::.mapRej(cryptoErr)*/ var assertResPQ = nonce => response => { if (response._ !== 'resPQ') { return (0, _fluture.reject)(ERR.sendPQ.response(response._)); /*::.mapRej(sendPqFail)*/ } if (!(0, _bin.bytesCmp)(nonce, response.nonce)) { return (0, _fluture.reject)(ERR.sendPQ.nonce()); /*::.mapRej(sendPqFail)*/ } return (0, _fluture.of)(response); /*::.mapRej(sendPqFail)*/ }; var authKeyFuture = (uid, url) => auth => { var { g, gA, nonce, serverNonce, dhPrime } = auth; var gBytes = (0, _bin.bytesFromHex)(g.toString(16)); var b = (0, _secureRandom2.default)(new Array(256)); var modPowFuture = modPowPartial(b, dhPrime); return modPowFuture(gBytes).map(gB => (0, _fetchObject.writeInnerDH)(uid, auth, gB)).map(prepareData(uid, url, auth)).chain((0, _sendPlainReq2.default)(uid, url)).map(_fetchObject.fetchDhParam).chain(assertDhParams(nonce, serverNonce)).both(modPowFuture(gA)).map(authKeysGen).chain(dhChoose(uid, url, auth)).map(result => Object.assign({}, result, auth)); }; var prepareData = (uid, url, auth) => dataWithHash => { var { g, serverNonce, nonce, tmpAesKey, tmpAesIv } = auth; var encryptedData = (0, _bin.aesEncryptSync)(dataWithHash, tmpAesKey, tmpAesIv); var request = new _tl.Serialization({ mtproto: true }, uid); log`onGb`('Send set_client_DH_params'); request.storeMethod('set_client_DH_params', { nonce, server_nonce: serverNonce, encrypted_data: encryptedData }); return request.writer.getBuffer(); }; function authKeysGen([response, authKey]) { var authKeyHash = (0, _bin.sha1BytesSync)(authKey); log`Got Set_client_DH_params_answer`(response._); return [response, { key: authKey, aux: authKeyHash.slice(0, 8), id: authKeyHash.slice(-8) }]; } var dhChoose = (uid, url, auth) => ([response, keys]) => /*::joinChain(*/dhSwitch(uid, url, auth, response, keys); /*::)*/ function dhSwitch(uid, url, auth, response, keys) { switch (response._) { case 'dh_gen_ok': return (/*::joinChain(*/dhGenOk(response, keys, auth) ); /*::)*/ case 'dh_gen_retry': return (/*::joinChain(*/dhGenRetry(uid, url, response, keys, auth) ); /*::)*/ case 'dh_gen_fail': return (/*::joinChain(*/dhGenFail(response, keys, auth) ); /*::)*/ default: return (0, _fluture.reject)(new Error('Unknown case')); /*::.mapRej(dhAnswerFail)*/ } } function dhGenOk(response, { key, id, aux }, { newNonce, serverNonce }) { var newNonceHash1 = (0, _bin.sha1BytesSync)(newNonce.concat([1], aux)).slice(-16); if (!(0, _bin.bytesCmp)(newNonceHash1, response.new_nonce_hash1)) { var err = (0, _fluture.reject)(ERR.dh.nonce.hash1()); /*::.mapRej(dhAnswerFail)*/ return err; } var serverSalt = (0, _bin.bytesXor)(newNonce.slice(0, 8), serverNonce.slice(0, 8)); // console.log('Auth successfull!', authKeyID, authKey, serverSalt) var result = (0, _fluture.of)({ authKeyID: /*:: toCryptoKey(*/id /*::)*/ , authKey: /*:: toCryptoKey(*/key /*::)*/ , //eslint-disable-next-line serverSalt: /*:: toCryptoKey(*/serverSalt /*::)*/ }); return result; } function dhGenRetry(uid, url, response, { aux }, auth) { var { newNonce } = auth; var newNonceHash2 = (0, _bin.sha1BytesSync)(newNonce.concat([2], aux)).slice(-16); if (!(0, _bin.bytesCmp)(newNonceHash2, response.new_nonce_hash2)) { var err = (0, _fluture.reject)(ERR.dh.nonce.hash2()); /*::.mapRej(dhAnswerFail)*/ return err; } return authKeyFuture(uid, url)(auth); } function dhGenFail(response, { aux }, { newNonce }) { var newNonceHash3 = (0, _bin.sha1BytesSync)(newNonce.concat([3], aux)).slice(-16); if (!(0, _bin.bytesCmp)(newNonceHash3, response.new_nonce_hash3)) { var err = (0, _fluture.reject)(ERR.dh.nonce.hash3()); /*::.mapRej(dhAnswerFail)*/ return err; } var result = (0, _fluture.reject)(ERR.dh.paramsFail()); /*::.mapRej(dhAnswerFail)*/ return result; } var mtpSendReqDh = (uid, url) => auth => (0, _fluture.of)((0, _fetchObject.writeReqDH)(uid, auth)).chain((0, _sendPlainReq2.default)(uid, url)).map(_fetchObject.fetchServerDh).chain(assertDhResponse(auth)).chain(decryptServerDH(uid, auth)); var decryptServerDH = (uid, auth) => ctx => decryptServerDHPlain(uid, makeAesKeys(auth), ctx); function makeAesKeys(auth) { var { serverNonce, newNonce } = auth; var tmpAesKey = aesKey(serverNonce, newNonce); var tmpAesIv = aesIv(serverNonce, newNonce); return Object.assign({}, auth, { tmpAesKey, tmpAesIv }); } function decryptServerDHPlain(uid, auth, response) { var { encrypted_answer: encryptedAnswer } = response; var { serverNonce, nonce, tmpAesKey, tmpAesIv } = auth; var answerWithHash = (0, _bin.aesDecryptSync)(encryptedAnswer, tmpAesKey, tmpAesIv); var hash = answerWithHash.slice(0, 20); var answerWithPadding = answerWithHash.slice(20); var deserializer = new _tl.Deserialization((0, _bin.bytesToArrayBuffer)(answerWithPadding), { mtproto: true }, uid); return (0, _fluture.of)(deserializer).map(_fetchObject.fetchDHInner).chain(assertDecryption(nonce, serverNonce)).chain(mtpVerifyDhParams(deserializer, hash, answerWithPadding)).map(afterServerDhDecrypt(uid, auth)); } var afterServerDhDecrypt = (uid, auth) => response => { log`DecryptServerDhDataAnswer`('Done decrypting answer'); var { g, dh_prime: dhPrime, g_a: gA, server_time: serverTime } = response; var localTime = (0, _timeManager.tsNow)(); (0, _timeManager.applyServerTime)(uid, serverTime, localTime); return Object.assign({}, auth, { g, gA, dhPrime, retry: 0 }); }; function aesKey(serverNonce, newNonce) { var arr1 = [...newNonce, ...serverNonce]; var arr2 = [...serverNonce, ...newNonce]; var key1 = (0, _bin.sha1BytesSync)(arr1); var key2 = (0, _bin.sha1BytesSync)(arr2).slice(0, 12); return key1.concat(key2); } function aesIv(serverNonce, newNonce) { var arr1 = [...serverNonce, ...newNonce]; var arr2 = [...newNonce, ...newNonce]; var arr3 = newNonce.slice(0, 4); var key1 = (0, _bin.sha1BytesSync)(arr1); var key2 = (0, _bin.sha1BytesSync)(arr2); var res = key1.slice(12).concat(key2, arr3); return res; } var minSize = Math.ceil(64 / _leemon.bpe) + 1; var leemonTwoPow = (() => { //Dirty cheat to count 2^(2048 - 64) var arr = Array(496) //This number contains 496 zeroes in hex .fill('0'); arr.unshift('1'); var hex = arr.join(''); var res = (0, _leemon.str2bigInt)(hex, 16, minSize); return res; })(); var innerLog = log`VerifyDhParams`; var mtpVerifyDhParams = (deserializer, hash, answerWithPadding) => (response /*:: : Fluture<Server_DH_inner_data, *> */) => { var { g, dh_prime: dhPrime, g_a: gA } = response; innerLog('begin'); var dhPrimeHex = (0, _bin.bytesToHex)(dhPrime); if (g !== 3 || dhPrimeHex !== _primeHex2.default) // The verified value is from https://core.telegram.org/mtproto/security_guidelines return (0, _fluture.reject)(ERR.verify.unknownDhPrime()); /*::.mapRej(verifyFail)*/ innerLog('dhPrime cmp OK'); var dhPrimeLeemon = (0, _leemon.str2bigInt)(dhPrimeHex, 16, minSize); var gALeemon = (0, _leemon.str2bigInt)((0, _bin.bytesToHex)(gA), 16, minSize); var dhDec = (0, _leemon.dup)(dhPrimeLeemon); (0, _leemon.sub_)(dhDec, _leemon.one); var case1 = !(0, _leemon.greater)(gALeemon, _leemon.one); var case2 = !(0, _leemon.greater)(dhDec, gALeemon); if (case1) return (0, _fluture.reject)(ERR.verify.case1()); /*::.mapRej(verifyFail)*/ if (case2) return (0, _fluture.reject)(ERR.verify.case2()); /*::.mapRej(verifyFail)*/ var case3 = !!(0, _leemon.greater)(leemonTwoPow, gALeemon); var dhSubPow = (0, _leemon.dup)(dhPrimeLeemon); (0, _leemon.sub)(dhSubPow, leemonTwoPow); var case4 = !(0, _leemon.greater)(dhSubPow, gALeemon); if (case3) return (0, _fluture.reject)(ERR.verify.case3()); /*::.mapRej(verifyFail)*/ if (case4) return (0, _fluture.reject)(ERR.verify.case4()); /*::.mapRej(verifyFail)*/ innerLog('2^{2048-64} < gA < dhPrime-2^{2048-64} OK'); var offset = deserializer.getOffset(); if (!(0, _bin.bytesCmp)(hash, (0, _bin.sha1BytesSync)(answerWithPadding.slice(0, offset)))) return (0, _fluture.reject)(ERR.decrypt.sha1()); /*::.mapRej(decryptFail)*/ return (0, _fluture.of)(response); /*::.mapRej(verifyFail)*/ }; var assertDecryption = (nonce, serverNonce) => (response /*:: : Fluture<Server_DH_inner_data, *> */) => { if (response._ !== 'server_DH_inner_data') return (0, _fluture.reject)(ERR.decrypt.response()); /*::.mapRej(decryptFail)*/ if (!(0, _bin.bytesCmp)(nonce, response.nonce)) return (0, _fluture.reject)(ERR.decrypt.nonce()); /*::.mapRej(decryptFail)*/ if (!(0, _bin.bytesCmp)(serverNonce, response.server_nonce)) return (0, _fluture.reject)(ERR.decrypt.serverNonce()); /*::.mapRej(decryptFail)*/ return (0, _fluture.of)(response); /*::.mapRej(decryptFail)*/ }; var assertDhParams = (nonce, serverNonce) => (response /*::: Fluture<Set_client_DH_params_answer, *> */) => { if (checkDhGen(response._)) { return (0, _fluture.reject)(ERR.dh.responseInvalid(response._)); /*::.mapRej(assertFail)*/ } else if (!(0, _bin.bytesCmp)(nonce, response.nonce)) { return (0, _fluture.reject)(ERR.dh.nonce.mismatch()); /*::.mapRej(assertFail)*/ } else if (!(0, _bin.bytesCmp)(serverNonce, response.server_nonce)) { return (0, _fluture.reject)(ERR.dh.nonce.server()); /*::.mapRej(assertFail)*/ } return (0, _fluture.of)(response); /*::.mapRej(assertFail)*/ }; function checkDhGen(_) { return _ !== 'dh_gen_ok' && _ !== 'dh_gen_retry' && _ !== 'dh_gen_fail'; } var assertDhResponse = ({ nonce, serverNonce, newNonce }) => (response /*:: : Fluture<*, *> */) => { if (response._ === 'server_DH_params_fail') { var newNonceHash = (0, _bin.sha1BytesSync)(newNonce).slice(-16); if (!(0, _bin.bytesCmp)(newNonceHash, response.new_nonce_hash)) { return (0, _fluture.reject)(ERR.sendDH.hash()); /*::.mapRej(assertFail)*/ } return (0, _fluture.reject)(ERR.sendDH.fail()); /*::.mapRej(assertFail)*/ } if (response._ !== 'server_DH_params_ok') { return (0, _fluture.reject)(ERR.sendDH.invalid(response._)); /*::.mapRej(assertFail)*/ } if (!(0, _bin.bytesCmp)(nonce, response.nonce)) { return (0, _fluture.reject)(ERR.sendDH.nonce()); /*::.mapRej(assertFail)*/ } if (!(0, _bin.bytesCmp)(serverNonce, response.server_nonce)) { return (0, _fluture.reject)(ERR.sendDH.serverNonce()); /*::.mapRej(assertFail)*/ } return (0, _fluture.of)(response); /*::.mapRej(assertFail)*/ }; var ERR = { dh: { paramsFail: () => new Error('[MT] Set_client_DH_params_answer fail'), nonce: { mismatch: () => new Error('[MT] Set_client_DH_params_answer nonce mismatch'), server: () => new Error('[MT] Set_client_DH_params_answer server_nonce mismatch'), hash1: () => new Error('[MT] Set_client_DH_params_answer new_nonce_hash1 mismatch'), hash2: () => new Error('[MT] Set_client_DH_params_answer new_nonce_hash2 mismatch'), hash3: () => new Error('[MT] Set_client_DH_params_answer new_nonce_hash3 mismatch') }, responseInvalid: _ => new Error(`[MT] Set_client_DH_params_answer response invalid: ${_}`) }, verify: { unknownDhPrime: () => new Error('[MT] DH params are not verified: unknown dhPrime'), case1: () => new Error('[MT] DH params are not verified: gA <= 1'), case2: () => new Error('[MT] DH params are not verified: gA >= dhPrime - 1'), case3: () => new Error('[MT] DH params are not verified: gA < 2^{2048-64}'), case4: () => new Error('[MT] DH params are not verified: gA > dhPrime - 2^{2048-64}') }, decrypt: { response: () => new Error(`[MT] server_DH_inner_data response invalid`), nonce: () => new Error('[MT] server_DH_inner_data nonce mismatch'), serverNonce: () => new Error('[MT] server_DH_inner_data serverNonce mismatch'), sha1: () => new Error('[MT] server_DH_inner_data SHA1-hash mismatch') }, sendDH: { invalid: _ => new Error(`[MT] Server_DH_Params response invalid: ${_}`), nonce: () => new Error('[MT] Server_DH_Params nonce mismatch'), serverNonce: () => new Error('[MT] Server_DH_Params server_nonce mismatch'), hash: () => new Error('[MT] server_DH_params_fail new_nonce_hash mismatch'), fail: () => new Error('[MT] server_DH_params_fail') }, sendPQ: { response: _ => new Error(`[MT] resPQ response invalid: ${_}`), nonce: () => new Error('[MT] resPQ nonce mismatch') } }; //# sourceMappingURL=index.js.map