Fix audio class, moving crypto functions

This commit is contained in:
Dan Seripap 2016-02-17 21:55:28 -05:00
parent 316db0cfc5
commit f008a7db93
3 changed files with 279 additions and 266 deletions

View File

@ -1,6 +1,6 @@
export default class AudoHandler { export default class AudoHandler {
constructor() { constructor() {
this.beep = new Audio('beep.mp3'); this._beep = window.Audio && new window.Audio('beep.mp3') || false;
this._soundEnabled = true; this._soundEnabled = true;
} }
@ -9,13 +9,14 @@ export default class AudoHandler {
} }
set soundEnabled(state) { set soundEnabled(state) {
if (state) {
this._soundEnabled = state; this._soundEnabled = state;
}
return this; return this;
} }
play() { play() {
this.beep.play(); if (this._beep && this.soundEnabled) {
this._beep.play();
}
return this;
} }
} }

237
src/js/crypto.js Normal file
View File

@ -0,0 +1,237 @@
export default class CryptoUtil {
constructor() {
this._crypto = window.crypto || false;
if (!this._crypto || (!this._crypto.subtle && !this._crypto.webkitSubtle)) {
$('#no-crypto').modal({
backdrop: 'static',
show: false,
keyboard: false
})
$('#no-crypto').modal('show');
return;
}
}
get crypto() {
return this._crypto;
}
convertStringToArrayBufferView(str) {
let bytes = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++) {
bytes[i] = str.charCodeAt(i);
}
return bytes;
}
convertArrayBufferViewToString(buffer) {
let str = "";
for (let i = 0; i < buffer.byteLength; i++) {
str += String.fromCharCode(buffer[i]);
}
return str;
}
createSigningKey() {
return this._crypto.subtle.generateKey(
{
name: "HMAC",
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
//length: 256, //optional, if you want your key length to differ from the hash function's block length
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["sign", "verify"] //can be any combination of "sign" and "verify"
);
}
createPrimaryKeys() {
return this._crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048, //can be 1024, 2048, or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt", "decrypt"] //must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"]
);
}
createSecretKey() {
return this._crypto.subtle.generateKey(
{
name: "AES-CBC",
length: 256, //can be 128, 192, or 256
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt", "decrypt"] //can be "encrypt", "decrypt", "wrapKey", or "unwrapKey"
);
}
encryptSecretKey(data, secretKey) {
// Secret key will be recipient's public key
return this._crypto.subtle.encrypt(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
},
secretKey,
data //ArrayBuffer of data you want to encrypt
);
}
decryptSecretKey(data, key) {
// key will be my private key
return this._crypto.subtle.decrypt(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
//label: Uint8Array([...]) //optional
},
key,
data //ArrayBuffer of the data
);
}
encryptSigningKey(data, signingKey) {
// Secret key will be recipient's public key
return this._crypto.subtle.encrypt(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
},
signingKey,
data //ArrayBuffer of data you want to encrypt
);
}
decryptSigningKey(data, key) {
// key will be my private key
return this._crypto.subtle.decrypt(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
//label: Uint8Array([...]) //optional
},
key,
data //ArrayBuffer of the data
);
}
encryptMessage(data, secretKey, iv) {
return this._crypto.subtle.encrypt(
{
name: "AES-CBC",
//Don't re-use initialization vectors!
//Always generate a new iv every time your encrypt!
iv: iv,
},
secretKey, //from generateKey or importKey above
data //ArrayBuffer of data you want to encrypt
);
}
decryptMessage(data, secretKey, iv) {
return this._crypto.subtle.decrypt(
{
name: "AES-CBC",
iv: iv, //The initialization vector you used to encrypt
},
secretKey, //from generateKey or importKey above
data //ArrayBuffer of the data
);
}
importSecretKey(jwkData, format) {
return this._crypto.subtle.importKey(
format || "jwk", //can be "jwk" or "raw"
//this is an example jwk key, "raw" would be an ArrayBuffer
jwkData,
{ //this is the algorithm options
name: "AES-CBC",
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt", "decrypt"] //can be "encrypt", "decrypt", "wrapKey", or "unwrapKey"
);
}
importPrimaryKey(jwkData, format) {
// Will be someone's public key
let hashObj = {
name: "RSA-OAEP"
};
if (!this._crypto.webkitSubtle) {
hashObj.hash = {name: "SHA-256"};
}
return this._crypto.subtle.importKey(
format || "jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
jwkData,
hashObj,
true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt"] //"encrypt" or "wrapKey" for public key import or
//"decrypt" or "unwrapKey" for private key imports
);
}
exportKey(key, format) {
// Will be public primary key or public signing key
return this._crypto.subtle.exportKey(
format || "jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
key //can be a publicKey or privateKey, as long as extractable was true
);
}
importSigningKey(jwkData) {
return this._crypto.subtle.importKey(
"raw", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
//this is an example jwk key, other key types are Uint8Array objects
jwkData,
{ //these are the algorithm options
name: "HMAC",
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
//length: 256, //optional, if you want your key length to differ from the hash function's block length
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["verify"] //"verify" for public key import, "sign" for private key imports
);
}
signKey(data, keyToSignWith) {
// Will use my private key
return this._crypto.subtle.sign(
{
name: "HMAC",
hash: {name: "SHA-256"}
},
keyToSignWith, //from generateKey or importKey above
data //ArrayBuffer of data you want to sign
);
}
verifyKey(signature, data, keyToVerifyWith) {
// Will verify with sender's public key
return this._crypto.subtle.verify(
{
name: "HMAC",
hash: {name: "SHA-256"}
},
keyToVerifyWith, //from generateKey or importKey above
signature, //ArrayBuffer of the signature
data //ArrayBuffer of the data
);
}
}

View File

@ -1,4 +1,5 @@
import AudioHandler from './audio'; import AudioHandler from './audio';
import CryptoUtil from './crypto';
let fs = window.RequestFileSystem || window.webkitRequestFileSystem; let fs = window.RequestFileSystem || window.webkitRequestFileSystem;
@ -9,6 +10,7 @@ window.favicon = new Favico({
$(function() { $(function() {
const audio = new AudioHandler(); const audio = new AudioHandler();
const cryptoUtil = new CryptoUtil();
let isActive = false; let isActive = false;
let newMessages = 0; let newMessages = 0;
@ -45,16 +47,6 @@ $(function() {
if (!roomId) return; if (!roomId) return;
if (!window.crypto || (!window.crypto.subtle && !window.crypto.webkitSubtle)) {
$('#no-crypto').modal({
backdrop: 'static',
show: false,
keyboard: false
})
$('#no-crypto').modal('show');
return;
}
$('input.share-text').val("https://darkwire.io" + roomId); $('input.share-text').val("https://darkwire.io" + roomId);
$('input.share-text').click(function() { $('input.share-text').click(function() {
@ -92,7 +84,7 @@ $(function() {
$inputMessage.focus(); $inputMessage.focus();
Promise.all([ Promise.all([
createPrimaryKeys() cryptoUtil.createPrimaryKeys()
]) ])
.then(function(data) { .then(function(data) {
keys = { keys = {
@ -100,7 +92,7 @@ $(function() {
private: data[0].privateKey private: data[0].privateKey
}; };
return Promise.all([ return Promise.all([
exportKey(data[0].publicKey, "spki") cryptoUtil.exportKey(data[0].publicKey, "spki")
]); ]);
}) })
.then(function(exportedKeys) { .then(function(exportedKeys) {
@ -301,7 +293,7 @@ $(function() {
let promise = new Promise(function(resolve, reject) { let promise = new Promise(function(resolve, reject) {
let currentUser = user; let currentUser = user;
Promise.all([ Promise.all([
importPrimaryKey(currentUser.publicKey, "spki") cryptoUtil.importPrimaryKey(currentUser.publicKey, "spki")
]) ])
.then(function(keys) { .then(function(keys) {
users.push({ users.push({
@ -352,7 +344,7 @@ $(function() {
username: username, username: username,
message: message message: message
}); });
let vector = crypto.getRandomValues(new Uint8Array(16)); let vector = cryptoUtil.crypto.getRandomValues(new Uint8Array(16));
let secretKey; let secretKey;
let secretKeys; let secretKeys;
@ -362,10 +354,10 @@ $(function() {
let encryptedMessageData; let encryptedMessageData;
// Generate new secret key and vector for each message // Generate new secret key and vector for each message
createSecretKey() cryptoUtil.createSecretKey()
.then(function(key) { .then(function(key) {
secretKey = key; secretKey = key;
return createSigningKey(); return cryptoUtil.createSigningKey();
}) })
.then(function(key) { .then(function(key) {
signingKey = key; signingKey = key;
@ -380,23 +372,23 @@ $(function() {
let secretKeyStr; let secretKeyStr;
// Export secret key // Export secret key
exportKey(secretKey, "raw") cryptoUtil.exportKey(secretKey, "raw")
.then(function(data) { .then(function(data) {
return encryptSecretKey(data, thisUser.publicKey); return cryptoUtil.encryptSecretKey(data, thisUser.publicKey);
}) })
.then(function(encryptedSecretKey) { .then(function(encryptedSecretKey) {
let encData = new Uint8Array(encryptedSecretKey); let encData = new Uint8Array(encryptedSecretKey);
secretKeyStr = convertArrayBufferViewToString(encData); secretKeyStr = cryptoUtil.convertArrayBufferViewToString(encData);
// Export HMAC signing key // Export HMAC signing key
return exportKey(signingKey, "raw"); return cryptoUtil.exportKey(signingKey, "raw");
}) })
.then(function(data) { .then(function(data) {
// Encrypt signing key with user's public key // Encrypt signing key with user's public key
return encryptSigningKey(data, thisUser.publicKey); return cryptoUtil.encryptSigningKey(data, thisUser.publicKey);
}) })
.then(function(encryptedSigningKey) { .then(function(encryptedSigningKey) {
let encData = new Uint8Array(encryptedSigningKey); let encData = new Uint8Array(encryptedSigningKey);
var str = convertArrayBufferViewToString(encData); var str = cryptoUtil.convertArrayBufferViewToString(encData);
resolve({ resolve({
id: thisUser.id, id: thisUser.id,
secretKey: secretKeyStr, secretKey: secretKeyStr,
@ -411,18 +403,18 @@ $(function() {
}) })
.then(function(data) { .then(function(data) {
secretKeys = data; secretKeys = data;
messageData = convertStringToArrayBufferView(message); messageData = cryptoUtil.convertStringToArrayBufferView(message);
return signKey(messageData, signingKey); return cryptoUtil.signKey(messageData, signingKey);
}) })
.then(function(data) { .then(function(data) {
signature = data; signature = data;
return encryptMessage(messageData, secretKey, vector); return cryptoUtil.encryptMessage(messageData, secretKey, vector);
}) })
.then(function(data) { .then(function(data) {
encryptedMessageData = data; encryptedMessageData = data;
let msg = convertArrayBufferViewToString(new Uint8Array(encryptedMessageData)); let msg = cryptoUtil.convertArrayBufferViewToString(new Uint8Array(encryptedMessageData));
let vct = convertArrayBufferViewToString(new Uint8Array(vector)); let vct = cryptoUtil.convertArrayBufferViewToString(new Uint8Array(vector));
let sig = convertArrayBufferViewToString(new Uint8Array(signature)); let sig = cryptoUtil.convertArrayBufferViewToString(new Uint8Array(signature));
socket.emit('new message', { socket.emit('new message', {
message: msg, message: msg,
vector: vct, vector: vct,
@ -439,17 +431,15 @@ $(function() {
if (!isActive) { if (!isActive) {
newMessages++; newMessages++;
favicon.badge(newMessages); favicon.badge(newMessages);
if (audio.soundEnabled && beep) {
audio.play(); audio.play();
} }
}
let message = data.message; let message = data.message;
let messageData = convertStringToArrayBufferView(message); let messageData = cryptoUtil.convertStringToArrayBufferView(message);
let username = data.username; let username = data.username;
let senderId = data.id let senderId = data.id
let vector = data.vector; let vector = data.vector;
let vectorData = convertStringToArrayBufferView(vector); let vectorData = cryptoUtil.convertStringToArrayBufferView(vector);
let secretKeys = data.secretKeys; let secretKeys = data.secretKeys;
let decryptedMessageData; let decryptedMessageData;
let decryptedMessage; let decryptedMessage;
@ -458,29 +448,29 @@ $(function() {
return key.id === myUserId; return key.id === myUserId;
}); });
let signature = data.signature; let signature = data.signature;
let signatureData = convertStringToArrayBufferView(signature); let signatureData = cryptoUtil.convertStringToArrayBufferView(signature);
let secretKeyArrayBuffer = convertStringToArrayBufferView(mySecretKey.secretKey); let secretKeyArrayBuffer = cryptoUtil.convertStringToArrayBufferView(mySecretKey.secretKey);
let signingKeyArrayBuffer = convertStringToArrayBufferView(mySecretKey.encryptedSigningKey); let signingKeyArrayBuffer = cryptoUtil.convertStringToArrayBufferView(mySecretKey.encryptedSigningKey);
decryptSecretKey(secretKeyArrayBuffer, keys.private) cryptoUtil.decryptSecretKey(secretKeyArrayBuffer, keys.private)
.then(function(data) { .then(function(data) {
return importSecretKey(new Uint8Array(data), "raw"); return cryptoUtil.importSecretKey(new Uint8Array(data), "raw");
}) })
.then(function(data) { .then(function(data) {
let secretKey = data; let secretKey = data;
return decryptMessage(messageData, secretKey, vectorData); return cryptoUtil.decryptMessage(messageData, secretKey, vectorData);
}) })
.then(function(data) { .then(function(data) {
decryptedMessageData = data; decryptedMessageData = data;
decryptedMessage = convertArrayBufferViewToString(new Uint8Array(data)) decryptedMessage = cryptoUtil.convertArrayBufferViewToString(new Uint8Array(data))
return decryptSigningKey(signingKeyArrayBuffer, keys.private) return cryptoUtil.decryptSigningKey(signingKeyArrayBuffer, keys.private)
}) })
.then(function(data) { .then(function(data) {
return importSigningKey(new Uint8Array(data), "raw"); return cryptoUtil.importSigningKey(new Uint8Array(data), "raw");
}) })
.then(function(data) { .then(function(data) {
let signingKey = data; let signingKey = data;
return verifyKey(signatureData, decryptedMessageData, signingKey); return cryptoUtil.verifyKey(signatureData, decryptedMessageData, signingKey);
}) })
.then(function(bool) { .then(function(bool) {
if (bool) { if (bool) {
@ -566,225 +556,10 @@ $(function() {
$('.navbar-toggle:visible').click(); $('.navbar-toggle:visible').click();
}); });
$('input.bs-switch').bootstrapSwitch(); let audioSwitch = $('input.bs-switch').bootstrapSwitch();
$('input.bs-switch').on('switchChange.bootstrapSwitch', function(event, state) { audioSwitch.on('switchChange.bootstrapSwitch', function(event, state) {
audio.setSoundEnabled(state); audio.soundEnabled = state;
}); });
function convertStringToArrayBufferView(str) {
var bytes = new Uint8Array(str.length);
for (var i = 0; i < str.length; i++) {
bytes[i] = str.charCodeAt(i);
}
return bytes;
}
function convertArrayBufferViewToString(buffer) {
var str = "";
for (var i = 0; i < buffer.byteLength; i++) {
str += String.fromCharCode(buffer[i]);
}
return str;
}
function createSigningKey() {
return window.crypto.subtle.generateKey(
{
name: "HMAC",
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
//length: 256, //optional, if you want your key length to differ from the hash function's block length
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["sign", "verify"] //can be any combination of "sign" and "verify"
);
}
function createPrimaryKeys() {
return window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048, //can be 1024, 2048, or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt", "decrypt"] //must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"]
);
}
function createSecretKey() {
return window.crypto.subtle.generateKey(
{
name: "AES-CBC",
length: 256, //can be 128, 192, or 256
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt", "decrypt"] //can be "encrypt", "decrypt", "wrapKey", or "unwrapKey"
);
}
function encryptSecretKey(data, secretKey) {
// Secret key will be recipient's public key
return window.crypto.subtle.encrypt(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
},
secretKey,
data //ArrayBuffer of data you want to encrypt
);
}
function decryptSecretKey(data, key) {
// key will be my private key
return window.crypto.subtle.decrypt(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
//label: Uint8Array([...]) //optional
},
key,
data //ArrayBuffer of the data
);
}
function encryptSigningKey(data, signingKey) {
// Secret key will be recipient's public key
return window.crypto.subtle.encrypt(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
},
signingKey,
data //ArrayBuffer of data you want to encrypt
);
}
function decryptSigningKey(data, key) {
// key will be my private key
return window.crypto.subtle.decrypt(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
//label: Uint8Array([...]) //optional
},
key,
data //ArrayBuffer of the data
);
}
function encryptMessage(data, secretKey, iv) {
return window.crypto.subtle.encrypt(
{
name: "AES-CBC",
//Don't re-use initialization vectors!
//Always generate a new iv every time your encrypt!
iv: iv,
},
secretKey, //from generateKey or importKey above
data //ArrayBuffer of data you want to encrypt
);
}
function decryptMessage(data, secretKey, iv) {
return window.crypto.subtle.decrypt(
{
name: "AES-CBC",
iv: iv, //The initialization vector you used to encrypt
},
secretKey, //from generateKey or importKey above
data //ArrayBuffer of the data
);
}
function importSecretKey(jwkData, format) {
return window.crypto.subtle.importKey(
format || "jwk", //can be "jwk" or "raw"
//this is an example jwk key, "raw" would be an ArrayBuffer
jwkData,
{ //this is the algorithm options
name: "AES-CBC",
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt", "decrypt"] //can be "encrypt", "decrypt", "wrapKey", or "unwrapKey"
);
}
function importPrimaryKey(jwkData, format) {
// Will be someone's public key
let hashObj = {
name: "RSA-OAEP"
};
if (!window.crypto.webkitSubtle) {
hashObj.hash = {name: "SHA-256"};
}
return window.crypto.subtle.importKey(
format || "jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
jwkData,
hashObj,
true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt"] //"encrypt" or "wrapKey" for public key import or
//"decrypt" or "unwrapKey" for private key imports
);
}
function exportKey(key, format) {
// Will be public primary key or public signing key
return window.crypto.subtle.exportKey(
format || "jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
key //can be a publicKey or privateKey, as long as extractable was true
);
}
function importSigningKey(jwkData) {
return window.crypto.subtle.importKey(
"raw", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
//this is an example jwk key, other key types are Uint8Array objects
jwkData,
{ //these are the algorithm options
name: "HMAC",
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
//length: 256, //optional, if you want your key length to differ from the hash function's block length
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["verify"] //"verify" for public key import, "sign" for private key imports
);
}
function signKey(data, keyToSignWith) {
// Will use my private key
return window.crypto.subtle.sign(
{
name: "HMAC",
hash: {name: "SHA-256"}
},
keyToSignWith, //from generateKey or importKey above
data //ArrayBuffer of data you want to sign
);
}
function verifyKey(signature, data, keyToVerifyWith) {
// Will verify with sender's public key
return window.crypto.subtle.verify(
{
name: "HMAC",
hash: {name: "SHA-256"}
},
keyToVerifyWith, //from generateKey or importKey above
signature, //ArrayBuffer of the signature
data //ArrayBuffer of the data
);
}
}); });