signal-desktop/libtextsecure/crypto.js
lilia ac7c95fed0 Validate argument lengths in crypto.js
These functions accept an array buffer and extract an AES and MAC key
from it without verifying it has the appropriate length. Ciphertext
messages are similarly dissected. The slice function does not raise an
error on out of bounds accesses but instead returns an empty or
partially-filled array. Empty or short arrays will be passed through to
the window.crypto.subtle API, where they will raise an error. We should
not rely on the Web Crypto API to validate key lengths or for MAC checks
to fail. Instead, validate the lengths of given parameters before
extracting their components.

// FREEBIE
2015-10-27 13:58:23 -07:00

115 lines
4.6 KiB
JavaScript

/*
* vim: ts=4:sw=4:expandtab
*/
;(function(){
'use strict';
// Various wrappers around low-level crypto operation for specific functions
var encrypt = function(key, data, iv) {
return window.crypto.subtle.importKey('raw', key, {name: 'AES-CBC'}, false, ['encrypt']).then(function(key) {
return window.crypto.subtle.encrypt({name: 'AES-CBC', iv: new Uint8Array(iv)}, key, data);
});
};
var decrypt = function(key, data, iv) {
return window.crypto.subtle.importKey('raw', key, {name: 'AES-CBC'}, false, ['decrypt']).then(function(key) {
return window.crypto.subtle.decrypt({name: 'AES-CBC', iv: new Uint8Array(iv)}, key, data);
});
};
var calculateMAC = function(key, data) {
return window.crypto.subtle.importKey('raw', key, {name: 'HMAC', hash: {name: 'SHA-256'}}, false, ['sign']).then(function(key) {
return window.crypto.subtle.sign( {name: 'HMAC', hash: 'SHA-256'}, key, data);
});
};
var verifyMAC = function(data, key, mac) {
return calculateMAC(key, data).then(function(calculated_mac) {
if (!isEqual(calculated_mac, mac, true))
throw new Error("Bad MAC");
});
}
window.textsecure = window.textsecure || {};
window.textsecure.crypto = {
// Decrypts message into a raw string
decryptWebsocketMessage: function(message, signaling_key) {
var decodedMessage = message.toArrayBuffer();
if (signaling_key.byteLength != 52) {
throw new Error("Got invalid length signaling_key");
}
if (decodedMessage.byteLength < 1 + 16 + 10) {
throw new Error("Got invalid length message");
}
if (new Uint8Array(decodedMessage)[0] != 1) {
throw new Error("Got bad version number: " + decodedMessage[0]);
}
var aes_key = signaling_key.slice(0, 32);
var mac_key = signaling_key.slice(32, 32 + 20);
var iv = decodedMessage.slice(1, 1 + 16);
var ciphertext = decodedMessage.slice(1 + 16, decodedMessage.byteLength - 10);
var ivAndCiphertext = decodedMessage.slice(0, decodedMessage.byteLength - 10);
var mac = decodedMessage.slice(decodedMessage.byteLength - 10, decodedMessage.byteLength);
return verifyMAC(ivAndCiphertext, mac_key, mac).then(function() {
return decrypt(aes_key, ciphertext, iv);
});
},
decryptAttachment: function(encryptedBin, keys) {
if (keys.byteLength != 64) {
throw new Error("Got invalid length attachment keys");
}
if (encryptedBin.byteLength < 16 + 32) {
throw new Error("Got invalid length attachment");
}
var aes_key = keys.slice(0, 32);
var mac_key = keys.slice(32, 64);
var iv = encryptedBin.slice(0, 16);
var ciphertext = encryptedBin.slice(16, encryptedBin.byteLength - 32);
var ivAndCiphertext = encryptedBin.slice(0, encryptedBin.byteLength - 32);
var mac = encryptedBin.slice(encryptedBin.byteLength - 32, encryptedBin.byteLength);
return verifyMAC(ivAndCiphertext, mac_key, mac).then(function() {
return decrypt(aes_key, ciphertext, iv);
});
},
encryptAttachment: function(plaintext, keys, iv) {
if (keys.byteLength != 64) {
throw new Error("Got invalid length attachment keys");
}
if (iv.byteLength != 16) {
throw new Error("Got invalid length attachment iv");
}
var aes_key = keys.slice(0, 32);
var mac_key = keys.slice(32, 64);
return encrypt(aes_key, plaintext, iv).then(function(ciphertext) {
var ivAndCiphertext = new Uint8Array(16 + ciphertext.byteLength);
ivAndCiphertext.set(new Uint8Array(iv));
ivAndCiphertext.set(new Uint8Array(ciphertext), 16);
return calculateMAC(mac_key, ivAndCiphertext.buffer).then(function(mac) {
var encryptedBin = new Uint8Array(16 + ciphertext.byteLength + 32);
encryptedBin.set(ivAndCiphertext);
encryptedBin.set(new Uint8Array(mac), 16 + ciphertext.byteLength);
return encryptedBin.buffer;
});
});
},
getRandomBytes: function(size) {
var array = new Uint8Array(size);
window.crypto.getRandomValues(array);
return array.buffer;
}
};
})();