aboutsummaryrefslogtreecommitdiffstats
path: root/view/js/crypto.js
blob: 55b9aea118b4c48da36dc424f21f2cdc6d2c765b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
function str_rot13 (str) {
  // http://kevin.vanzonneveld.net
  // +   original by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
  // +   improved by: Ates Goral (http://magnetiq.com)
  // +   bugfixed by: Onno Marsman
  // +   improved by: Rafa? Kukawski (http://blog.kukawski.pl)
  // *     example 1: str_rot13('Kevin van Zonneveld');
  // *     returns 1: 'Xriva ina Mbaariryq'
  // *     example 2: str_rot13('Xriva ina Mbaariryq');
  // *     returns 2: 'Kevin van Zonneveld'
  // *     example 3: str_rot13(33);
  // *     returns 3: '33'
	return (str + '').replace(/[a-z]/gi, function (s) {
		return String.fromCharCode(s.charCodeAt(0) + (s.toLowerCase() < 'n' ? 13 : -13));
	});
}

async function sodium_encrypt(element) {
	if (!window.sodium) {
		window.sodium = await SodiumPlus.auto();
	}

	if (typeof tinyMCE !== typeof undefined) {
		tinyMCE.triggerSave(false,true);
	}

	let message = $(element).val();
	let password = prompt(aStr['passphrase']);

	if (!password) {
		return false;
	}

	let hint = bin2hex(prompt(aStr['passhint']));

	let salt = await sodium.randombytes_buf(16);
	let nonce = await sodium.randombytes_buf(24);
	let key = await sodium.crypto_pwhash(
		32,
		password,
		salt,
		sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
		sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
	);

	// Message can be a string, buffer, array, etc.
	let ciphertext = await sodium.crypto_secretbox(message, nonce, key);
	delete message, password, key;

	let s = await sodium.sodium_bin2hex(salt);
	let n = await sodium.sodium_bin2hex(nonce);
	let c = await sodium.sodium_bin2hex(ciphertext);
	let encrypted = window.btoa(s + '.' + n + '.' + c);
	let val = "[crypt alg='XSalsa20' hint='" + hint + "']" + encrypted + '[/crypt]';

	$(element).val(val);
}

async function sodium_decrypt(alg, hint, encrypted, element) {
	if (alg !== 'XSalsa20') {
		alert('Unsupported algorithm');
		return false;
	}

	let arr = window.atob(encrypted).split('.');
	let salt = await sodium.sodium_hex2bin(arr[0]);
	let nonce = await sodium.sodium_hex2bin(arr[1]);
	let ciphertext = await sodium.sodium_hex2bin(arr[2]);
	let password = prompt((hint.length) ? hex2bin(hint) : aStr['passphrase']);

	if (!password) {
		return false;
	}

	let key = await sodium.crypto_pwhash(
		32,
		password,
		salt,
		sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
		sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
	);

	let decrypted = await sodium.crypto_secretbox_open(ciphertext, nonce, key);
	delete password, key;

	if ($(element).css('display') === 'none' && typeof tinyMCE !== typeof undefined) {
		tinyMCE.activeEditor.setContent(newdiv);
	}
	else {
		$(element).html(decrypted.toString('utf-8'));
	}
}

function hz_encrypt(alg, elem) {
	var enc_text = '';
	var newdiv = '';

	if(typeof tinyMCE !== "undefined")
		tinyMCE.triggerSave(false,true);

	var text = $(elem).val();

	// key and hint need to be localised

        var passphrase = prompt(aStr['passphrase']);
        // let the user cancel this dialogue
        if (passphrase == null)
                return false;
        var enc_key = bin2hex(passphrase);

	// If you don't provide a key you get rot13, which doesn't need a key
	// but consequently isn't secure.

	if(! enc_key)
		alg = 'rot13';

	if((alg == 'rot13') || (alg == 'triple-rot13'))
		newdiv = "[crypt alg='rot13']" + window.btoa(str_rot13(text)) + '[/crypt]';

	if(alg == 'AES-128-CCM') {

			// This is the prompt we're going to use when the receiver tries to open it.
			// Maybe "Grandma's maiden name" or "our secret place" or something.

			var enc_hint = bin2hex(prompt(aStr['passhint']));

			enc_text = sjcl.encrypt(enc_key, text);

			encrypted = enc_text.toString();

			newdiv = "[crypt alg='AES-128-CCM' hint='" + enc_hint + "']" + window.btoa(encrypted) + '[/crypt]';
	}

	enc_key = '';

	// This might be a comment box on a page with a tinymce editor
	// so check if there is a tinymce editor but also check the display
	// property of our source element - because a tinymce instance
	// will have display "none". If a normal textarea such as in a comment
	// box has display "none" you wouldn't be able to type in it.

	if($(elem).css('display') == 'none' && typeof tinyMCE !== "undefined") {
		tinyMCE.activeEditor.setContent(newdiv);
	}
	else {
		$(elem).val(newdiv);
	}

}

function hz_decrypt(alg, hint, text, elem) {

	var dec_text = '';

	var supported = ['AES-128-CCM', 'rot13', 'triple-rot13'];

	if(supported.indexOf(alg) < 0) {
		alert('Sorry, this encryption type is not supported anymore.\r\nConsider asking your admin to install the cryptojs addon for legacy crypto support.');
		return;
	}

	text = window.atob(text);

	if(alg == 'rot13' || alg == 'triple-rot13')
		dec_text = str_rot13(text);
	else {
		var enc_key = bin2hex(prompt((hint.length) ? hex2bin(hint) : aStr['passphrase']));
	}

	if(alg == 'AES-128-CCM') {
		dec_text = sjcl.decrypt(enc_key, text);
	}

	enc_key = '';

	// Not sure whether to drop this back in the conversation display.
	// It probably needs a lightbox or popup window because any conversation
	// updates could
	// wipe out the text and make you re-enter the key if it was in the
	// conversation. For now we do that so you can read it.

	var dec_result = dec_text.toString();
	delete dec_text;

	// incorrect decryptions *usually* but don't always have zero length
	// If the person typo'd let them try again without reloading the page
	// otherwise they'll have no "padlock" to click to try again.

	if(dec_result.length) {
		$(elem).html(b2h(dec_result));
		dec_result = '';
	}
}