<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>FairEmail - Decrypt</title> <link rel="shortcut icon" href="https://raw.githubusercontent.com/M66B/FairEmail/master/app/src/main/ic_launcher-web.png"> <meta name="theme-color" content="#006db3"> <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"> <meta name="description" content="Decrypt password protected content"> <meta name="author" content="M66B"> <meta name="robots" content="noindex"> <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#sources --> <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; img-src data: https://raw.githubusercontent.com/M66B/FairEmail/master/app/src/main/ic_launcher-web.png;"> <style> body { padding-left: 10px; padding-right: 10px; font-family: Arial, Helvetica, sans-serif; } .button { display: inline-block; text-align: center; cursor: pointer; } .button span:first-child { font-size: x-large; } .button span:last-child { font-size: smaller; } </style> <style> .noscript { display: none; } </style> <noscript> <style> .noscript { display: block; } </style> </noscript> <!-- https://github.com/cure53/DOMPurify 2.4.0 --> <script src="purify.min.js"></script> <script> window.addEventListener('load', load); function load() { const form = document.getElementById('form') const message = document.getElementById('message'); const copy = document.getElementById('copy'); const email = document.getElementById('email'); const close = document.getElementById('close'); const error = document.getElementById('error'); const details = document.getElementById('details'); const year = document.getElementById('year'); form.addEventListener('submit', submit); if (window.location.hash) if (crypto.subtle && typeof Uint8Array === 'function' && typeof TextEncoder === 'function') { form.style.display = 'block'; form.password.focus(); } else { error.textContent = 'Your browser is unsuitable for decrypting content'; error.style.display = 'block'; details.innerHTML = 'crypto.subtle: ' + (crypto.subtle ? 'Yes' : 'No') + '<br>' + 'Uint8Array: ' + (Uint8Array ? 'Yes' : 'No') + '<br>' + 'TextEncoder: ' + (TextEncoder ? 'Yes' : 'No') + '<br>'; details.style.display = 'block'; } else { error.textContent = 'Nothing to see here'; error.style.display = 'block'; } copy.onclick = function (event) { event.preventDefault(); const blob = new Blob([message.innerHTML], { type: 'text/html' }); const clip = new ClipboardItem({ 'text/html': blob }); navigator.clipboard.write([clip]).then(function() { alert('Copied to clipboard'); }, function() { alert('Copy failed'); }); } email.onclick = function (event) { event.preventDefault(); window.location.href = "mailto:?body=" + encodeURIComponent(message.textContent); } close.onclick = function (event) { event.preventDefault(); form.fields.disabled = false; form.style.display = 'block'; content.style.display = 'none'; message.innerHTML = ''; form.password.focus(); } year.textContent = new Date().getFullYear(); } function submit(event) { event.preventDefault(); decrypt(); } async function decrypt() { const form = document.getElementById('form') const content = document.getElementById('content'); const message = document.getElementById('message'); const error = document.getElementById('error'); const details = document.getElementById('details'); try { form.fields.disabled = true; content.style.display = 'none'; error.style.display = 'none'; details.style.display = 'none'; if (!form.password.value) throw new Error('Password required'); const dirty = await _decrypt(form.password.value); const clean = DOMPurify.sanitize(dirty, { USE_PROFILES: { html: true }, SANITIZE_NAMED_PROPS: true }); form.password.value = ''; message.innerHTML = clean; var a = message.getElementsByTagName('a'); for (let i = 0; i < a.length; i++) if (a[i].href) { a[i].rel = 'noopener noreferrer'; a[i].setAttribute('target', '_blank'); a[i].onclick = function() { return confirm('Go to ' + a[i].href + ' ?'); }; } form.style.display = 'none'; content.style.display = 'block'; } catch (e) { console.log("%O", e); form.fields.disabled = false; form.password.value = ''; form.password.focus(); error.textContent = 'Could not decrypt the message. Is the password correct?'; error.style.display = 'block'; details.textContent = e.toString(); details.style.display = 'block'; } } async function _decrypt(password) { const msg = atob(window.location.hash.substr(1).replaceAll('-', '+').replaceAll('_', '/')); const buf = new Uint8Array(msg.length); for (let i = 0; i < msg.length; i++) buf[i] = msg.charCodeAt(i); const version = buf[0]; const salt = buf.slice(1, 1 + 16); const iv = buf.slice(1 + 16, 1 + 16 + 12); const e = buf.slice(1 + 16 + 12, buf.length); // https://developer.mozilla.org/en-US/docs/Web/API/Crypto/subtle // https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder const passwordBuffer = new TextEncoder('UTF-8').encode(password); const importedKey = await crypto.subtle.importKey('raw', passwordBuffer, 'PBKDF2', false, ['deriveBits']); const derivation = await crypto.subtle.deriveBits({name: 'PBKDF2', hash: 'SHA-512', salt: salt, iterations: 120000}, importedKey, 256); const importedEncryptionKey = await crypto.subtle.importKey('raw', derivation, {name: 'AES-GCM'}, false, ['decrypt']); const decrypted = await crypto.subtle.decrypt({name: 'AES-GCM', iv: iv, tagLength: 128}, importedEncryptionKey, e); return new TextDecoder('UTF-8').decode(decrypted); } </script> </head> <body> <p class="noscript" style="color: red; font-weight: bold;">Please enable JavaScript</p> <form id="form" action="#" method="GET" style="display: none;"> <p> Someone sent you password protected content with <a href="https://email.faircode.eu/" target="_blank">FairEmail</a>. </p> <p> The sender should have provided the password. </p> <hr> <fieldset id="fields" style="border:0 none; margin: 0; padding: 0;"> <p> <label for="password">Enter password 🔑</label><br> <input id="password" name="password" type="password" required><br> <span style="font-size: smaller;">Passwords are case-sensitive </span> </p> <p> <input id="submit" style="padding: 3px;" type="submit" value="🔓 Decrypt"> </p> </fieldset> <p style="font-size: smaller;"> Passwords, encrypted, and decrypted content stay in your own browser. See <a href="https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq184" target="_blank">here</a> for more information. </p> <hr> </form> <div id="content" style="display: none; width: 100%;"> <p>The sender sent you this password protected content with <a href="https://email.faircode.eu/" target="_blank">FairEmail</a>:</p> <hr style="margin-top: 30px;"> <p id="message" style="width: 100%; font-size: larger;"></p> <hr style="margin-bottom: 30px;"> <div> <div id="copy" class="button"> <span>📋</span><br> <span>Copy</span> </div>   <div id="email" class="button"> <span>📧</span><br> <span>Email</span> </div>   <div id="close" class="button"> <span>✕</span><br> <span>Close</span> </div> </div> </div> <p id="error" style="color: red; font-weight: bold; display: none;"></p> <p id="details" style="font-size: x-small; display: none;"></p> <p style="padding-top: 30px;"> Copyright © 2018–<span id="year">2022</span> by Marcel Bokhorst (M66B) <br> <br> <span style="font-size: smaller;">This page is <a href="https://github.com/M66B/FairEmail/blob/master/decrypt/index.html" target="_blank">open source</a>.</span> </p> </body> </html>