Browse Source

Migrate AES to Native Client

Up to 4x faster upload files
Up to 2x faster download files
master
Igor Zhukov 10 years ago
parent
commit
5661533fc4
  1. 68
      app/js/lib/bin_utils.js
  2. 6
      app/js/lib/crypto_worker.js
  3. 151
      app/js/lib/mtproto.js
  4. 56
      app/js/lib/ng_utils.js
  5. 36
      app/js/lib/tl_utils.js
  6. 11
      app/nacl/Makefile
  7. BIN
      app/nacl/mtproto_crypto.bc
  8. 47
      app/nacl/mtproto_crypto.cc
  9. 3
      app/nacl/mtproto_crypto.nmf
  10. BIN
      app/nacl/mtproto_crypto.pexe
  11. 3
      server.js

68
app/js/lib/bin_utils.js

@ -112,11 +112,11 @@ function bytesXor (bytes1, bytes2) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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;
}

6
app/js/lib/crypto_worker.js

@ -29,15 +29,15 @@ onmessage = function (e) { @@ -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:

151
app/js/lib/mtproto.js

@ -82,7 +82,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) @@ -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']) @@ -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']) @@ -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']) @@ -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']) @@ -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']) @@ -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']) @@ -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']) @@ -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']) @@ -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']) @@ -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']) @@ -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']) @@ -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']) @@ -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']) @@ -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']) @@ -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']) @@ -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']) @@ -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']) @@ -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');
}

56
app/js/lib/ng_utils.js

@ -500,18 +500,16 @@ angular.module('izhukov.utils', []) @@ -500,18 +500,16 @@ angular.module('izhukov.utils', [])
};
if (navigator.mimeTypes['application/x-pnacl'] !== undefined) {
var listener = $('<div id="nacl_listener"><embed id="mtproto_crypto" width="0" height="0" src="nacl/mtproto_crypto.nmf" type="application/x-pnacl" /></div>').appendTo($('body'))[0];
var listener = $('<div id="nacl_listener"><embed id="mtproto_crypto" width="0" height="0" src="nacl/mtproto_crypto.nmf?'+Math.random()+'" type="application/x-pnacl" /></div>').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', []) @@ -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) {

36
app/js/lib/tl_utils.js

@ -170,6 +170,9 @@ TLSerialization.prototype.storeBytes = function (bytes, field) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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);

11
app/nacl/Makefile

@ -48,16 +48,7 @@ clean: @@ -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)

BIN
app/nacl/mtproto_crypto.bc

Binary file not shown.

47
app/nacl/mtproto_crypto.cc

@ -61,11 +61,6 @@ class MtprotoCryptoInstance : public pp::Instance { @@ -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 { @@ -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 { @@ -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<char*>(abData.Map());
char* keyBuff = static_cast<char*>(abKey.Map());
char* ivBuff = static_cast<char*>(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<char*>(abData.Map());
char* keyBuff = static_cast<char*>(abKey.Map());
char* ivBuff = static_cast<char*>(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 { @@ -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);
// }
}
};

3
app/nacl/mtproto_crypto.nmf

@ -2,7 +2,8 @@ @@ -2,7 +2,8 @@
"program": {
"portable": {
"pnacl-translate": {
"url": "mtproto_crypto.pexe?13"
"url": "mtproto_crypto.pexe?55",
"optlevel": 2
}
}
}

BIN
app/nacl/mtproto_crypto.pexe

Binary file not shown.

3
server.js

@ -89,7 +89,8 @@ StaticServlet.MimeMap = { @@ -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) {

Loading…
Cancel
Save