diff --git a/app/js/lib/bin_utils.js b/app/js/lib/bin_utils.js index 69344b13..36c61559 100644 --- a/app/js/lib/bin_utils.js +++ b/app/js/lib/bin_utils.js @@ -112,11 +112,11 @@ function bytesXor (bytes1, bytes2) { } function bytesToWords (bytes) { - var len = bytes.byteLength || bytes.length, - words = [], i; if (bytes instanceof ArrayBuffer) { bytes = new Uint8Array(bytes); } + var len = bytes.length, + words = [], i; for (i = 0; i < len; i++) { words[i >>> 2] |= bytes[i] << (24 - (i % 4) * 8); } @@ -156,6 +156,24 @@ function bytesToArrayBuffer (b) { return (new Uint8Array(b)).buffer; } +function convertToArrayBuffer(bytes) { + // Be careful with converting subarrays!! + if (bytes instanceof ArrayBuffer) { + return bytes; + } + if (bytes.buffer !== undefined) { + return bytes.buffer; + } + return bytesToArrayBuffer(bytes); +} + +function convertToUint8Array(bytes) { + if (bytes.buffer !== undefined) { + return bytes; + } + return new Uint8Array(bytes); +} + function bytesFromArrayBuffer (buffer) { var len = buffer.byteLength, byteView = new Uint8Array(buffer), @@ -207,25 +225,24 @@ function uintToInt (val) { return val; } -function sha1Hash (bytes) { +function sha1HashSync (bytes) { this.rushaInstance = this.rushaInstance || new Rusha(1024 * 1024); + // console.log(dT(), 'SHA-1 hash start', bytes.byteLength || bytes.length); - var hashBytes = bytesFromArrayBuffer(rushaInstance.rawDigest(bytes).buffer); + var hashBytes = rushaInstance.rawDigest(bytes).buffer; // console.log(dT(), 'SHA-1 hash finish'); return hashBytes; } +function sha1BytesSync (bytes) { + return bytesFromArrayBuffer(sha1HashSync(bytes)); +} -function rsaEncrypt (publicKey, bytes) { - var needPadding = 255 - bytes.length; - if (needPadding > 0) { - var padding = new Array(needPadding); - (new SecureRandom()).nextBytes(padding); - bytes = bytes.concat(padding); - } +function rsaEncrypt (publicKey, bytes) { + bytes = addPadding(bytes, 255); // console.log('RSA encrypt start'); var N = new BigInteger(publicKey.modulus, 16), @@ -233,18 +250,16 @@ function rsaEncrypt (publicKey, bytes) { X = new BigInteger(bytes), encryptedBigInt = X.modPowInt(E, N), encryptedBytes = bytesFromBigInt(encryptedBigInt, 256); - // console.log('RSA encrypt finish'); return encryptedBytes; } -function aesEncrypt (bytes, keyBytes, ivBytes) { +function addPadding(bytes, blockSize) { + blockSize = blockSize || 16; var len = bytes.byteLength || bytes.length; - console.log(dT(), 'AES encrypt start', len/*, bytesToHex(keyBytes), bytesToHex(ivBytes)*/); - - var needPadding = 16 - (len % 16); - if (needPadding > 0 && needPadding < 16) { + var needPadding = blockSize - (len % blockSize); + if (needPadding > 0 && needPadding < blockSize) { var padding = new Array(needPadding); (new SecureRandom()).nextBytes(padding); @@ -255,6 +270,15 @@ function aesEncrypt (bytes, keyBytes, ivBytes) { } } + return bytes; +} + +function aesEncryptSync (bytes, keyBytes, ivBytes) { + var len = bytes.byteLength || bytes.length; + + // console.log(dT(), 'AES encrypt start', len/*, bytesToHex(keyBytes), bytesToHex(ivBytes)*/); + bytes = addPadding(bytes); + var encryptedWords = CryptoJS.AES.encrypt(bytesToWords(bytes), bytesToWords(keyBytes), { iv: bytesToWords(ivBytes), padding: CryptoJS.pad.NoPadding, @@ -262,15 +286,14 @@ function aesEncrypt (bytes, keyBytes, ivBytes) { }).ciphertext; var encryptedBytes = bytesFromWords(encryptedWords); - - console.log(dT(), 'AES encrypt finish'); + // console.log(dT(), 'AES encrypt finish'); return encryptedBytes; } -function aesDecrypt (encryptedBytes, keyBytes, ivBytes) { - // console.log('AES decrypt start', encryptedBytes.length/*, bytesToHex(keyBytes), bytesToHex(ivBytes)*/); +function aesDecryptSync (encryptedBytes, keyBytes, ivBytes) { + // console.log(dT(), 'AES decrypt start', encryptedBytes.length); var decryptedWords = CryptoJS.AES.decrypt({ciphertext: bytesToWords(encryptedBytes)}, bytesToWords(keyBytes), { iv: bytesToWords(ivBytes), padding: CryptoJS.pad.NoPadding, @@ -278,8 +301,7 @@ function aesDecrypt (encryptedBytes, keyBytes, ivBytes) { }); var bytes = bytesFromWords(decryptedWords); - - // console.log('AES decrypt finish'); + // console.log(dT(), 'AES decrypt finish'); return bytes; } diff --git a/app/js/lib/crypto_worker.js b/app/js/lib/crypto_worker.js index 2f42cf55..4a3c9ff0 100644 --- a/app/js/lib/crypto_worker.js +++ b/app/js/lib/crypto_worker.js @@ -29,15 +29,15 @@ onmessage = function (e) { break; case 'sha1-hash': - result = sha1Hash(e.data.bytes); + result = sha1HashSync(e.data.bytes); break; case 'aes-encrypt': - result = aesEncrypt(e.data.bytes, e.data.keyBytes, e.data.ivBytes); + result = aesEncryptSync(e.data.bytes, e.data.keyBytes, e.data.ivBytes); break; case 'aes-decrypt': - result = aesDecrypt(e.data.encryptedBytes, e.data.keyBytes, e.data.ivBytes); + result = aesDecryptSync(e.data.encryptedBytes, e.data.keyBytes, e.data.ivBytes); break; default: diff --git a/app/js/lib/mtproto.js b/app/js/lib/mtproto.js index a4e71e49..10c3fce6 100644 --- a/app/js/lib/mtproto.js +++ b/app/js/lib/mtproto.js @@ -82,7 +82,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) var buffer = RSAPublicKey.getBuffer(); - var fingerprintBytes = sha1Hash(buffer).slice(-8); + var fingerprintBytes = sha1BytesSync(buffer).slice(-8); fingerprintBytes.reverse(); publicKeysParsed[bytesToHex(fingerprintBytes)] = { @@ -169,7 +169,11 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) .factory('MtpAuthorizer', function (MtpDcConfigurator, MtpRsaKeysManager, MtpSecureRandom, MtpTimeManager, CryptoWorker, $http, $q, $timeout) { var chromeMatches = navigator.userAgent.match(/Chrome\/(\d+(\.\d+)?)/), - chromeVersion = chromeMatches && parseFloat(chromeMatches[1]) || false; + chromeVersion = chromeMatches && parseFloat(chromeMatches[1]) || false, + xhrSendBuffer = !('ArrayBufferView' in window) && (!chromeVersion || chromeVersion < 30); + + delete $http.defaults.headers.post['Content-Type']; + delete $http.defaults.headers.common['Accept']; function mtpSendPlainRequest (dcID, requestBuffer) { var requestLength = requestBuffer.byteLength, @@ -190,16 +194,10 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) resultArray.set(headerArray); resultArray.set(requestArray, headerArray.length); - delete $http.defaults.headers.post['Content-Type']; - delete $http.defaults.headers.common['Accept']; - - if (!('ArrayBufferView' in window) && (!chromeVersion || chromeVersion < 30)) { - resultArray = resultArray.buffer; - } - - var requestPromise; + var requestData = xhrSendBuffer ? resultBuffer : resultArray, + requestPromise; try { - requestPromise = $http.post('http://' + MtpDcConfigurator.chooseServer(dcID) + '/apiw1', resultArray, { + requestPromise = $http.post('http://' + MtpDcConfigurator.chooseServer(dcID) + '/apiw1', requestData, { responseType: 'arraybuffer', transformRequest: null }); @@ -305,7 +303,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) new_nonce: auth.newNonce }, 'P_Q_inner_data', 'DECRYPTED_DATA'); - var dataWithHash = sha1Hash(data.getBuffer()).concat(data.getBytes()); + var dataWithHash = sha1BytesSync(data.getBuffer()).concat(data.getBytes()); var request = new TLSerialization({mtproto: true}); request.storeMethod('req_DH_params', { @@ -337,7 +335,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) } if (response._ == 'server_DH_params_fail') { - var newNonceHash = sha1Hash(auth.newNonce).slice(-16) + var newNonceHash = sha1BytesSync(auth.newNonce).slice(-16); if (!bytesCmp (newNonceHash, response.new_nonce_hash)) { deferred.reject(new Error('server_DH_params_fail new_nonce_hash mismatch')); return false; @@ -362,10 +360,10 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) function mtpDecryptServerDhDataAnswer (auth, encryptedAnswer) { auth.localTime = tsNow(); - auth.tmpAesKey = sha1Hash(auth.newNonce.concat(auth.serverNonce)).concat(sha1Hash(auth.serverNonce.concat(auth.newNonce)).slice(0, 12)); - auth.tmpAesIv = sha1Hash(auth.serverNonce.concat(auth.newNonce)).slice(12).concat(sha1Hash([].concat(auth.newNonce, auth.newNonce)), auth.newNonce.slice(0, 4)); + auth.tmpAesKey = sha1BytesSync(auth.newNonce.concat(auth.serverNonce)).concat(sha1BytesSync(auth.serverNonce.concat(auth.newNonce)).slice(0, 12)); + auth.tmpAesIv = sha1BytesSync(auth.serverNonce.concat(auth.newNonce)).slice(12).concat(sha1BytesSync([].concat(auth.newNonce, auth.newNonce)), auth.newNonce.slice(0, 4)); - var answerWithHash = aesDecrypt(encryptedAnswer, auth.tmpAesKey, auth.tmpAesIv); + var answerWithHash = aesDecryptSync(encryptedAnswer, auth.tmpAesKey, auth.tmpAesIv); var hash = answerWithHash.slice(0, 20); var answerWithPadding = answerWithHash.slice(20); @@ -395,7 +393,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) var offset = deserializer.getOffset(); - if (!bytesCmp(hash, sha1Hash(answerWithPadding.slice(0, offset)))) { + if (!bytesCmp(hash, sha1BytesSync(answerWithPadding.slice(0, offset)))) { throw new Error('server_DH_inner_data SHA1-hash mismatch'); } @@ -420,9 +418,9 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) g_b: gB, }, 'Client_DH_Inner_Data'); - var dataWithHash = sha1Hash(data.getBuffer()).concat(data.getBytes()); + var dataWithHash = sha1BytesSync(data.getBuffer()).concat(data.getBytes()); - var encryptedData = aesEncrypt(dataWithHash, auth.tmpAesKey, auth.tmpAesIv); + var encryptedData = aesEncryptSync(dataWithHash, auth.tmpAesKey, auth.tmpAesIv); var request = new TLSerialization({mtproto: true}); request.storeMethod('set_client_DH_params', { @@ -451,14 +449,14 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) } CryptoWorker.modPow(auth.gA, auth.b, auth.dhPrime).then(function (authKey) { - var authKeyHash = sha1Hash(authKey), + var authKeyHash = sha1BytesSync(authKey), authKeyAux = authKeyHash.slice(0, 8), authKeyID = authKeyHash.slice(-8); console.log(dT(), 'Got Set_client_DH_params_answer', response._); switch (response._) { case 'dh_gen_ok': - var newNonceHash1 = sha1Hash(auth.newNonce.concat([1], authKeyAux)).slice(-16); + var newNonceHash1 = sha1BytesSync(auth.newNonce.concat([1], authKeyAux)).slice(-16); if (!bytesCmp(newNonceHash1, response.new_nonce_hash1)) { deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash1 mismatch')); @@ -476,7 +474,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) break; case 'dh_gen_retry': - var newNonceHash2 = sha1Hash(auth.newNonce.concat([2], authKeyAux)).slice(-16); + var newNonceHash2 = sha1BytesSync(auth.newNonce.concat([2], authKeyAux)).slice(-16); if (!bytesCmp(newNonceHash2, response.new_nonce_hash2)) { deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash2 mismatch')); return false; @@ -485,7 +483,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) return mtpSendSetClientDhParams(auth); case 'dh_gen_fail': - var newNonceHash3 = sha1Hash(auth.newNonce.concat([3], authKeyAux)).slice(-16); + var newNonceHash3 = sha1BytesSync(auth.newNonce.concat([3], authKeyAux)).slice(-16); if (!bytesCmp(newNonceHash3, response.new_nonce_hash3)) { deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash3 mismatch')); return false; @@ -553,7 +551,11 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) offline, offlineInited = false, chromeMatches = navigator.userAgent.match(/Chrome\/(\d+(\.\d+)?)/), - chromeVersion = chromeMatches && parseFloat(chromeMatches[1]) || false; + chromeVersion = chromeMatches && parseFloat(chromeMatches[1]) || false, + xhrSendBuffer = !('ArrayBufferView' in window) && (!chromeVersion || chromeVersion < 30); + + delete $http.defaults.headers.post['Content-Type']; + delete $http.defaults.headers.common['Accept']; $rootScope.retryOnline = function () { $(document.body).trigger('online'); @@ -566,7 +568,9 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) this.iii = iii++; this.authKey = authKey; - this.authKeyID = sha1Hash(authKey).slice(-8); + this.authKeyUint8 = convertToUint8Array(authKey); + this.authKeyBuffer = convertToArrayBuffer(authKey); + this.authKeyID = sha1BytesSync(authKey).slice(-8); this.serverSalt = serverSalt; @@ -827,19 +831,47 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) }; MtpNetworker.prototype.getMsgKeyIv = function (msgKey, isOut) { - var authKey = this.authKey, - x = isOut ? 0 : 8; - - var promises = { - sha1a: CryptoWorker.sha1Hash(msgKey.concat(authKey.slice(x, x + 32))), - sha1b: CryptoWorker.sha1Hash(authKey.slice(32 + x, 48 + x).concat(msgKey, authKey.slice(48 + x, 64 + x))), - sha1c: CryptoWorker.sha1Hash(authKey.slice(64 + x, 96 + x).concat(msgKey)), - sha1d: CryptoWorker.sha1Hash(msgKey.concat(authKey.slice(96 + x, 128 + x))) - }; + var authKey = this.authKeyUint8, + x = isOut ? 0 : 8, + sha1aText = new Uint8Array(48), + sha1bText = new Uint8Array(48), + sha1cText = new Uint8Array(48), + sha1dText = new Uint8Array(48), + promises = {}; + + sha1aText.set(msgKey, 0); + sha1aText.set(authKey.subarray(x, x + 32), 16); + promises.sha1a = CryptoWorker.sha1Hash(sha1aText); + + sha1bText.set(authKey.subarray(x + 32, x + 48), 0); + sha1bText.set(msgKey, 16); + sha1bText.set(authKey.subarray(x + 48, x + 64), 32); + promises.sha1b = CryptoWorker.sha1Hash(sha1bText); + + sha1cText.set(authKey.subarray(x + 64, x + 96), 0); + sha1cText.set(msgKey, 32); + promises.sha1c = CryptoWorker.sha1Hash(sha1cText); + + sha1dText.set(msgKey, 0); + sha1dText.set(authKey.subarray(x + 96, x + 128), 16); + promises.sha1d = CryptoWorker.sha1Hash(sha1dText); return $q.all(promises).then(function (result) { - var aesKey = result.sha1a.slice(0, 8).concat(result.sha1b.slice(8, 20), result.sha1c.slice(4, 16)); - var aesIv = result.sha1a.slice(8, 20).concat(result.sha1b.slice(0, 8), result.sha1c.slice(16, 20), result.sha1d.slice(0, 8)); + var aesKey = new Uint8Array(32), + aesIv = new Uint8Array(32); + sha1a = new Uint8Array(result.sha1a), + sha1b = new Uint8Array(result.sha1b), + sha1c = new Uint8Array(result.sha1c), + sha1d = new Uint8Array(result.sha1d); + + aesKey.set(sha1a.subarray(0, 8)); + aesKey.set(sha1b.subarray(8, 20), 8); + aesKey.set(sha1c.subarray(4, 16), 20); + + aesIv.set(sha1a.subarray(8, 20)); + aesIv.set(sha1b.subarray(0, 8), 12); + aesIv.set(sha1c.subarray(16, 20), 20); + aesIv.set(sha1d.subarray(0, 8), 24); return [aesKey, aesIv]; }); @@ -1076,14 +1108,14 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) MtpNetworker.prototype.getEncryptedMessage = function (bytes) { var self = this; - console.log(dT(), 'Start encrypt', bytes.byteLength); + // console.log(dT(), 'Start encrypt', bytes.byteLength); return CryptoWorker.sha1Hash(bytes).then(function (bytesHash) { - console.log(dT(), 'after hash'); - var msgKey = bytesHash.slice(-16); + // console.log(dT(), 'after hash'); + var msgKey = new Uint8Array(bytesHash).subarray(4, 20); return self.getMsgKeyIv(msgKey, true).then(function (keyIv) { - console.log(dT(), 'after msg key iv'); + // console.log(dT(), 'after msg key iv'); return CryptoWorker.aesEncrypt(bytes, keyIv[0], keyIv[1]).then(function (encryptedBytes) { - console.log(dT(), 'Finish encrypt'); + // console.log(dT(), 'Finish encrypt'); return { bytes: encryptedBytes, msgKey: msgKey @@ -1117,18 +1149,12 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) return this.getEncryptedMessage(data.getBuffer()).then(function (encryptedResult) { // console.log(dT(), 'Got encrypted out message'/*, encryptedResult*/); - var request = new TLSerialization({startMaxLength: encryptedResult.bytes.length + 256}); + var request = new TLSerialization({startMaxLength: encryptedResult.bytes.byteLength + 256}); request.storeIntBytes(self.authKeyID, 64, 'auth_key_id'); request.storeIntBytes(encryptedResult.msgKey, 128, 'msg_key'); request.storeRawBytes(encryptedResult.bytes, 'encrypted_data'); - delete $http.defaults.headers.post['Content-Type']; - delete $http.defaults.headers.common['Accept']; - - var resultArray = request.getArray(); - if (!('ArrayBufferView' in window) && (!chromeVersion || chromeVersion < 30)) { - resultArray = resultArray.buffer; - } + var requestData = xhrSendBuffer ? request.getBuffer() : request.getArray(); var requestPromise; try { @@ -1136,7 +1162,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) responseType: 'arraybuffer', transformRequest: null }); - requestPromise = $http.post('http://' + MtpDcConfigurator.chooseServer(self.dcID) + '/apiw1', resultArray, options); + requestPromise = $http.post('http://' + MtpDcConfigurator.chooseServer(self.dcID) + '/apiw1', requestData, options); } catch (e) { requestPromise = $q.reject(e); } @@ -1172,32 +1198,29 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) var deserializer = new TLDeserialization(responseBuffer); - var authKeyID = deserializer.fetchIntBytes(64, 'auth_key_id'); + var authKeyID = deserializer.fetchIntBytes(64, false, 'auth_key_id'); if (!bytesCmp(authKeyID, this.authKeyID)) { throw new Error('Invalid server auth_key_id: ' + bytesToHex(authKeyID)); } - var msgKey = deserializer.fetchIntBytes(128, 'msg_key'); - - var dataLength = responseBuffer.byteLength - deserializer.getOffset(); - var encryptedData = deserializer.fetchRawBytes(dataLength, 'encrypted_data'); + var msgKey = deserializer.fetchIntBytes(128, true, 'msg_key'), + encryptedData = deserializer.fetchRawBytes(responseBuffer.byteLength - deserializer.getOffset(), true, 'encrypted_data'); return this.getDecryptedMessage(msgKey, encryptedData).then(function (dataWithPadding) { - var buffer = bytesToArrayBuffer(dataWithPadding); - - var deserializer = new TLDeserialization(buffer, {mtproto: true}); + var deserializer = new TLDeserialization(dataWithPadding, {mtproto: true}); - var salt = deserializer.fetchIntBytes(64, 'salt'); - var sessionID = deserializer.fetchIntBytes(64, 'session_id'); + var salt = deserializer.fetchIntBytes(64, false, 'salt'); + var sessionID = deserializer.fetchIntBytes(64, false, 'session_id'); var messageID = deserializer.fetchLong('message_id'); var seqNo = deserializer.fetchInt('seq_no'); - var messageBody = deserializer.fetchRawBytes(false, 'message_data'); + var messageBody = deserializer.fetchRawBytes(false, true, 'message_data'); - var offset = deserializer.getOffset(); + var hashData = convertToUint8Array(dataWithPadding).subarray(0, deserializer.getOffset()); - return CryptoWorker.sha1Hash(dataWithPadding.slice(0, offset)).then(function (dataHashed) { - if (!bytesCmp(msgKey, dataHashed.slice(-16))) { + return CryptoWorker.sha1Hash(hashData).then(function (dataHash) { + if (!bytesCmp(msgKey, bytesFromArrayBuffer(dataHash).slice(-16))) { + console.warn(msgKey, bytesFromArrayBuffer(dataHash)); throw new Error('server msgKey mismatch'); } diff --git a/app/js/lib/ng_utils.js b/app/js/lib/ng_utils.js index 1fccf450..76a1a172 100644 --- a/app/js/lib/ng_utils.js +++ b/app/js/lib/ng_utils.js @@ -500,18 +500,16 @@ angular.module('izhukov.utils', []) }; if (navigator.mimeTypes['application/x-pnacl'] !== undefined) { - var listener = $('
').appendTo($('body'))[0]; + var listener = $('
').appendTo($('body'))[0]; listener.addEventListener('load', function (e) { aesNaClEmbed = listener.firstChild; - console.warn('NaCl ready', aesNaClEmbed); + console.log(dT(), 'NaCl ready'); }, true); listener.addEventListener('message', function (e) { - console.log(e.data.result); - console.log(bytesFromArrayBuffer(e.data.result)); finalizeTask(e.data.taskID, e.data.result); }, true); listener.addEventListener('error', function (e) { - console.error(e); + console.error('NaCl error', e); }, true); } @@ -543,57 +541,49 @@ angular.module('izhukov.utils', []) return { sha1Hash: function (bytes) { if (useSha1Crypto) { + // We don't use buffer since typedArray.subarray(...).buffer gives the whole buffer and not sliced one. webCrypto.digest supports typed array var deferred = $q.defer(), - buffer = bytes instanceof ArrayBuffer - ? bytes - : bytesToArrayBuffer(bytes); - - webCrypto.digest({name: 'SHA-1'}, buffer).then(function (digest) { - deferred.resolve(bytesFromArrayBuffer(digest)); + bytesTyped = Array.isArray(bytes) ? convertToUint8Array(bytes) : bytes; + // console.log(dT(), 'Native sha1 start'); + webCrypto.digest({name: 'SHA-1'}, bytesTyped).then(function (digest) { + // console.log(dT(), 'Native sha1 done'); + deferred.resolve(digest); }, function (e) { console.error('Crypto digest error', e); useSha1Crypto = false; - deferred.resolve(sha1Hash(bytes)); + deferred.resolve(sha1HashSync(bytes)); }); return deferred.promise; } - if (worker && false) { // due overhead for data transfer - return performTaskWorker ('sha1-hash', {bytes: bytes}); - } return $timeout(function () { - return sha1Hash(bytes); + return sha1HashSync(bytes); }); }, aesEncrypt: function (bytes, keyBytes, ivBytes) { if (aesNaClEmbed) { + // aesEncryptSync(bytes, keyBytes, ivBytes); return performTaskWorker('aes-encrypt', { - bytes: bytes, - keyBytes: bytesToArrayBuffer(keyBytes), - ivBytes: bytesToArrayBuffer(ivBytes) - }, aesNaClEmbed); - } - if (worker && false) { // due overhead for data transfer - return performTaskWorker('aes-encrypt', { - bytes: bytes, - keyBytes: keyBytes, - ivBytes: ivBytes + bytes: addPadding(convertToArrayBuffer(bytes)), + keyBytes: convertToArrayBuffer(keyBytes), + ivBytes: convertToArrayBuffer(ivBytes) }, aesNaClEmbed); } return $timeout(function () { - return aesEncrypt(bytes, keyBytes, ivBytes); + return convertToArrayBuffer(aesEncryptSync(bytes, keyBytes, ivBytes)); }); }, aesDecrypt: function (encryptedBytes, keyBytes, ivBytes) { - if (worker && false) { // due overhead for data transfer + if (aesNaClEmbed) { + // aesDecryptSync(encryptedBytes, keyBytes, ivBytes); return performTaskWorker('aes-decrypt', { - encryptedBytes: encryptedBytes, - keyBytes: keyBytes, - ivBytes: ivBytes - }); + encryptedBytes: addPadding(convertToArrayBuffer(encryptedBytes)), + keyBytes: convertToArrayBuffer(keyBytes), + ivBytes: convertToArrayBuffer(ivBytes) + }, aesNaClEmbed); } return $timeout(function () { - return aesDecrypt(encryptedBytes, keyBytes, ivBytes); + return convertToArrayBuffer(aesDecryptSync(encryptedBytes, keyBytes, ivBytes)); }); }, factorize: function (bytes) { diff --git a/app/js/lib/tl_utils.js b/app/js/lib/tl_utils.js index 18c736b2..b9feb8ed 100644 --- a/app/js/lib/tl_utils.js +++ b/app/js/lib/tl_utils.js @@ -170,6 +170,9 @@ TLSerialization.prototype.storeBytes = function (bytes, field) { } TLSerialization.prototype.storeIntBytes = function (bytes, bits, field) { + if (bytes instanceof ArrayBuffer) { + bytes = new Uint8Array(bytes); + } var len = bytes.length; if ((bits % 32) || (len * 8) != bits) { throw new Error('Invalid bits: ' + bits + ', ' + bytes.length); @@ -178,13 +181,15 @@ TLSerialization.prototype.storeIntBytes = function (bytes, bits, field) { this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':int' + bits); this.checkLength(len); - for (var i = 0; i < len; i++) { - this.byteView[this.offset++] = bytes[i]; - } + this.byteView.set(bytes, this.offset); + this.offset += len; }; TLSerialization.prototype.storeRawBytes = function (bytes, field) { - var len = bytes.byteLength || bytes.length; + if (bytes instanceof ArrayBuffer) { + bytes = new Uint8Array(bytes); + } + var len = bytes.length; this.debug && console.log('>>>', bytesToHex(bytes), (field || '')); this.checkLength(len); @@ -412,12 +417,18 @@ TLDeserialization.prototype.fetchBytes = function (field) { return bytes; } -TLDeserialization.prototype.fetchIntBytes = function (bits, field) { +TLDeserialization.prototype.fetchIntBytes = function (bits, typed, field) { if (bits % 32) { throw new Error('Invalid bits: ' + bits); } var len = bits / 8; + if (typed) { + var result = this.byteView.subarray(this.offset, this.offset + len); + this.offset += len; + return result; + } + var bytes = []; for (var i = 0; i < len; i++) { bytes.push(this.byteView[this.offset++]); @@ -429,11 +440,18 @@ TLDeserialization.prototype.fetchIntBytes = function (bits, field) { }; -TLDeserialization.prototype.fetchRawBytes = function (len, field) { +TLDeserialization.prototype.fetchRawBytes = function (len, typed, field) { if (len === false) { len = this.readInt((field || '') + '_length'); } + if (typed) { + var bytes = new Uint8Array(len); + bytes.set(this.byteView.subarray(this.offset, this.offset + len)); + this.offset += len; + return bytes; + } + var bytes = []; for (var i = 0; i < len; i++) { bytes.push(this.byteView[this.offset++]); @@ -448,9 +466,9 @@ TLDeserialization.prototype.fetchObject = function (type, field) { switch (type) { case 'int': return this.fetchInt(field); case 'long': return this.fetchLong(field); - case 'int128': return this.fetchIntBytes(128, field); - case 'int256': return this.fetchIntBytes(256, field); - case 'int512': return this.fetchIntBytes(512, field); + case 'int128': return this.fetchIntBytes(128, false, field); + case 'int256': return this.fetchIntBytes(256, false, field); + case 'int512': return this.fetchIntBytes(512, false, field); case 'string': return this.fetchString(field); case 'bytes': return this.fetchBytes(field); case 'double': return this.fetchDouble(field); diff --git a/app/nacl/Makefile b/app/nacl/Makefile index 5a2e71f8..e7aceea1 100644 --- a/app/nacl/Makefile +++ b/app/nacl/Makefile @@ -48,16 +48,7 @@ clean: mtproto_crypto.bc: mtproto_crypto.cc aes_core.c aes_ige.c aes_misc.c $(PNACL_CXX) -o $@ $^ -O2 $(CXXFLAGS) $(LDFLAGS) + # $(PNACL_CXX) -g -o $@ $^ $(CXXFLAGS) $(LDFLAGS) mtproto_crypto.pexe: mtproto_crypto.bc $(PNACL_FINALIZE) -o $@ $< - - -# -# Makefile target to run the SDK's simple HTTP server and serve this example. -# -HTTPD_PY := python $(NACL_SDK_ROOT)/tools/httpd.py - -.PHONY: serve -serve: all - $(HTTPD_PY) -C $(CURDIR) diff --git a/app/nacl/mtproto_crypto.bc b/app/nacl/mtproto_crypto.bc index 641be2bc..6f4b8a4a 100755 Binary files a/app/nacl/mtproto_crypto.bc and b/app/nacl/mtproto_crypto.bc differ diff --git a/app/nacl/mtproto_crypto.cc b/app/nacl/mtproto_crypto.cc index dcdaa08f..369cce59 100644 --- a/app/nacl/mtproto_crypto.cc +++ b/app/nacl/mtproto_crypto.cc @@ -61,11 +61,6 @@ class MtprotoCryptoInstance : public pp::Instance { /// @param[in] var_message The message posted by the browser. virtual void HandleMessage(const pp::Var& var_message) { - // if (1) { - // PostMessage(var_message); - // return; - // } - if (!var_message.is_dictionary()) { return; } @@ -80,7 +75,7 @@ class MtprotoCryptoInstance : public pp::Instance { int32_t intTaskID = varTaskID.AsInt(); std::string strTask = varTask.AsString(); - pp::Var varResult;// = pp::Var::Var(); + pp::Var varResult; if (strTask == "aes-encrypt") { pp::Var varData = request.Get(pp::Var::Var("bytes")); @@ -95,21 +90,42 @@ class MtprotoCryptoInstance : public pp::Instance { pp::VarArrayBuffer abKey = pp::VarArrayBuffer::VarArrayBuffer(varKey); pp::VarArrayBuffer abIv = pp::VarArrayBuffer::VarArrayBuffer(varIv); - int length = abData.ByteLength(); char* what = static_cast(abData.Map()); char* keyBuff = static_cast(abKey.Map()); char* ivBuff = static_cast(abIv.Map()); + int length = abData.ByteLength(); AES_KEY akey; AES_set_encrypt_key((const unsigned char *) keyBuff, 32 * 8, &akey); + AES_ige_encrypt((const unsigned char *)what, (unsigned char *)what, length, &akey, (unsigned char *)ivBuff, AES_ENCRYPT); + + varResult = abData; + + } + else if (strTask == "aes-decrypt") { + pp::Var varData = request.Get(pp::Var::Var("encryptedBytes")); + pp::Var varKey = request.Get(pp::Var::Var("keyBytes")); + pp::Var varIv = request.Get(pp::Var::Var("ivBytes")); + + if (!varData.is_array_buffer() || !varKey.is_array_buffer() || !varIv.is_array_buffer()) { + return; + } + + pp::VarArrayBuffer abData = pp::VarArrayBuffer::VarArrayBuffer(varData); + pp::VarArrayBuffer abKey = pp::VarArrayBuffer::VarArrayBuffer(varKey); + pp::VarArrayBuffer abIv = pp::VarArrayBuffer::VarArrayBuffer(varIv); + + char* what = static_cast(abData.Map()); + char* keyBuff = static_cast(abKey.Map()); + char* ivBuff = static_cast(abIv.Map()); + int length = abData.ByteLength(); + + AES_KEY akey; + AES_set_decrypt_key((const unsigned char *) keyBuff, 32 * 8, &akey); AES_ige_encrypt((const unsigned char *)what, (unsigned char *)what, length, &akey, (unsigned char *)ivBuff, AES_DECRYPT); - // varResult = pp::Var::Var(what); - // varResult = pp::VarArrayBuffer::VarArrayBuffer(pp::Var::Var(what)); - abData.Unmap(); varResult = abData; - // varResult = pp::VarArrayBuffer::VarArrayBuffer(); - // pp::VarArrayBuffer varResult(what); + } else { varResult = pp::Var::Var(); } @@ -119,13 +135,6 @@ class MtprotoCryptoInstance : public pp::Instance { response.Set(pp::Var::Var("result"), varResult); PostMessage(response); - - // std::string message = var_message.AsString(); - // pp::Var var_reply; - // if (message == kHelloString) { - // var_reply = pp::Var(kReplyString); - // PostMessage(var_reply); - // } } }; diff --git a/app/nacl/mtproto_crypto.nmf b/app/nacl/mtproto_crypto.nmf index e1bf3ddb..147fa0ef 100644 --- a/app/nacl/mtproto_crypto.nmf +++ b/app/nacl/mtproto_crypto.nmf @@ -2,8 +2,9 @@ "program": { "portable": { "pnacl-translate": { - "url": "mtproto_crypto.pexe?13" + "url": "mtproto_crypto.pexe?55", + "optlevel": 2 } } } -} +} \ No newline at end of file diff --git a/app/nacl/mtproto_crypto.pexe b/app/nacl/mtproto_crypto.pexe index f7f4b71f..27ce9e30 100644 Binary files a/app/nacl/mtproto_crypto.pexe and b/app/nacl/mtproto_crypto.pexe differ diff --git a/server.js b/server.js index 0141f34c..fce3a10a 100755 --- a/server.js +++ b/server.js @@ -89,7 +89,8 @@ StaticServlet.MimeMap = {   'svg': 'image/svg+xml',   'wav': 'audio/wav', 'ico': 'image/vnd.microsoft.icon', - 'pexe': 'application/x-pnacl' + 'pexe': 'application/x-pnacl', + 'bc': 'application/x-pnacl' }; StaticServlet.prototype.handleRequest = function(req, res) {