Use asymmetric encryption to replace passphrase exchange

All parties now create public/private key pairs for
encryption/decryption and signing/verification. Public keys are passed
around and stored in server memory while the room is alive. Session
keys, which are used to encrypt cleartext messages, are newly generated
for each message and are encrypted using each participant’s public key.
Messages are signed using the sender’s private signing key.
This commit is contained in:
Alan Friedman 2016-01-23 20:49:29 -05:00
parent 64b12776b8
commit d46b8f24b5
3 changed files with 361 additions and 262 deletions

View File

@ -20,7 +20,6 @@ $(function() {
]; ];
let $window = $(window); let $window = $(window);
let $usernameInput = $('.usernameInput'); // Input for username
let $messages = $('.messages'); // Messages area let $messages = $('.messages'); // Messages area
let $inputMessage = $('.inputMessage'); // Input message input box let $inputMessage = $('.inputMessage'); // Input message input box
let $key = $('.key'); let $key = $('.key');
@ -33,14 +32,15 @@ $(function() {
// Prompt for setting a username // Prompt for setting a username
let username; let username;
let myUserId;
let connected = false; let connected = false;
let typing = false; let typing = false;
let lastTypingTime; let lastTypingTime;
let $currentInput = $usernameInput.focus();
let encryptionKey;
let roomId = window.location.pathname.length ? window.location.pathname : null; let roomId = window.location.pathname.length ? window.location.pathname : null;
let keys = {};
if (!roomId) return; if (!roomId) return;
if (!window.crypto || (!window.crypto.subtle && !window.crypto.webkitSubtle)) { if (!window.crypto || (!window.crypto.subtle && !window.crypto.webkitSubtle)) {
@ -53,11 +53,18 @@ $(function() {
return; return;
} }
$('textarea.share-text').val("Let's chat on darkwire.io at https://darkwire.io" + roomId);
$('textarea.share-text').click(function() {
$(this).focus();
$(this).select();
this.setSelectionRange(0, 9999);
});
var crypto = window.crypto; var crypto = window.crypto;
var cryptoSubtle = window.crypto.subtle || window.crypto.webkitSubtle; var cryptoSubtle = window.crypto.subtle || window.crypto.webkitSubtle;
let socket = io(roomId); let socket = io(roomId);
$('#roomIdKey').text(roomId.replace('/', ''));
FastClick.attach(document.body); FastClick.attach(document.body);
@ -69,7 +76,7 @@ $(function() {
} }
// Sets the client's username // Sets the client's username
function setUsername () { function initChat () {
username = window.username; username = window.username;
// warn not incognitor // warn not incognitor
if (!fs) { if (!fs) {
@ -83,19 +90,41 @@ $(function() {
// If the username is valid // If the username is valid
if (username) { if (username) {
$chatPage.show(); $chatPage.show();
$currentInput = $inputMessage.focus(); $inputMessage.focus();
// Tell the server your username Promise.all([
socket.emit('add user', username); createPrimaryKeys(),
createSigningKeys()
])
.then(function(data) {
keys.primary = {
public: data[0].publicKey,
private: data[0].privateKey
};
keys.signing = {
public: data[1].publicKey,
private: data[1].privateKey
};
return Promise.all([
exportKey(data[0].publicKey),
exportKey(data[1].publicKey),
]);
})
.then(function(exportedKeys) {
// Tell the server your username and send public keys
socket.emit('add user', {
username: username,
publicPrimaryKey: exportedKeys[0],
publicSigningKey: exportedKeys[1]
});
});
} }
} }
// Sends a chat message // Sends a chat message
function sendMessage () { function sendMessage () {
// Don't allow sending if key is empty // Don't send unless other users exist
if (!encryptionKey.trim().length) return; if (users.length <= 1) return;
var vector = crypto.getRandomValues(new Uint8Array(16));
let message = $inputMessage.val(); let message = $inputMessage.val();
// Prevent markup from being injected into the message // Prevent markup from being injected into the message
@ -108,16 +137,63 @@ $(function() {
username: username, username: username,
message: message message: message
}); });
// tell server to execute 'new message' and send along one parameter let vector = crypto.getRandomValues(new Uint8Array(16));
createKey(encryptionKey)
let secretKey;
let secretKeys;
let messageData;
let signature;
// Generate new secret key and vector for each message
createSecretKey()
.then(function(key) { .then(function(key) {
return encryptData(message, key, vector); secretKey = key;
// Generate secretKey and encrypt with each user's public key
let promises = [];
_.each(users, function(user) {
// It not me
if (user.username !== window.username) {
let promise = new Promise(function(resolve, reject) {
let thisUser = user;
let exportedSecretKey;
exportKey(key, "raw")
.then(function(data) {
exportedSecretKey = data;
return encryptSecretKey(data, thisUser.publicPrimaryKey);
})
.then(function(encryptedSecretKey) {
var encData = new Uint8Array(encryptedSecretKey);
var str = convertArrayBufferViewToString(encData);
resolve({
id: thisUser.id,
secretKey: str
});
});
});
promises.push(promise);
}
});
return Promise.all(promises);
}) })
.then(function(data) { .then(function(data) {
var encryptedData = new Uint8Array(data); secretKeys = data;
messageData = convertStringToArrayBufferView(message);
return signKey(messageData, keys.signing.private)
})
.then(function(data) {
signature = data;
return encryptMessage(messageData, secretKey, vector)
})
.then(function(encryptedData) {
let msg = convertArrayBufferViewToString(new Uint8Array(encryptedData));
let vct = convertArrayBufferViewToString(new Uint8Array(vector));
let sig = convertArrayBufferViewToString(new Uint8Array(signature));
socket.emit('new message', { socket.emit('new message', {
message: convertArrayBufferViewtoString(encryptedData), message: msg,
vector: convertArrayBufferViewtoString(vector) vector: vct,
secretKeys: secretKeys,
signature: sig
}); });
}); });
} }
@ -264,15 +340,6 @@ $(function() {
typing = false; typing = false;
} }
// If enter is pressed on key input then close key modal
if (event.which === 13 && $('#join-modal input').is(':focus')) {
checkJoinKey();
}
// If enter is pressed on edit key input
if (event.which === 13 && $('#settings-modal .edit-key input.key').is(':focus')) {
saveKey();
}
}); });
$inputMessage.on('input propertychange paste change', function() { $inputMessage.on('input propertychange paste change', function() {
@ -285,11 +352,6 @@ $(function() {
} }
}); });
$genKey.click(function () {
let key = generatePassword();
updateKeyVal(key);
});
// Select message input when closing modal // Select message input when closing modal
$('.modal').on('hidden.bs.modal', function (e) { $('.modal').on('hidden.bs.modal', function (e) {
$inputMessage.focus(); $inputMessage.focus();
@ -312,30 +374,57 @@ $(function() {
return text; return text;
} }
// Socket events
// Whenever the server emits 'login', log the login message // Whenever the server emits 'login', log the login message
socket.on('login', function (data) { socket.on('user joined', function (data) {
connected = true; connected = true;
addParticipantsMessage(data); addParticipantsMessage(data);
users = data.users; let importKeysPromises = [];
// Import all user keys if not already there
_.each(data.users, function(user) {
if (!_.findWhere(users, {id: user.id})) {
let promise = new Promise(function(resolve, reject) {
let currentUser = user;
Promise.all([
importPrimaryKey(currentUser.publicPrimaryKey),
importSigningKey(currentUser.publicSigningKey)
])
.then(function(keys) {
users.push({
id: currentUser.id,
username: currentUser.username,
publicPrimaryKey: keys[0],
publicSigningKey: keys[1]
});
resolve();
});
});
importKeysPromises.push(promise);
}
});
let key = generatePassword(); if (!myUserId) {
// Set my id if not already set
let me = _.findWhere(data.users, {username: username});
myUserId = me.id;
}
if (data.numUsers > 1) { Promise.all(importKeysPromises)
$('#join-modal').modal('show'); .then(function() {
$('#join-modal').on('shown.bs.modal', function (e) { // All users' keys have been imported
$('#join-modal input').focus(); if (data.numUsers === 1) {
$('#first-modal').modal('show');
}
$('.modal').on('shown.bs.modal', function (e) {
autosize.update($('textarea.share-text'));
}); });
key = ''; log(data.username + ' joined');
}
updateKeyVal(key);
$('.modal').on('shown.bs.modal', function (e) { renderParticipantsList();
autosize.update($('textarea.share-text')); });
});
}); });
@ -349,44 +438,64 @@ $(function() {
beep.play(); beep.play();
} }
} }
var username = data.username;
createKey(encryptionKey) let message = data.message;
.then(function(key) { let messageData = convertStringToArrayBufferView(message);
var msg = convertStringToArrayBufferView(data.message); let username = data.username;
var vector = convertStringToArrayBufferView(data.vector); let senderId = data.id
return decryptData(msg, key, vector) let vector = data.vector;
let vectorData = convertStringToArrayBufferView(vector);
let secretKeys = data.secretKeys;
let decryptedMessageData;
let decryptedMessage;
let mySecretKey = _.find(secretKeys, function(key) {
return key.id === myUserId;
});
let signature = data.signature;
let signatureData = convertStringToArrayBufferView(signature);
let secretKeyArrayBuffer = convertStringToArrayBufferView(mySecretKey.secretKey);
decryptSecretKey(secretKeyArrayBuffer, keys.primary.private)
.then(function(data) {
return new Uint8Array(data);
}) })
.then(function(data) { .then(function(data) {
var decryptedData = new Uint8Array(data); return importSecretKey(data, "raw");
var msg = convertArrayBufferViewtoString(decryptedData);
addChatMessage({
username: username,
message: msg
});
}) })
.catch(function() { .then(function(data) {
let secretKey = data;
return decryptMessage(messageData, secretKey, vectorData);
})
.then(function(data) {
decryptedMessageData = data;
decryptedMessage = convertArrayBufferViewToString(new Uint8Array(data))
})
.then(function() {
// Find who sent msg (senderId), get their public key and verifyKey() with it and signature
let sender = _.find(users, function(user) {
return user.id === senderId;
});
let senderPublicVerifyKey = sender.publicSigningKey;
return verifyKey(signatureData, decryptedMessageData, senderPublicVerifyKey)
})
.then(function(bool) {
if (bool) {
addChatMessage({
username: username,
message: decryptedMessage
});
}
}); });
}); });
// Whenever the server emits 'user joined', log it in the chat body
socket.on('user joined', function (data) {
log(data.username + ' joined');
addParticipantsMessage(data);
users = data.users;
renderParticipantsList();
});
// Whenever the server emits 'user left', log it in the chat body // Whenever the server emits 'user left', log it in the chat body
socket.on('user left', function (data) { socket.on('user left', function (data) {
log(data.username + ' left'); log(data.username + ' left');
addParticipantsMessage(data); addParticipantsMessage(data);
removeChatTyping(data); removeChatTyping(data);
users = data.users; users = _.without(users, _.findWhere(users, {id: data.id}));
renderParticipantsList(); renderParticipantsList();
}); });
@ -401,11 +510,7 @@ $(function() {
removeChatTyping(data); removeChatTyping(data);
}); });
socket.on('first', function() { initChat();
$('#first-modal').modal('show');
});
setUsername();
window.onfocus = function () { window.onfocus = function () {
isActive = true; isActive = true;
@ -426,33 +531,8 @@ $(function() {
$('#about-modal').modal('show'); $('#about-modal').modal('show');
}); });
$('.room-url').text('https://darkwire.io' + roomId);
$('.room-id').text(roomId.replace('/', ''));
$('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip();
function joinKeyInputChanged(val) {
if (!val.trim().length) {
$('#join-modal .modal-footer button').attr('disabled', 'disabled');
} else {
$('#join-modal .modal-footer button').removeAttr('disabled');
}
}
$('#join-modal .key').on('input propertychange paste change', function() {
let val = $(this).val().trim();
joinKeyInputChanged(val);
});
$('#settings-modal input.key').on('input propertychange paste change', function() {
let val = $(this).val().trim();
if (val !== encryptionKey && val.length) {
$('#settings-modal #save-key-edit').removeAttr('disabled');
} else {
$('#settings-modal #save-key-edit').attr('disabled', 'disabled');
}
});
$('.navbar .participants').click(function() { $('.navbar .participants').click(function() {
renderParticipantsList(); renderParticipantsList();
$('#participants-modal').modal('show'); $('#participants-modal').modal('show');
@ -460,101 +540,29 @@ $(function() {
function renderParticipantsList() { function renderParticipantsList() {
$('#participants-modal ul.users').empty(); $('#participants-modal ul.users').empty();
_.each(users, function(username) { _.each(users, function(user) {
let li; let li;
if (username === window.username) { if (user.username === window.username) {
// User is me // User is me
li = $("<li>" + username + " <span class='you'>(you)</span></li>").css('color', getUsernameColor(username)); li = $("<li>" + user.username + " <span class='you'>(you)</span></li>").css('color', getUsernameColor(user.username));
} else { } else {
li = $("<li>" + username + "</li>").css('color', getUsernameColor(username)); li = $("<li>" + user.username + "</li>").css('color', getUsernameColor(user.username));
} }
$('#participants-modal ul.users') $('#participants-modal ul.users')
.append(li); .append(li);
}); });
} }
function updateKeyVal(val) {
$('.key').val(val);
$('.key').text(val);
encryptionKey = val;
$('textarea.share-text').val("Let's chat on darkwire.io at https://darkwire.io" + roomId + " using the passphrase " + encryptionKey);
autosize.update($('textarea.share-text'));
}
// Prevent closing join-modal
$('#join-modal').modal({
backdrop: 'static',
show: false,
keyboard: false
});
$('.read-key').click(function() {
$('.edit-key').show();
$('.edit-key input').focus();
$(this).hide();
});
$('.edit-key #cancel-key-edit').click(function() {
cancelSaveKey();
});
$('.edit-key #save-key-edit').click(function() {
saveKey();
});
function cancelSaveKey() {
$('.edit-key').hide();
$('.read-key').show();
updateKeyVal(encryptionKey);
}
function saveKey() {
let key = $('.edit-key input.key').val().trim();
if (!key.length) return;
$('.edit-key').hide();
$('.read-key').show();
updateKeyVal(key || encryptionKey);
}
$('#join-modal .modal-footer button').click(function() {
checkJoinKey();
});
function checkJoinKey() {
let key = $('#join-modal input').val().trim();
if (!key.length) return;
updateKeyVal(key);
$('#join-modal').modal('hide');
socket.emit('user joined');
}
$('#settings-modal').on('hide.bs.modal', function (e) {
cancelSaveKey();
});
$('#send-message-btn').click(function() { $('#send-message-btn').click(function() {
sendMessage(); sendMessage();
socket.emit('stop typing'); socket.emit('stop typing');
typing = false; typing = false;
}); });
function generatePassword() {
return uuid.v4();
}
$('.navbar-collapse ul li a').click(function() { $('.navbar-collapse ul li a').click(function() {
$('.navbar-toggle:visible').click(); $('.navbar-toggle:visible').click();
}); });
autosize($('textarea.share-text'));
$('textarea.share-text').click(function() {
$(this).focus();
$(this).select();
this.setSelectionRange(0, 9999);
});
$('input.bs-switch').bootstrapSwitch(); $('input.bs-switch').bootstrapSwitch();
$('input.bs-switch').on('switchChange.bootstrapSwitch', function(event, state) { $('input.bs-switch').on('switchChange.bootstrapSwitch', function(event, state) {
@ -570,7 +578,7 @@ $(function() {
return bytes; return bytes;
} }
function convertArrayBufferViewtoString(buffer) { function convertArrayBufferViewToString(buffer) {
var str = ""; var str = "";
for (var i = 0; i < buffer.byteLength; i++) { for (var i = 0; i < buffer.byteLength; i++) {
str += String.fromCharCode(buffer[i]); str += String.fromCharCode(buffer[i]);
@ -579,29 +587,161 @@ $(function() {
return str; return str;
} }
function createKey(password) { function createSigningKeys() {
return cryptoSubtle.digest({ return crypto.subtle.generateKey(
name: "SHA-256" {
}, convertStringToArrayBufferView(password)) name: "RSASSA-PKCS1-v1_5",
.then(function(result) { modulusLength: 2048, //can be 1024, 2048, or 4096
return cryptoSubtle.importKey("raw", result, { publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
name: "AES-CBC" hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
}, false, ["encrypt", "decrypt"]); },
}); true, //whether the key is extractable (i.e. can be used in exportKey)
["sign", "verify"] //can be any combination of "sign" and "verify"
);
} }
function encryptData(data, key, vector) { function createPrimaryKeys() {
return cryptoSubtle.encrypt({ return crypto.subtle.generateKey(
name: "AES-CBC", {
iv: vector name: "RSA-OAEP",
}, key, convertStringToArrayBufferView(data)); 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 decryptData(data, key, vector) { function createSecretKey() {
return cryptoSubtle.decrypt({ return crypto.subtle.generateKey(
name: "AES-CBC", {
iv: vector name: "AES-CBC",
}, key, data); length: 256, //can be 128, 192, or 256
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt", "decrypt", "wrapKey", "unwrapKey"] //can be "encrypt", "decrypt", "wrapKey", or "unwrapKey"
);
}
function encryptSecretKey(data, secretKey) {
// Secret key will be recipient's public key
return crypto.subtle.encrypt(
{
name: "RSA-OAEP"
},
secretKey,
data //ArrayBuffer of data you want to encrypt
);
}
function decryptSecretKey(data, key) {
// key will be my private key
return crypto.subtle.decrypt(
{
name: "RSA-OAEP",
//label: Uint8Array([...]) //optional
},
key,
data //ArrayBuffer of the data
);
}
function encryptMessage(data, secretKey, iv) {
return 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 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 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) {
// Will be someone's public key
return crypto.subtle.importKey(
"jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
jwkData,
{ //these are the algorithm options
name: "RSA-OAEP",
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"] //"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 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 crypto.subtle.importKey(
"jwk", //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: "RSASSA-PKCS1-v1_5",
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)
["verify"] //"verify" for public key import, "sign" for private key imports
);
}
function signKey(data, keyToSignWith) {
// Will use my private key
return crypto.subtle.sign(
{
name: "RSASSA-PKCS1-v1_5"
},
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 crypto.subtle.verify(
{
name: "RSASSA-PKCS1-v1_5"
},
keyToVerifyWith, //from generateKey or importKey above
signature, //ArrayBuffer of the signature
data //ArrayBuffer of the data
);
} }
}); });

View File

@ -1,6 +1,7 @@
import _ from 'underscore'; import _ from 'underscore';
import {EventEmitter} from 'events'; import {EventEmitter} from 'events';
import util from 'util'; import util from 'util';
import uuid from 'uuid';
class Room { class Room {
constructor(io = {}, id = {}) { constructor(io = {}, id = {}) {
@ -19,39 +20,34 @@ class Room {
// we tell the client to execute 'new message' // we tell the client to execute 'new message'
socket.broadcast.emit('new message', { socket.broadcast.emit('new message', {
username: socket.username, username: socket.username,
id: socket.user.id,
message: data.message, message: data.message,
vector: data.vector vector: data.vector,
secretKeys: data.secretKeys,
signature: data.signature
}); });
}); });
socket.on('add user', (username) => { socket.on('add user', (data) => {
if (addedUser) return; if (addedUser) return;
if (this.numUsers === 0) { data.id = uuid.v4();
socket.emit('first'); this.users.push(data);
}
this.users.push(username);
// we store the username in the socket session for this client // we store the username in the socket session for this client
socket.username = username; socket.username = data.username;
socket.user = data;
++this.numUsers; ++this.numUsers;
addedUser = true; addedUser = true;
socket.emit('login', {
// Broadcast to ALL sockets, including this one
thisIO.emit('user joined', {
username: socket.username,
numUsers: this.numUsers, numUsers: this.numUsers,
users: this.users users: this.users
}); });
}); });
socket.on('user joined', () => {
// echo globally (all clients) that a person has connected
socket.broadcast.emit('user joined', {
username: socket.username,
numUsers: this.numUsers,
users: this.users
});
});
// when the client emits 'typing', we broadcast it to others // when the client emits 'typing', we broadcast it to others
socket.on('typing', () => { socket.on('typing', () => {
socket.broadcast.emit('typing', { socket.broadcast.emit('typing', {
@ -70,22 +66,20 @@ class Room {
socket.on('disconnect', () => { socket.on('disconnect', () => {
if (addedUser) { if (addedUser) {
--this.numUsers; --this.numUsers;
this.users = _.without(this.users, socket.user);
this.users = _.without(this.users, socket.username);
// echo globally that this client has left // echo globally that this client has left
socket.broadcast.emit('user left', { socket.broadcast.emit('user left', {
username: socket.username, username: socket.username,
numUsers: this.numUsers, numUsers: this.numUsers,
users: this.users users: this.users,
id: socket.user.id
}); });
// remove room from rooms array // remove room from rooms array
if (this.numUsers === 0) { if (this.numUsers === 0) {
this.emit('empty'); this.emit('empty');
} }
this.users = _.without(this.users, socket.username);
} }
}); });
}); });

View File

@ -3,8 +3,8 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>darkwire.io - anonymous, encrypted chat</title> <title>darkwire.io - encrypted web chat</title>
<meta name="description" content="darkwire.io is the simplest way to chat online anonymously."> <meta name="description" content="darkwire.io is the simplest way to chat with encryption online.">
<link rel="shortcut icon" type="image/png" href="favicon.ico"> <link rel="shortcut icon" type="image/png" href="favicon.ico">
<link rel="stylesheet" href="/vendor/bootstrap-switch.min.css"> <link rel="stylesheet" href="/vendor/bootstrap-switch.min.css">
<link rel="stylesheet" href="/vendor/bootstrap.min.css"> <link rel="stylesheet" href="/vendor/bootstrap.min.css">
@ -50,7 +50,7 @@
<div class="chatArea"> <div class="chatArea">
<ul class="messages"> <ul class="messages">
<li class="log"> <li class="log">
<p>Welcome to darkwire.io - anonymous, encrypted chat</p> <p>Welcome to darkwire.io - encrypted chat</p>
</li> </li>
</ul> </ul>
</div> </div>
@ -69,11 +69,9 @@
<h4 class="modal-title">About</h4> <h4 class="modal-title">About</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p class="bold">WARNING: This product is in beta and its source code has not been peer-reviewed or undergone a security audit. This is a demo only and not intended for security-critical use.</p> <p class="bold">WARNING: This product is in beta and its source code has not been peer-reviewed or undergone a security audit. This is a demo only and not intended for security-critical use. <a href="https://github.com/seripap/darkwire.io" target="_blank">View source code</a>.</p>
<p>darkwire.io is the simplest way to chat anonymously, and with encryption, online. Chat history is never stored on a server or database, and plain text messages are never transferred over the wire.</p> <p>darkwire.io is the simplest way to chat with encryption online. Chat history is never stored on a server or database, and plain text messages are never transferred over the wire.</p>
<p>Your encryption passphrase ensures that only people you trust can decipher your messages. If you change your passphrase, make sure to notify all other participants.</p>
<p>Questions/comments? Email us at hello[at]darkwire.io</p> <p>Questions/comments? Email us at hello[at]darkwire.io</p>
</div> </div>
@ -97,20 +95,6 @@
<textarea class="form-control share-text" rows="3" readonly id="settings-share-text" onclick="this.select()"></textarea> <textarea class="form-control share-text" rows="3" readonly id="settings-share-text" onclick="this.select()"></textarea>
</p> </p>
<br> <br>
<h6>Edit Your Passphrase</h6>
<div class="read-key">
<span class="key" id="read-key"></span>&nbsp;<span class="glyphicon glyphicon-pencil"></span>
</div>
<div class="edit-key">
<div class="input-group">
<input class="form-control key" placeholder="Enter passphrase here" type="text" id="settings-key"></input>
<div class="input-group-btn">
<button class="btn btn-default" type="button" id='cancel-key-edit'>Cancel</button>
<button class="btn btn-primary" type="button" id='save-key-edit' disabled="disabled">Save</button>
</div>
</div>
</div>
<br>
<h6>Sound</h6> <h6>Sound</h6>
<input type="checkbox" name="my-checkbox" class="form-control bs-switch" checked> <input type="checkbox" name="my-checkbox" class="form-control bs-switch" checked>
</div> </div>
@ -129,8 +113,8 @@
<h4 class="modal-title">Welcome to darkwire.io</h4> <h4 class="modal-title">Welcome to darkwire.io</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<h6>We've placed you in a new chat room</h6>
<p class="bold">WARNING: This product is in beta and its source code has not been peer-reviewed or undergone a security audit. This is a demo only and not intended for security-critical use. <a href="https://github.com/seripap/darkwire.io" target="_blank">View source code</a>.</p> <p class="bold">WARNING: This product is in beta and its source code has not been peer-reviewed or undergone a security audit. This is a demo only and not intended for security-critical use. <a href="https://github.com/seripap/darkwire.io" target="_blank">View source code</a>.</p>
<p>We've placed you in a new chat room.</p>
<br> <br>
<h6>Invite People to This Room</h6> <h6>Invite People to This Room</h6>
<p> <p>
@ -144,25 +128,6 @@
</div> </div>
</div> </div>
<div class="modal fade" id="join-modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Welcome to darkwire.io</h4>
</div>
<div class="modal-body">
<h6>Enter Your Passphrase Below</h6>
<input class="form-control key" placeholder="Enter passphrase here" type="text" id='join-key'></input>
<br>
<p class="bold">WARNING: This product is in beta and its source code has not been peer-reviewed or undergone a security audit. This is a demo only and not intended for security-critical use.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary encryption-active" disabled="disabled" data-dismiss="modal">Done</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="participants-modal" tabindex="-1" role="dialog"> <div class="modal fade" id="participants-modal" tabindex="-1" role="dialog">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">