Skip to content

Commit

Permalink
Prettify E2E password prompt
Browse files Browse the repository at this point in the history
  • Loading branch information
bilde2910 committed Nov 27, 2019
1 parent 9357f82 commit 6dd1404
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 43 deletions.
8 changes: 6 additions & 2 deletions frontend/assets/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
"page_title": "Hauk",
"expired_head": "Location expired",
"expired_body": "The shared location you tried to access was not found on the server. If this link worked before, the share might have expired.",
"e2e_password_prompt": "This share is protected by end-to-end encryption. Please enter the encryption password to access the share:",
"e2e_incorrect": "The encryption password you entered was wrong. Please try again:",
"e2e_title": "End-to-end encryption",
"e2e_placeholder": "Encryption password",
"e2e_password_prompt": "This share is protected by end-to-end encryption. Please enter the encryption password to access the share.",
"e2e_incorrect": "The encryption password you entered was wrong. Please try again.",
"e2e_unavailable_secure": "This share is protected by end-to-end encryption. Decryption is currently unavailable because you are not using HTTPS. Please ensure you are using HTTPS, then try again.",
"e2e_unsupported": "This share is protected by end-to-end encryption. Your browser does not appear to support the cryptographic functions required to decrypt such shares. Please try again with another web browser.",
"gnss_signal_head": "Please wait",
Expand All @@ -16,6 +18,8 @@
"status_expired": "Expired",
"status_offline": "Offline",
"btn_dismiss": "Dismiss",
"btn_cancel": "Cancel",
"btn_decrypt": "Decrypt",
"f_droid_badge_url": "https://fdroid.gitlab.io/artwork/badge/get-it-on.png",
"google_play_badge_url": "https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"
}
20 changes: 16 additions & 4 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,11 @@
</a></p>
</div>

<div id="expired" class="dialog hidden">
<div id="message-popup" class="dialog hidden">
<div>
<p class="header" data-i18n="dialog_expired_head"></p>
<p class="body" data-i18n="dialog_expired_body"></p>
<p class="button"><input type="button" id="dismiss-expired" data-i18n-attr="value" data-i18n="btn_dismiss"></p>
<p class="header" id="message-title"></p>
<p class="body" id="message-body"></p>
<p class="button"><input type="button" id="dismiss-message" data-i18n-attr="value" data-i18n="btn_dismiss"></p>
</div>
</div>

Expand All @@ -97,6 +97,18 @@
</div>
</div>

<div id="e2e-prompt" class="dialog hidden">
<div>
<p class="header" data-i18n="e2e_title"></p>
<p class="body" id="e2e-password-label" data-i18n="e2e_password_prompt"></p>
<p class="body"><input type="password" id="e2e-password" data-i18n-attr="placeholder" data-i18n="e2e_placeholder"></p>
<p class="button">
<input type="button" id="cancel-e2e-password" data-i18n-attr="value" data-i18n="btn_cancel">
<input type="button" id="decrypt-e2e-password" data-i18n-attr="value" data-i18n="btn_decrypt">
</p>
</div>
</div>

<script src="./main.js" charset="UTF-8"></script>
</body>
</html>
120 changes: 83 additions & 37 deletions frontend/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,14 +246,25 @@ function getJSON(url, callback, invalid) {
xhr.send();
}

var dismissExpiredE = document.getElementById("dismiss-expired");
if (dismissExpiredE !== null) {
dismissExpiredE.addEventListener("click", function() {
var expiredE = document.getElementById("expired");
if (expiredE !== null) expiredE.style.display = "none";
// General message popup box. Reused for several popups.
var dismissMessageE = document.getElementById("dismiss-message");
if (dismissMessageE !== null) {
dismissMessageE.addEventListener("click", function() {
var messageE = document.getElementById("message-popup");
if (messageE !== null) messageE.style.display = "none";
});
}

// Shows a dialog box with a title and message.
function showMessage(title, message) {
var messageE = document.getElementById("message-popup");
var titleE = document.getElementById("message-title");
var bodyE = document.getElementById("message-body");
if (titleE !== null) titleE.textContent = title;
if (bodyE !== null) bodyE.textContent = message;
if (messageE !== null) messageE.style.display = "block";
}

var dismissOfflineE = document.getElementById("dismiss-offline");
if (dismissOfflineE !== null) {
dismissOfflineE.addEventListener("click", function() {
Expand All @@ -262,12 +273,33 @@ if (dismissOfflineE !== null) {
});
}

// End-to-end encryption password prompt handlers.
var passwordInputE = document.getElementById("e2e-password");
var passwordDecryptE = document.getElementById("decrypt-e2e-password");
if (passwordInputE !== null) {
passwordInputE.addEventListener("keyup", function(e) {
if (e.keyCode == 13) {
if (passwordDecryptE !== null) {
passwordDecryptE.click();
}
}
});
}

var passwordCancelE = document.getElementById("cancel-e2e-password");
if (passwordCancelE !== null) {
passwordCancelE.addEventListener("click", function() {
var promptE = document.getElementById("e2e-prompt");
if (promptE !== null) promptE.style.display = "none";
if (passwordDecryptE !== null && acceptKeyFunc !== null) passwordDecryptE.removeEventListener("click", acceptKeyFunc);
});
}

var fetchIntv;
var countIntv;

function setNewInterval(expire, interval) {
var countdownE = document.getElementById("countdown");
var expiredE = document.getElementById("expired");

// The data contains an expiration time. Create a countdown at the top of
// the map screen that ends when the share is over.
Expand Down Expand Up @@ -300,7 +332,7 @@ function setNewInterval(expire, interval) {
clearInterval(fetchIntv);
clearInterval(countIntv);
if (countdownE !== null) countdownE.textContent = LANG["status_expired"];
if (expiredE !== null) expiredE.style.display = "block";
showMessage(LANG["dialog_expired_head"], LANG["dialog_expired_body"]);
}

getJSON("./api/fetch.php?id=" + id, function(data) {
Expand All @@ -317,7 +349,7 @@ function setNewInterval(expire, interval) {
clearInterval(fetchIntv);
clearInterval(countIntv);
if (countdownE !== null) countdownE.textContent = LANG["status_expired"];
if (expiredE !== null) expiredE.style.display = "block";
showMessage(LANG["dialog_expired_head"], LANG["dialog_expired_body"]);
});
}, interval * 1000);
}
Expand All @@ -336,9 +368,8 @@ var following = null;
// The decryption key for end-to-end encrypted shares.
var aesKey = null;

// Whether or not the user has already entered an incorrect encryption password
// at least once.
var hasEnteredPass = false;
// Button handler for the "Decrypt" button on the E2E password prompt.
var acceptKeyFunc = null;

// Converts a base64-encoded string to a Uint8Array ArrayBuffer for use with
// WebCrypto.
Expand Down Expand Up @@ -366,43 +397,58 @@ function processUpdate(data, init) {

// Check for crypto support if necessary.
if (data.encrypted && !("crypto" in window)) {
alert(LANG["e2e_unsupported"]);
showMessage(LANG["e2e_title"], LANG["e2e_unsupported"]);
return;
} else if (data.encrypted && !("subtle" in window.crypto)) {
if (!window.isSecureContext) {
alert(LANG["e2e_unavailable_secure"]);
showMessage(LANG["e2e_title"], LANG["e2e_unavailable_secure"]);
} else {
alert(LANG["e2e_unsupported"]);
showMessage(LANG["e2e_title"], LANG["e2e_unsupported"]);
}
return;
}

if (data.encrypted && aesKey == null) {
// If using end-to-end encryption, we need to decrypt the data. We have
// not obtained an AES key yet, so prompt the user for it.
var password = prompt(hasEnteredPass ? LANG["e2e_incorrect"] : LANG["e2e_password_prompt"]);
if (password == null) return;
hasEnteredPass = true;

// Get the salt in binary format.
var salt = byteArray(data.salt);

// Derive the encryption key using PBKDF2 with SHA-1. SHA-1 was chosen
// because of availability in Android.
crypto.subtle
.importKey("raw", new TextEncoder("utf-8").encode(password), "PBKDF2", false, ["deriveKey"])
.then(key => crypto.subtle.deriveKey(
{name: "PBKDF2", salt: salt, iterations: 65536, hash: "SHA-1"},
key,
{name: "AES-CBC", length: 256},
false,
["decrypt"]
))
.then(key => {
// Store the crypto key and re-process the update.
aesKey = key;
processUpdate(data, init);
});
var promptE = document.getElementById("e2e-prompt");
var labelE = document.getElementById("e2e-password-label");
if (promptE !== null && passwordInputE !== null && passwordDecryptE !== null && labelE !== null) {
acceptKeyFunc = function() {
// Remove the event listener, hide the dialog and fetch the
// password.
passwordDecryptE.removeEventListener("click", acceptKeyFunc);
promptE.style.display = "none";
labelE.textContent = LANG["e2e_incorrect"];
var password = passwordInputE.value;

// Get the salt in binary format.
var salt = byteArray(data.salt);

// Derive the encryption key using PBKDF2 with SHA-1. SHA-1 was chosen
// because of availability in Android.
crypto.subtle
.importKey("raw", new TextEncoder("utf-8").encode(password), "PBKDF2", false, ["deriveKey"])
.then(key => crypto.subtle.deriveKey(
{name: "PBKDF2", salt: salt, iterations: 65536, hash: "SHA-1"},
key,
{name: "AES-CBC", length: 256},
false,
["decrypt"]
))
.then(key => {
// Store the crypto key and re-process the update.
aesKey = key;
processUpdate(data, init);
});
};

// Attach the listener to the dialog box and show it.
passwordDecryptE.addEventListener("click", acceptKeyFunc);
passwordInputE.value = "";
promptE.style.display = "block";
passwordInputE.focus();
}

return;

Expand Down
5 changes: 5 additions & 0 deletions frontend/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ body {
font-size: 1em;
}

/* Ensure the password prompt fills the dialog box. */
.dialog input[type=password] {
width: calc(100% - 20px);
}

/* Visible on the root page of Hauk. */
#url {
color: #d80037;
Expand Down

0 comments on commit 6dd1404

Please sign in to comment.