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. 5
      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) {
} }
function bytesToWords (bytes) { function bytesToWords (bytes) {
var len = bytes.byteLength || bytes.length,
words = [], i;
if (bytes instanceof ArrayBuffer) { if (bytes instanceof ArrayBuffer) {
bytes = new Uint8Array(bytes); bytes = new Uint8Array(bytes);
} }
var len = bytes.length,
words = [], i;
for (i = 0; i < len; i++) { for (i = 0; i < len; i++) {
words[i >>> 2] |= bytes[i] << (24 - (i % 4) * 8); words[i >>> 2] |= bytes[i] << (24 - (i % 4) * 8);
} }
@ -156,6 +156,24 @@ function bytesToArrayBuffer (b) {
return (new Uint8Array(b)).buffer; 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) { function bytesFromArrayBuffer (buffer) {
var len = buffer.byteLength, var len = buffer.byteLength,
byteView = new Uint8Array(buffer), byteView = new Uint8Array(buffer),
@ -207,25 +225,24 @@ function uintToInt (val) {
return val; return val;
} }
function sha1Hash (bytes) { function sha1HashSync (bytes) {
this.rushaInstance = this.rushaInstance || new Rusha(1024 * 1024); this.rushaInstance = this.rushaInstance || new Rusha(1024 * 1024);
// console.log(dT(), 'SHA-1 hash start', bytes.byteLength || bytes.length); // 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'); // console.log(dT(), 'SHA-1 hash finish');
return hashBytes; 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'); // console.log('RSA encrypt start');
var N = new BigInteger(publicKey.modulus, 16), var N = new BigInteger(publicKey.modulus, 16),
@ -233,18 +250,16 @@ function rsaEncrypt (publicKey, bytes) {
X = new BigInteger(bytes), X = new BigInteger(bytes),
encryptedBigInt = X.modPowInt(E, N), encryptedBigInt = X.modPowInt(E, N),
encryptedBytes = bytesFromBigInt(encryptedBigInt, 256); encryptedBytes = bytesFromBigInt(encryptedBigInt, 256);
// console.log('RSA encrypt finish'); // console.log('RSA encrypt finish');
return encryptedBytes; return encryptedBytes;
} }
function aesEncrypt (bytes, keyBytes, ivBytes) { function addPadding(bytes, blockSize) {
blockSize = blockSize || 16;
var len = bytes.byteLength || bytes.length; var len = bytes.byteLength || bytes.length;
console.log(dT(), 'AES encrypt start', len/*, bytesToHex(keyBytes), bytesToHex(ivBytes)*/); var needPadding = blockSize - (len % blockSize);
if (needPadding > 0 && needPadding < blockSize) {
var needPadding = 16 - (len % 16);
if (needPadding > 0 && needPadding < 16) {
var padding = new Array(needPadding); var padding = new Array(needPadding);
(new SecureRandom()).nextBytes(padding); (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), { var encryptedWords = CryptoJS.AES.encrypt(bytesToWords(bytes), bytesToWords(keyBytes), {
iv: bytesToWords(ivBytes), iv: bytesToWords(ivBytes),
padding: CryptoJS.pad.NoPadding, padding: CryptoJS.pad.NoPadding,
@ -262,15 +286,14 @@ function aesEncrypt (bytes, keyBytes, ivBytes) {
}).ciphertext; }).ciphertext;
var encryptedBytes = bytesFromWords(encryptedWords); var encryptedBytes = bytesFromWords(encryptedWords);
// console.log(dT(), 'AES encrypt finish');
console.log(dT(), 'AES encrypt finish');
return encryptedBytes; return encryptedBytes;
} }
function aesDecrypt (encryptedBytes, keyBytes, ivBytes) { function aesDecryptSync (encryptedBytes, keyBytes, ivBytes) {
// console.log('AES decrypt start', encryptedBytes.length/*, bytesToHex(keyBytes), bytesToHex(ivBytes)*/);
// console.log(dT(), 'AES decrypt start', encryptedBytes.length);
var decryptedWords = CryptoJS.AES.decrypt({ciphertext: bytesToWords(encryptedBytes)}, bytesToWords(keyBytes), { var decryptedWords = CryptoJS.AES.decrypt({ciphertext: bytesToWords(encryptedBytes)}, bytesToWords(keyBytes), {
iv: bytesToWords(ivBytes), iv: bytesToWords(ivBytes),
padding: CryptoJS.pad.NoPadding, padding: CryptoJS.pad.NoPadding,
@ -278,8 +301,7 @@ function aesDecrypt (encryptedBytes, keyBytes, ivBytes) {
}); });
var bytes = bytesFromWords(decryptedWords); var bytes = bytesFromWords(decryptedWords);
// console.log(dT(), 'AES decrypt finish');
// console.log('AES decrypt finish');
return bytes; return bytes;
} }

6
app/js/lib/crypto_worker.js

@ -29,15 +29,15 @@ onmessage = function (e) {
break; break;
case 'sha1-hash': case 'sha1-hash':
result = sha1Hash(e.data.bytes); result = sha1HashSync(e.data.bytes);
break; break;
case 'aes-encrypt': 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; break;
case 'aes-decrypt': 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; break;
default: default:

151
app/js/lib/mtproto.js

@ -82,7 +82,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
var buffer = RSAPublicKey.getBuffer(); var buffer = RSAPublicKey.getBuffer();
var fingerprintBytes = sha1Hash(buffer).slice(-8); var fingerprintBytes = sha1BytesSync(buffer).slice(-8);
fingerprintBytes.reverse(); fingerprintBytes.reverse();
publicKeysParsed[bytesToHex(fingerprintBytes)] = { publicKeysParsed[bytesToHex(fingerprintBytes)] = {
@ -169,7 +169,11 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
.factory('MtpAuthorizer', function (MtpDcConfigurator, MtpRsaKeysManager, MtpSecureRandom, MtpTimeManager, CryptoWorker, $http, $q, $timeout) { .factory('MtpAuthorizer', function (MtpDcConfigurator, MtpRsaKeysManager, MtpSecureRandom, MtpTimeManager, CryptoWorker, $http, $q, $timeout) {
var chromeMatches = navigator.userAgent.match(/Chrome\/(\d+(\.\d+)?)/), 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) { function mtpSendPlainRequest (dcID, requestBuffer) {
var requestLength = requestBuffer.byteLength, var requestLength = requestBuffer.byteLength,
@ -190,16 +194,10 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
resultArray.set(headerArray); resultArray.set(headerArray);
resultArray.set(requestArray, headerArray.length); resultArray.set(requestArray, headerArray.length);
delete $http.defaults.headers.post['Content-Type']; var requestData = xhrSendBuffer ? resultBuffer : resultArray,
delete $http.defaults.headers.common['Accept']; requestPromise;
if (!('ArrayBufferView' in window) && (!chromeVersion || chromeVersion < 30)) {
resultArray = resultArray.buffer;
}
var requestPromise;
try { try {
requestPromise = $http.post('http://' + MtpDcConfigurator.chooseServer(dcID) + '/apiw1', resultArray, { requestPromise = $http.post('http://' + MtpDcConfigurator.chooseServer(dcID) + '/apiw1', requestData, {
responseType: 'arraybuffer', responseType: 'arraybuffer',
transformRequest: null transformRequest: null
}); });
@ -305,7 +303,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
new_nonce: auth.newNonce new_nonce: auth.newNonce
}, 'P_Q_inner_data', 'DECRYPTED_DATA'); }, '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}); var request = new TLSerialization({mtproto: true});
request.storeMethod('req_DH_params', { request.storeMethod('req_DH_params', {
@ -337,7 +335,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
} }
if (response._ == 'server_DH_params_fail') { 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)) { if (!bytesCmp (newNonceHash, response.new_nonce_hash)) {
deferred.reject(new Error('server_DH_params_fail new_nonce_hash mismatch')); deferred.reject(new Error('server_DH_params_fail new_nonce_hash mismatch'));
return false; return false;
@ -362,10 +360,10 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
function mtpDecryptServerDhDataAnswer (auth, encryptedAnswer) { function mtpDecryptServerDhDataAnswer (auth, encryptedAnswer) {
auth.localTime = tsNow(); auth.localTime = tsNow();
auth.tmpAesKey = sha1Hash(auth.newNonce.concat(auth.serverNonce)).concat(sha1Hash(auth.serverNonce.concat(auth.newNonce)).slice(0, 12)); auth.tmpAesKey = sha1BytesSync(auth.newNonce.concat(auth.serverNonce)).concat(sha1BytesSync(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.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 hash = answerWithHash.slice(0, 20);
var answerWithPadding = answerWithHash.slice(20); var answerWithPadding = answerWithHash.slice(20);
@ -395,7 +393,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
var offset = deserializer.getOffset(); 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'); throw new Error('server_DH_inner_data SHA1-hash mismatch');
} }
@ -420,9 +418,9 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
g_b: gB, g_b: gB,
}, 'Client_DH_Inner_Data'); }, '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}); var request = new TLSerialization({mtproto: true});
request.storeMethod('set_client_DH_params', { 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) { CryptoWorker.modPow(auth.gA, auth.b, auth.dhPrime).then(function (authKey) {
var authKeyHash = sha1Hash(authKey), var authKeyHash = sha1BytesSync(authKey),
authKeyAux = authKeyHash.slice(0, 8), authKeyAux = authKeyHash.slice(0, 8),
authKeyID = authKeyHash.slice(-8); authKeyID = authKeyHash.slice(-8);
console.log(dT(), 'Got Set_client_DH_params_answer', response._); console.log(dT(), 'Got Set_client_DH_params_answer', response._);
switch (response._) { switch (response._) {
case 'dh_gen_ok': 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)) { if (!bytesCmp(newNonceHash1, response.new_nonce_hash1)) {
deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash1 mismatch')); deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash1 mismatch'));
@ -476,7 +474,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
break; break;
case 'dh_gen_retry': 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)) { if (!bytesCmp(newNonceHash2, response.new_nonce_hash2)) {
deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash2 mismatch')); deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash2 mismatch'));
return false; return false;
@ -485,7 +483,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
return mtpSendSetClientDhParams(auth); return mtpSendSetClientDhParams(auth);
case 'dh_gen_fail': 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)) { if (!bytesCmp(newNonceHash3, response.new_nonce_hash3)) {
deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash3 mismatch')); deferred.reject(new Error('Set_client_DH_params_answer new_nonce_hash3 mismatch'));
return false; return false;
@ -553,7 +551,11 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
offline, offline,
offlineInited = false, offlineInited = false,
chromeMatches = navigator.userAgent.match(/Chrome\/(\d+(\.\d+)?)/), 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 () { $rootScope.retryOnline = function () {
$(document.body).trigger('online'); $(document.body).trigger('online');
@ -566,7 +568,9 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
this.iii = iii++; this.iii = iii++;
this.authKey = authKey; 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; this.serverSalt = serverSalt;
@ -827,19 +831,47 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
}; };
MtpNetworker.prototype.getMsgKeyIv = function (msgKey, isOut) { MtpNetworker.prototype.getMsgKeyIv = function (msgKey, isOut) {
var authKey = this.authKey, var authKey = this.authKeyUint8,
x = isOut ? 0 : 8; x = isOut ? 0 : 8,
sha1aText = new Uint8Array(48),
var promises = { sha1bText = new Uint8Array(48),
sha1a: CryptoWorker.sha1Hash(msgKey.concat(authKey.slice(x, x + 32))), sha1cText = new Uint8Array(48),
sha1b: CryptoWorker.sha1Hash(authKey.slice(32 + x, 48 + x).concat(msgKey, authKey.slice(48 + x, 64 + x))), sha1dText = new Uint8Array(48),
sha1c: CryptoWorker.sha1Hash(authKey.slice(64 + x, 96 + x).concat(msgKey)), promises = {};
sha1d: CryptoWorker.sha1Hash(msgKey.concat(authKey.slice(96 + x, 128 + x)))
}; 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) { 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 aesKey = new Uint8Array(32),
var aesIv = result.sha1a.slice(8, 20).concat(result.sha1b.slice(0, 8), result.sha1c.slice(16, 20), result.sha1d.slice(0, 8)); 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]; return [aesKey, aesIv];
}); });
@ -1076,14 +1108,14 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
MtpNetworker.prototype.getEncryptedMessage = function (bytes) { MtpNetworker.prototype.getEncryptedMessage = function (bytes) {
var self = this; var self = this;
console.log(dT(), 'Start encrypt', bytes.byteLength); // console.log(dT(), 'Start encrypt', bytes.byteLength);
return CryptoWorker.sha1Hash(bytes).then(function (bytesHash) { return CryptoWorker.sha1Hash(bytes).then(function (bytesHash) {
console.log(dT(), 'after hash'); // console.log(dT(), 'after hash');
var msgKey = bytesHash.slice(-16); var msgKey = new Uint8Array(bytesHash).subarray(4, 20);
return self.getMsgKeyIv(msgKey, true).then(function (keyIv) { 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) { return CryptoWorker.aesEncrypt(bytes, keyIv[0], keyIv[1]).then(function (encryptedBytes) {
console.log(dT(), 'Finish encrypt'); // console.log(dT(), 'Finish encrypt');
return { return {
bytes: encryptedBytes, bytes: encryptedBytes,
msgKey: msgKey msgKey: msgKey
@ -1117,18 +1149,12 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
return this.getEncryptedMessage(data.getBuffer()).then(function (encryptedResult) { return this.getEncryptedMessage(data.getBuffer()).then(function (encryptedResult) {
// console.log(dT(), 'Got encrypted out message'/*, 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(self.authKeyID, 64, 'auth_key_id');
request.storeIntBytes(encryptedResult.msgKey, 128, 'msg_key'); request.storeIntBytes(encryptedResult.msgKey, 128, 'msg_key');
request.storeRawBytes(encryptedResult.bytes, 'encrypted_data'); request.storeRawBytes(encryptedResult.bytes, 'encrypted_data');
delete $http.defaults.headers.post['Content-Type']; var requestData = xhrSendBuffer ? request.getBuffer() : request.getArray();
delete $http.defaults.headers.common['Accept'];
var resultArray = request.getArray();
if (!('ArrayBufferView' in window) && (!chromeVersion || chromeVersion < 30)) {
resultArray = resultArray.buffer;
}
var requestPromise; var requestPromise;
try { try {
@ -1136,7 +1162,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
responseType: 'arraybuffer', responseType: 'arraybuffer',
transformRequest: null 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) { } catch (e) {
requestPromise = $q.reject(e); requestPromise = $q.reject(e);
} }
@ -1172,32 +1198,29 @@ angular.module('izhukov.mtproto', ['izhukov.utils'])
var deserializer = new TLDeserialization(responseBuffer); 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)) { if (!bytesCmp(authKeyID, this.authKeyID)) {
throw new Error('Invalid server auth_key_id: ' + bytesToHex(authKeyID)); throw new Error('Invalid server auth_key_id: ' + bytesToHex(authKeyID));
} }
var msgKey = deserializer.fetchIntBytes(128, 'msg_key'); var msgKey = deserializer.fetchIntBytes(128, true, 'msg_key'),
encryptedData = deserializer.fetchRawBytes(responseBuffer.byteLength - deserializer.getOffset(), true, 'encrypted_data');
var dataLength = responseBuffer.byteLength - deserializer.getOffset();
var encryptedData = deserializer.fetchRawBytes(dataLength, 'encrypted_data');
return this.getDecryptedMessage(msgKey, encryptedData).then(function (dataWithPadding) { return this.getDecryptedMessage(msgKey, encryptedData).then(function (dataWithPadding) {
var buffer = bytesToArrayBuffer(dataWithPadding); var deserializer = new TLDeserialization(dataWithPadding, {mtproto: true});
var deserializer = new TLDeserialization(buffer, {mtproto: true});
var salt = deserializer.fetchIntBytes(64, 'salt'); var salt = deserializer.fetchIntBytes(64, false, 'salt');
var sessionID = deserializer.fetchIntBytes(64, 'session_id'); var sessionID = deserializer.fetchIntBytes(64, false, 'session_id');
var messageID = deserializer.fetchLong('message_id'); var messageID = deserializer.fetchLong('message_id');
var seqNo = deserializer.fetchInt('seq_no'); 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) { return CryptoWorker.sha1Hash(hashData).then(function (dataHash) {
if (!bytesCmp(msgKey, dataHashed.slice(-16))) { if (!bytesCmp(msgKey, bytesFromArrayBuffer(dataHash).slice(-16))) {
console.warn(msgKey, bytesFromArrayBuffer(dataHash));
throw new Error('server msgKey mismatch'); throw new Error('server msgKey mismatch');
} }

56
app/js/lib/ng_utils.js

@ -500,18 +500,16 @@ angular.module('izhukov.utils', [])
}; };
if (navigator.mimeTypes['application/x-pnacl'] !== undefined) { 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) { listener.addEventListener('load', function (e) {
aesNaClEmbed = listener.firstChild; aesNaClEmbed = listener.firstChild;
console.warn('NaCl ready', aesNaClEmbed); console.log(dT(), 'NaCl ready');
}, true); }, true);
listener.addEventListener('message', function (e) { listener.addEventListener('message', function (e) {
console.log(e.data.result);
console.log(bytesFromArrayBuffer(e.data.result));
finalizeTask(e.data.taskID, e.data.result); finalizeTask(e.data.taskID, e.data.result);
}, true); }, true);
listener.addEventListener('error', function (e) { listener.addEventListener('error', function (e) {
console.error(e); console.error('NaCl error', e);
}, true); }, true);
} }
@ -543,57 +541,49 @@ angular.module('izhukov.utils', [])
return { return {
sha1Hash: function (bytes) { sha1Hash: function (bytes) {
if (useSha1Crypto) { 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(), var deferred = $q.defer(),
buffer = bytes instanceof ArrayBuffer bytesTyped = Array.isArray(bytes) ? convertToUint8Array(bytes) : bytes;
? bytes // console.log(dT(), 'Native sha1 start');
: bytesToArrayBuffer(bytes); webCrypto.digest({name: 'SHA-1'}, bytesTyped).then(function (digest) {
// console.log(dT(), 'Native sha1 done');
webCrypto.digest({name: 'SHA-1'}, buffer).then(function (digest) { deferred.resolve(digest);
deferred.resolve(bytesFromArrayBuffer(digest));
}, function (e) { }, function (e) {
console.error('Crypto digest error', e); console.error('Crypto digest error', e);
useSha1Crypto = false; useSha1Crypto = false;
deferred.resolve(sha1Hash(bytes)); deferred.resolve(sha1HashSync(bytes));
}); });
return deferred.promise; return deferred.promise;
} }
if (worker && false) { // due overhead for data transfer
return performTaskWorker ('sha1-hash', {bytes: bytes});
}
return $timeout(function () { return $timeout(function () {
return sha1Hash(bytes); return sha1HashSync(bytes);
}); });
}, },
aesEncrypt: function (bytes, keyBytes, ivBytes) { aesEncrypt: function (bytes, keyBytes, ivBytes) {
if (aesNaClEmbed) { if (aesNaClEmbed) {
// aesEncryptSync(bytes, keyBytes, ivBytes);
return performTaskWorker('aes-encrypt', { return performTaskWorker('aes-encrypt', {
bytes: bytes, bytes: addPadding(convertToArrayBuffer(bytes)),
keyBytes: bytesToArrayBuffer(keyBytes), keyBytes: convertToArrayBuffer(keyBytes),
ivBytes: bytesToArrayBuffer(ivBytes) ivBytes: convertToArrayBuffer(ivBytes)
}, aesNaClEmbed);
}
if (worker && false) { // due overhead for data transfer
return performTaskWorker('aes-encrypt', {
bytes: bytes,
keyBytes: keyBytes,
ivBytes: ivBytes
}, aesNaClEmbed); }, aesNaClEmbed);
} }
return $timeout(function () { return $timeout(function () {
return aesEncrypt(bytes, keyBytes, ivBytes); return convertToArrayBuffer(aesEncryptSync(bytes, keyBytes, ivBytes));
}); });
}, },
aesDecrypt: function (encryptedBytes, 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', { return performTaskWorker('aes-decrypt', {
encryptedBytes: encryptedBytes, encryptedBytes: addPadding(convertToArrayBuffer(encryptedBytes)),
keyBytes: keyBytes, keyBytes: convertToArrayBuffer(keyBytes),
ivBytes: ivBytes ivBytes: convertToArrayBuffer(ivBytes)
}); }, aesNaClEmbed);
} }
return $timeout(function () { return $timeout(function () {
return aesDecrypt(encryptedBytes, keyBytes, ivBytes); return convertToArrayBuffer(aesDecryptSync(encryptedBytes, keyBytes, ivBytes));
}); });
}, },
factorize: function (bytes) { factorize: function (bytes) {

36
app/js/lib/tl_utils.js

@ -170,6 +170,9 @@ TLSerialization.prototype.storeBytes = function (bytes, field) {
} }
TLSerialization.prototype.storeIntBytes = function (bytes, bits, field) { TLSerialization.prototype.storeIntBytes = function (bytes, bits, field) {
if (bytes instanceof ArrayBuffer) {
bytes = new Uint8Array(bytes);
}
var len = bytes.length; var len = bytes.length;
if ((bits % 32) || (len * 8) != bits) { if ((bits % 32) || (len * 8) != bits) {
throw new Error('Invalid bits: ' + bits + ', ' + bytes.length); 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.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':int' + bits);
this.checkLength(len); this.checkLength(len);
for (var i = 0; i < len; i++) { this.byteView.set(bytes, this.offset);
this.byteView[this.offset++] = bytes[i]; this.offset += len;
}
}; };
TLSerialization.prototype.storeRawBytes = function (bytes, field) { 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.debug && console.log('>>>', bytesToHex(bytes), (field || ''));
this.checkLength(len); this.checkLength(len);
@ -412,12 +417,18 @@ TLDeserialization.prototype.fetchBytes = function (field) {
return bytes; return bytes;
} }
TLDeserialization.prototype.fetchIntBytes = function (bits, field) { TLDeserialization.prototype.fetchIntBytes = function (bits, typed, field) {
if (bits % 32) { if (bits % 32) {
throw new Error('Invalid bits: ' + bits); throw new Error('Invalid bits: ' + bits);
} }
var len = bits / 8; var len = bits / 8;
if (typed) {
var result = this.byteView.subarray(this.offset, this.offset + len);
this.offset += len;
return result;
}
var bytes = []; var bytes = [];
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
bytes.push(this.byteView[this.offset++]); 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) { if (len === false) {
len = this.readInt((field || '') + '_length'); 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 = []; var bytes = [];
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
bytes.push(this.byteView[this.offset++]); bytes.push(this.byteView[this.offset++]);
@ -448,9 +466,9 @@ TLDeserialization.prototype.fetchObject = function (type, field) {
switch (type) { switch (type) {
case 'int': return this.fetchInt(field); case 'int': return this.fetchInt(field);
case 'long': return this.fetchLong(field); case 'long': return this.fetchLong(field);
case 'int128': return this.fetchIntBytes(128, field); case 'int128': return this.fetchIntBytes(128, false, field);
case 'int256': return this.fetchIntBytes(256, field); case 'int256': return this.fetchIntBytes(256, false, field);
case 'int512': return this.fetchIntBytes(512, field); case 'int512': return this.fetchIntBytes(512, false, field);
case 'string': return this.fetchString(field); case 'string': return this.fetchString(field);
case 'bytes': return this.fetchBytes(field); case 'bytes': return this.fetchBytes(field);
case 'double': return this.fetchDouble(field); case 'double': return this.fetchDouble(field);

11
app/nacl/Makefile

@ -48,16 +48,7 @@ clean:
mtproto_crypto.bc: mtproto_crypto.cc aes_core.c aes_ige.c aes_misc.c mtproto_crypto.bc: mtproto_crypto.cc aes_core.c aes_ige.c aes_misc.c
$(PNACL_CXX) -o $@ $^ -O2 $(CXXFLAGS) $(LDFLAGS) $(PNACL_CXX) -o $@ $^ -O2 $(CXXFLAGS) $(LDFLAGS)
# $(PNACL_CXX) -g -o $@ $^ $(CXXFLAGS) $(LDFLAGS)
mtproto_crypto.pexe: mtproto_crypto.bc mtproto_crypto.pexe: mtproto_crypto.bc
$(PNACL_FINALIZE) -o $@ $< $(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 {
/// @param[in] var_message The message posted by the browser. /// @param[in] var_message The message posted by the browser.
virtual void HandleMessage(const pp::Var& var_message) { virtual void HandleMessage(const pp::Var& var_message) {
// if (1) {
// PostMessage(var_message);
// return;
// }
if (!var_message.is_dictionary()) { if (!var_message.is_dictionary()) {
return; return;
} }
@ -80,7 +75,7 @@ class MtprotoCryptoInstance : public pp::Instance {
int32_t intTaskID = varTaskID.AsInt(); int32_t intTaskID = varTaskID.AsInt();
std::string strTask = varTask.AsString(); std::string strTask = varTask.AsString();
pp::Var varResult;// = pp::Var::Var(); pp::Var varResult;
if (strTask == "aes-encrypt") { if (strTask == "aes-encrypt") {
pp::Var varData = request.Get(pp::Var::Var("bytes")); 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 abKey = pp::VarArrayBuffer::VarArrayBuffer(varKey);
pp::VarArrayBuffer abIv = pp::VarArrayBuffer::VarArrayBuffer(varIv); pp::VarArrayBuffer abIv = pp::VarArrayBuffer::VarArrayBuffer(varIv);
int length = abData.ByteLength();
char* what = static_cast<char*>(abData.Map()); char* what = static_cast<char*>(abData.Map());
char* keyBuff = static_cast<char*>(abKey.Map()); char* keyBuff = static_cast<char*>(abKey.Map());
char* ivBuff = static_cast<char*>(abIv.Map()); char* ivBuff = static_cast<char*>(abIv.Map());
int length = abData.ByteLength();
AES_KEY akey; AES_KEY akey;
AES_set_encrypt_key((const unsigned char *) keyBuff, 32 * 8, &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); 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 = abData;
// varResult = pp::VarArrayBuffer::VarArrayBuffer();
// pp::VarArrayBuffer varResult(what);
} else { } else {
varResult = pp::Var::Var(); varResult = pp::Var::Var();
} }
@ -119,13 +135,6 @@ class MtprotoCryptoInstance : public pp::Instance {
response.Set(pp::Var::Var("result"), varResult); response.Set(pp::Var::Var("result"), varResult);
PostMessage(response); PostMessage(response);
// std::string message = var_message.AsString();
// pp::Var var_reply;
// if (message == kHelloString) {
// var_reply = pp::Var(kReplyString);
// PostMessage(var_reply);
// }
} }
}; };

5
app/nacl/mtproto_crypto.nmf

@ -2,8 +2,9 @@
"program": { "program": {
"portable": { "portable": {
"pnacl-translate": { "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 = {
  'svg': 'image/svg+xml',   'svg': 'image/svg+xml',
  'wav': 'audio/wav',   'wav': 'audio/wav',
'ico': 'image/vnd.microsoft.icon', 'ico': 'image/vnd.microsoft.icon',
'pexe': 'application/x-pnacl' 'pexe': 'application/x-pnacl',
'bc': 'application/x-pnacl'
}; };
StaticServlet.prototype.handleRequest = function(req, res) { StaticServlet.prototype.handleRequest = function(req, res) {

Loading…
Cancel
Save