Cable-Desktop/js/crypto.js
lilia 9f676af9bb Refactor crypto.js and native client interface
NB: this diff is best viewed with --ignore-whitespace

Distills crypto.js down to the hard cryptoey bones. It pulls from
webcrypto for aes and hmac, and from native client for curve25519 stuff
or potentially another object implementing the handful of needed
curve25519 functions.

Everything else formerly known as crypto, including session storage and
management, axolotl, etc.. is now protocol.js. The separation is not
quite perfect, but it's a big step.

nativeclient.js now enables talking to the native client module through
a high level interface as well as registering callbacks that will be
executed once the module is loaded. And it has tests!

Finally, this commit removes all references to the "testing_only"
object, preferring to run tests on textsecure.crypto instead.
2014-11-06 04:33:43 -08:00

141 lines
6 KiB
JavaScript

/* vim: ts=4:sw=4
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
;(function() {
window.textsecure = window.textsecure || {};
/*
* textsecure.crypto
* glues together various implementations into a single interface
* for all low-level crypto operations,
*/
// TODO Select an ecc implementation
//var ecc = textsecure.NATIVE_CLIENT ? textsecure.nativeclient : textsecure.tweetnacl || asmjs;
var ecc = textsecure.nativeclient;
window.textsecure.crypto = {
getRandomBytes: function(size) {
// At some point we might consider XORing in hashes of random
// UI events to strengthen ourselves against RNG flaws in crypto.getRandomValues
// ie maybe take a look at how Gibson does it at https://www.grc.com/r&d/js.htm
var array = new Uint8Array(size);
window.crypto.getRandomValues(array);
return array.buffer;
},
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);
});
},
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);
});
},
sign: 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);
});
},
HKDF: function(input, salt, info) {
// Specific implementation of RFC 5869 that only returns the first 3 32-byte chunks
// TODO: We dont always need the third chunk, we might skip it
return window.textsecure.crypto.sign(salt, input).then(function(PRK) {
var infoBuffer = new ArrayBuffer(info.byteLength + 1 + 32);
var infoArray = new Uint8Array(infoBuffer);
infoArray.set(new Uint8Array(info), 32);
infoArray[infoArray.length - 1] = 1;
return window.textsecure.crypto.sign(PRK, infoBuffer.slice(32)).then(function(T1) {
infoArray.set(new Uint8Array(T1));
infoArray[infoArray.length - 1] = 2;
return window.textsecure.crypto.sign(PRK, infoBuffer).then(function(T2) {
infoArray.set(new Uint8Array(T2));
infoArray[infoArray.length - 1] = 3;
return window.textsecure.crypto.sign(PRK, infoBuffer).then(function(T3) {
return [ T1, T2, T3 ];
});
});
});
});
},
// Curve 25519 crypto
createKeyPair: function(privKey) {
if (privKey === undefined) {
privKey = textsecure.crypto.getRandomBytes(32);
}
if (privKey.byteLength != 32) {
throw new Error("Invalid private key");
}
return ecc.privToPub(privKey).then(function(raw_keys) {
// prepend version byte
var origPub = new Uint8Array(raw_keys.pubKey);
var pub = new Uint8Array(33);
pub.set(origPub, 1);
pub[0] = 5;
return { pubKey: pub.buffer, privKey: raw_keys.privKey };
});
},
ECDHE: function(pubKey, privKey) {
pubKey = validatePubKeyFormat(pubKey);
if (privKey === undefined || privKey.byteLength != 32)
throw new Error("Invalid private key");
if (pubKey === undefined || pubKey.byteLength != 32)
throw new Error("Invalid public key");
return ecc.ECDHE(pubKey, privKey);
},
Ed25519Sign: function(privKey, message) {
if (privKey === undefined || privKey.byteLength != 32)
throw new Error("Invalid private key");
if (message === undefined)
throw new Error("Invalid message");
return ecc.Ed25519Sign(privKey, message);
},
Ed25519Verify: function(pubKey, msg, sig) {
pubKey = validatePubKeyFormat(pubKey);
if (pubKey === undefined || pubKey.byteLength != 32)
throw new Error("Invalid public key");
if (msg === undefined)
throw new Error("Invalid message");
if (sig === undefined || sig.byteLength != 64)
throw new Error("Invalid signature");
return ecc.Ed25519Verify(pubKey, msg, sig);
}
};
var validatePubKeyFormat = function(pubKey) {
if (pubKey === undefined || ((pubKey.byteLength != 33 || new Uint8Array(pubKey)[0] != 5) && pubKey.byteLength != 32))
throw new Error("Invalid public key");
if (pubKey.byteLength == 33) {
return pubKey.slice(1);
} else {
console.error("WARNING: Expected pubkey of length 33, please report the ST and client that generated the pubkey");
return pubKey;
}
};
})();