diff options
Diffstat (limited to 'sjcl-front.js')
| -rw-r--r-- | sjcl-front.js | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/sjcl-front.js b/sjcl-front.js new file mode 100644 index 0000000..1a67d64 --- /dev/null +++ b/sjcl-front.js | |||
| @@ -0,0 +1,264 @@ | |||
| 1 | function inject_css() { | ||
| 2 | var s = document.createElement('style'); | ||
| 3 | s.setAttribute('type', 'text/css'); | ||
| 4 | s.appendChild(document.createTextNode("\ | ||
| 5 | @font-face { \n\ | ||
| 6 | font-family: 'fontello'; src: url('data:application/octet-stream;base64,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') format('woff'); } \n\ | ||
| 7 | .sjcl-undecrypted { background-color: rgba(255, 0, 0, 0.2); cursor: pointer; } \n\ | ||
| 8 | .sjcl-decrypted { background-color: rgba(0, 255, 0, 0.2); } \n\ | ||
| 9 | .sjcl-hook { display: inline-block; position: relative; vertical-align: top; } \n\ | ||
| 10 | .sjcl-hook:before, .sjcl-decrypted:before, .sjcl-undecrypted:before { content: '\\e801 '; font-family: 'fontello'; opacity: 0.5; font-size: 1.1em; padding: 0 0.3em 0 0.3em; margin-right: 0.2em; box-shadow: 0 0 0.3em currentColor; border-radius: 50%; } \n\ | ||
| 11 | .sjcl-dropdown, .sjcl-addkey-sub { display: none; border-radius: 0.4em; } \n\ | ||
| 12 | .sjcl-hook:hover .sjcl-dropdown { display: block; position: absolute; min-width: 300px; background-color: #f9f9f9; box-shadow: 0px 1em 2em 0 rgba(0,0,0,0.2); color: black; } \n\ | ||
| 13 | .sjcl-decrypt, .sjcl-usekey, .sjcl-addkey { padding: 2px 3px 2px 6px; cursor: pointer; border-radius: 0 0 0.4em 0.4em; } \n\ | ||
| 14 | .sjcl-decrypt:hover, .sjcl-usekey:hover, .sjcl-addkey:hover { border-radius: 0.4em; background-color: #f1f1f1; } \n\ | ||
| 15 | .sjcl-addkey:hover .sjcl-addkey-sub { display: block; } \n\ | ||
| 16 | .sjcl-addkey-sub label { display: inline-block !important; width: 40%; } \n\ | ||
| 17 | .sjcl-addkey-button, .sjcl-delkey-button { display: inline-block; text-align: center; margin-right: 6px; width: 40%; border-radius: 0.4em; border: solid 0.1em lime; } \n\ | ||
| 18 | .sjcl-delkey-button { border: solid 0.1em red; } \n\ | ||
| 19 | .sjcl-addkey-button:hover { background-color: lime; } \n\ | ||
| 20 | .sjcl-delkey-button:hover { background-color: red; } \ | ||
| 21 | ")); | ||
| 22 | document.getElementsByTagName('head')[0].appendChild(s); | ||
| 23 | } | ||
| 24 | |||
| 25 | function inject_textarea_hook() { | ||
| 26 | // Get rid of old hooks | ||
| 27 | var hooks = document.getElementsByClassName("sjcl-hook"); | ||
| 28 | for(var i = hooks.length - 1; i >= 0; i--) { | ||
| 29 | var n = hooks.item(i); | ||
| 30 | n.parentNode.removeChild(n); | ||
| 31 | } | ||
| 32 | // And insert new hooks | ||
| 33 | var walk=document.createTreeWalker(document.body,NodeFilter.SHOW_ELEMENT,null,false); | ||
| 34 | while(n = walk.nextNode()) | ||
| 35 | if (n.type == 'textarea') { | ||
| 36 | var keys = list_keys(); | ||
| 37 | var h = document.createElement('div'); | ||
| 38 | h.className='sjcl-hook'; | ||
| 39 | var dd = '<div class="sjcl-dropdown">' | ||
| 40 | for (var i in keys) | ||
| 41 | dd += '<div class="sjcl-usekey" keyname="'+keys[i]+'">Encrypt with: "'+keys[i]+'"</div>'; | ||
| 42 | h.innerHTML = dd + '<div class="sjcl-decrypt">Decrypt</div><hr/><div class="sjcl-addkey">Manage keys<div class="sjcl-addkey-sub"><label>Name:</label><input class="sjcl-addkey-name" type="text"/><label>Key:</label><input class="sjcl-addkey-key" type="password"/><div class="sjcl-addkey-button">add!</div><div class="sjcl-delkey-button">delete!</div></div></div></div>'; | ||
| 43 | n.parentNode.insertBefore(h,n.nextSibling); | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | function decrypt_string(text, target_node, do_prompt) { | ||
| 48 | var rp, ct, plain, key_name, key, do_store = false; | ||
| 49 | |||
| 50 | /* Clear sjcl classes from target node */ | ||
| 51 | target_node.className = target_node.className.replace(/(^| )sjcl-[a-z]+($| )/, ''); | ||
| 52 | |||
| 53 | /* Check sjcl signature and bail out */ | ||
| 54 | if (text.substring(0,7) != 'sjcl://') | ||
| 55 | throw 'Text is not encrypted.'; | ||
| 56 | |||
| 57 | /* Skip signature */ | ||
| 58 | text = text.substring(7); | ||
| 59 | /* Retrieve key name */ | ||
| 60 | try { | ||
| 61 | ct = JSON.parse(text); | ||
| 62 | } catch (e) { | ||
| 63 | target_node.className += ' sjcl-garbled'; | ||
| 64 | throw 'Cipher text is garbled.'; | ||
| 65 | } | ||
| 66 | try { | ||
| 67 | key_name = sjcl.codec.utf8String.fromBits(sjcl.codec.base64.toBits(ct.adata)); | ||
| 68 | } catch(e) { | ||
| 69 | target_node.className += ' sjcl-garbled'; | ||
| 70 | throw 'Can not extract key name'; | ||
| 71 | } | ||
| 72 | |||
| 73 | key = retrieve_key(key_name); | ||
| 74 | if (!key && do_prompt) { | ||
| 75 | key = prompt( 'Enter password for the key ' + key_name); | ||
| 76 | do_store = true; | ||
| 77 | } | ||
| 78 | if (!key) { | ||
| 79 | target_node.className += ' sjcl-undecrypted'; | ||
| 80 | throw 'You need the key "' + key_name + '" to decrypt.'; | ||
| 81 | } | ||
| 82 | try { | ||
| 83 | plain = sjcl.decrypt(key, text, {}, rp); | ||
| 84 | } catch (e) { | ||
| 85 | if (do_store) | ||
| 86 | alert('Could not decrypt'); | ||
| 87 | target_node.className += ' sjcl-undecrytable'; | ||
| 88 | throw 'Your key "' + key_name + '" can not decrypt.'; | ||
| 89 | } | ||
| 90 | if (do_store) | ||
| 91 | store_key(key_name, key); | ||
| 92 | |||
| 93 | target_node.className += ' sjcl-decrypted'; | ||
| 94 | return plain.replace(/\s+$/gm,''); | ||
| 95 | } | ||
| 96 | |||
| 97 | function decrypt_element(n) { | ||
| 98 | try { | ||
| 99 | n.data = decrypt_string(n.data, n.parentElement, false); | ||
| 100 | } catch(e) { | ||
| 101 | n.parentElement.ciphertext = n.data; | ||
| 102 | n.parentElement.ciphertext_node = n; | ||
| 103 | n.data = e; | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | function decrypt_undecrypted(n) { | ||
| 108 | if(!n.ciphertext || n.ciphertext_node==null) | ||
| 109 | return; | ||
| 110 | try { | ||
| 111 | n.ciphertext_node.data = decrypt_string(n.ciphertext, n, true); | ||
| 112 | inject_textarea_hook(); | ||
| 113 | find_undecrypted_nodes(); | ||
| 114 | } catch(e) { | ||
| 115 | n.ciphertext_node.data = e; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | function decrypt_textarea(n) { | ||
| 120 | try { | ||
| 121 | n.value = decrypt_string(n.value, n, false); | ||
| 122 | } catch(e) { | ||
| 123 | alert(e); | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | function encrypt_textarea(n,name,key) { | ||
| 128 | var t = n.value + (" ".repeat(17 + Math.floor((Math.random() * 64)))); | ||
| 129 | var p = {adata:name, iter:1000, mode:'ccm', ts:128, ks:256, iter: 1000 }; | ||
| 130 | n.value = 'sjcl://'+sjcl.encrypt(key, t || '', p); | ||
| 131 | n.className = n.className.replace(/(^| )sjcl-[a-z]+($| )/, '')+' sjcl-undecrypted'; | ||
| 132 | } | ||
| 133 | |||
| 134 | /* Auto node operation */ | ||
| 135 | function find_encrypted_nodes() { | ||
| 136 | var n, walk = document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT,null,false); | ||
| 137 | while(n = walk.nextNode()) | ||
| 138 | if (n.data.substring(0,7) == 'sjcl://') | ||
| 139 | decrypt_element(n, false); | ||
| 140 | } | ||
| 141 | |||
| 142 | function find_undecrypted_nodes() { | ||
| 143 | var undec = document.getElementsByClassName("sjcl-undecrypted"); | ||
| 144 | for(var i = 0; i < undec.length; i++) | ||
| 145 | decrypt_undecrypted(undec.item(i), false); | ||
| 146 | } | ||
| 147 | |||
| 148 | /* Passphrase accessors */ | ||
| 149 | function manage_key(n, do_add) { | ||
| 150 | var name = n.parentNode.getElementsByClassName('sjcl-addkey-name')[0]; | ||
| 151 | var key = n.parentNode.getElementsByClassName('sjcl-addkey-key')[0]; | ||
| 152 | if (!name.value) | ||
| 153 | alert( 'Missing key name.'); | ||
| 154 | else | ||
| 155 | store_key(name.value, do_add ? key.value : ''); | ||
| 156 | inject_textarea_hook(); | ||
| 157 | } | ||
| 158 | |||
| 159 | /* Try to use the most logliving method to store key array */ | ||
| 160 | function store_key(name, key) { | ||
| 161 | /* Collect keys from all storage methods */ | ||
| 162 | var obj = {}; | ||
| 163 | try { | ||
| 164 | obj = JSON.parse(localStorage['frab-sjcl']); | ||
| 165 | } catch(e) {} | ||
| 166 | try { | ||
| 167 | var sso = JSON.parse(sessionStorage['frab-sjcl']); | ||
| 168 | for (var k in sso) { obj[k] = sso[k]; } | ||
| 169 | delete sessionStorage['frab-sjcl']; | ||
| 170 | } catch(e) {} | ||
| 171 | try { | ||
| 172 | wo = JSON.parse(window['frab-sjcl']); | ||
| 173 | for (var k in wo) { obj[k] = wo[k]; } | ||
| 174 | delete window['frab-sjcl']; | ||
| 175 | } catch(e) {} | ||
| 176 | |||
| 177 | if (key) | ||
| 178 | obj[name] = key; | ||
| 179 | else | ||
| 180 | delete obj[name]; | ||
| 181 | var out = JSON.stringify(obj); | ||
| 182 | |||
| 183 | /* Try to store in local/sessionStorage and fall back to | ||
| 184 | the window object, so at least we can decrypt all elements | ||
| 185 | locally */ | ||
| 186 | try { | ||
| 187 | localStorage['frab-sjcl'] = out; | ||
| 188 | if (localStorage['frab-sjcl'] != out) | ||
| 189 | throw 0; | ||
| 190 | return; | ||
| 191 | } catch(e) {} | ||
| 192 | try { | ||
| 193 | sessionStorage['frab-sjcl'] = out; | ||
| 194 | if (sessionStorage['frab-sjcl'] != out) | ||
| 195 | throw 0; | ||
| 196 | return; | ||
| 197 | } catch(e) {} | ||
| 198 | |||
| 199 | window['frab-sjcl'] = out; | ||
| 200 | } | ||
| 201 | |||
| 202 | function retrieve_key(name) { | ||
| 203 | try { | ||
| 204 | var obj = JSON.parse(localStorage['frab-sjcl'] || '{}'); | ||
| 205 | if (obj && obj[name]) | ||
| 206 | return obj[name]; | ||
| 207 | } catch(e) {} | ||
| 208 | try { | ||
| 209 | var obj = JSON.parse(sessionStorage['frab-sjcl'] || '{}'); | ||
| 210 | if (obj && obj[name]) | ||
| 211 | return obj[name]; | ||
| 212 | } catch(e) {} | ||
| 213 | try { | ||
| 214 | var obj = JSON.parse(window['frab-sjcl'] || '{}'); | ||
| 215 | if (obj && obj[name]) | ||
| 216 | return obj[name]; | ||
| 217 | } catch(e) {} | ||
| 218 | return ''; | ||
| 219 | } | ||
| 220 | |||
| 221 | function list_keys() { | ||
| 222 | var key_list = []; | ||
| 223 | try { | ||
| 224 | key_list = (Object.keys(JSON.parse(localStorage['frab-sjcl']))); | ||
| 225 | } catch(e) {} | ||
| 226 | try { | ||
| 227 | key_list = key_list.concat(Object.keys(JSON.parse(sessionStorage['frab-sjcl']))); | ||
| 228 | } catch(e) {} | ||
| 229 | try { | ||
| 230 | key_list = key_list.concat(Object.keys(JSON.parse(window['frab-sjcl']))); | ||
| 231 | } catch(e) {} | ||
| 232 | return key_list; | ||
| 233 | } | ||
| 234 | |||
| 235 | /* Event handler plumbing */ | ||
| 236 | function click_body(e) { | ||
| 237 | var n = e.target; | ||
| 238 | |||
| 239 | if (n.className=='sjcl-usekey') { | ||
| 240 | var name = n.getAttribute('keyname'); | ||
| 241 | encrypt_textarea(n.parentNode.parentNode.previousSibling, name, retrieve_key(name)); | ||
| 242 | } | ||
| 243 | if (n.className=='sjcl-decrypt') | ||
| 244 | decrypt_textarea(n.parentNode.parentNode.previousSibling); | ||
| 245 | if ((' '+n.className+' ').indexOf(' sjcl-undecrypted ') > -1) | ||
| 246 | decrypt_undecrypted(n); | ||
| 247 | if (n.className=='sjcl-addkey-button') | ||
| 248 | manage_key(n,true); | ||
| 249 | if (n.className=='sjcl-delkey-button') | ||
| 250 | manage_key(n,false); | ||
| 251 | } | ||
| 252 | |||
| 253 | function loaded() { | ||
| 254 | sjcl.random.startCollectors(); | ||
| 255 | document.body.addEventListener("click", click_body) | ||
| 256 | |||
| 257 | inject_css(); | ||
| 258 | inject_textarea_hook(); | ||
| 259 | |||
| 260 | /* Try to decrypt all encrypted nodes */ | ||
| 261 | find_encrypted_nodes(); | ||
| 262 | } | ||
| 263 | |||
| 264 | window.addEventListener('load', loaded); | ||
