diff options
author | Dirk Engling <erdgeist@erdgeist.org> | 2016-10-09 01:54:34 +0200 |
---|---|---|
committer | Dirk Engling <erdgeist@erdgeist.org> | 2016-10-09 01:54:34 +0200 |
commit | c932cd295d414f78c65167e7717b04f61e93122a (patch) | |
tree | aad57f7461ad72ba4ebf4e411f112e1eacb26ad0 /sjcl-front.js |
Initial tests
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); | ||