Commit 1d5cf8eb authored by JC Brand's avatar JC Brand

Fix AES-GCM encryption/decryption so that it works with Conversations

Fixes #497
parent dd71d6ee
...@@ -71570,7 +71570,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -71570,7 +71570,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
// Copyright (c) 2013-2018, the Converse.js developers // Copyright (c) 2013-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2) // Licensed under the Mozilla Public License (MPLv2)
/* global libsignal, ArrayBuffer, parseInt */ /* global libsignal, ArrayBuffer, parseInt, crypto */
(function (root, factory) { (function (root, factory) {
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! converse-core */ "./src/converse-core.js"), __webpack_require__(/*! templates/toolbar_omemo.html */ "./src/templates/toolbar_omemo.html")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! converse-core */ "./src/converse-core.js"), __webpack_require__(/*! templates/toolbar_omemo.html */ "./src/templates/toolbar_omemo.html")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
...@@ -71769,6 +71769,29 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -71769,6 +71769,29 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
}); });
}, },
async encryptMessage(plaintext) {
// The client MUST use fresh, randomly generated key/IV pairs
// with AES-128 in Galois/Counter Mode (GCM).
const iv = crypto.getRandomValues(new window.Uint8Array(16));
const key = await crypto.subtle.generateKey(KEY_ALGO, true, ["encrypt", "decrypt"]);
const algo = {
'name': 'AES-GCM',
'iv': iv,
'tagLength': TAG_LENGTH
};
const encrypted = await crypto.subtle.encrypt(algo, key, u.stringToArrayBuffer(plaintext));
const length = encrypted.byteLength - (128 + 7 >> 3),
ciphertext = encrypted.slice(0, length),
tag = encrypted.slice(length);
const exported_key = await crypto.subtle.exportKey("raw", key);
return Promise.resolve({
'key': key,
'key_and_tag': u.appendArrayBuffer(exported_key, tag),
'payload': u.arrayBufferToBase64(ciphertext),
'iv': u.arrayBufferToBase64(iv)
});
},
decryptMessage(obj) { decryptMessage(obj) {
return crypto.subtle.importKey('raw', obj.key, KEY_ALGO, true, ['encrypt', 'decrypt']).then(key_obj => { return crypto.subtle.importKey('raw', obj.key, KEY_ALGO, true, ['encrypt', 'decrypt']).then(key_obj => {
const algo = { const algo = {
...@@ -71776,15 +71799,16 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -71776,15 +71799,16 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
'iv': u.base64ToArrayBuffer(obj.iv), 'iv': u.base64ToArrayBuffer(obj.iv),
'tagLength': TAG_LENGTH 'tagLength': TAG_LENGTH
}; };
return window.crypto.subtle.decrypt(algo, key_obj, u.base64ToArrayBuffer(obj.payload)); const cipher = u.appendArrayBuffer(u.base64ToArrayBuffer(obj.payload), obj.tag);
}).then(out => new TextDecoder().decode(out)); return crypto.subtle.decrypt(algo, key_obj, cipher);
}).then(out => u.arrayBufferToString(out));
}, },
reportDecryptionError(e) { reportDecryptionError(e) {
const _converse = this.__super__._converse, const _converse = this.__super__._converse,
__ = _converse.__; __ = _converse.__;
this.messages.create({ this.messages.create({
'message': __("Sorry, could not decrypt a received OMEMO message due to an error.") + `${e.name} ${e.message}`, 'message': __("Sorry, could not decrypt a received OMEMO message due to an error.") + ` ${e.name} ${e.message}`,
'type': 'error' 'type': 'error'
}); });
...@@ -71879,34 +71903,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -71879,34 +71903,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
return Promise.all(devices.map(device => this.getSession(device))).then(() => devices); return Promise.all(devices.map(device => this.getSession(device))).then(() => devices);
}, },
encryptMessage(plaintext) {
// The client MUST use fresh, randomly generated key/IV pairs
// with AES-128 in Galois/Counter Mode (GCM).
const iv = window.crypto.getRandomValues(new window.Uint8Array(16));
let key;
return window.crypto.subtle.generateKey(KEY_ALGO, true, // extractable
["encrypt", "decrypt"] // key usages
).then(result => {
key = result;
const algo = {
'name': 'AES-GCM',
'iv': iv,
'tagLength': TAG_LENGTH
};
return window.crypto.subtle.encrypt(algo, key, new TextEncoder().encode(plaintext));
}).then(ciphertext => {
return window.crypto.subtle.exportKey("raw", key).then(key => {
const tag = ciphertext.slice(ciphertext.byteLength - (TAG_LENGTH + 7 >> 3));
return Promise.resolve({
'key': key,
'key_and_tag': u.appendArrayBuffer(key, tag),
'payload': u.arrayBufferToBase64(ciphertext),
'iv': u.arrayBufferToBase64(iv)
});
});
});
},
getSessionCipher(jid, id) { getSessionCipher(jid, id) {
const _converse = this.__super__._converse, const _converse = this.__super__._converse,
address = new libsignal.SignalProtocolAddress(jid, id); address = new libsignal.SignalProtocolAddress(jid, id);
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
// Copyright (c) 2013-2018, the Converse.js developers // Copyright (c) 2013-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2) // Licensed under the Mozilla Public License (MPLv2)
/* global libsignal, ArrayBuffer, parseInt */ /* global libsignal, ArrayBuffer, parseInt, crypto */
(function (root, factory) { (function (root, factory) {
define([ define([
...@@ -201,6 +201,30 @@ ...@@ -201,6 +201,30 @@
}); });
}, },
async encryptMessage (plaintext) {
// The client MUST use fresh, randomly generated key/IV pairs
// with AES-128 in Galois/Counter Mode (GCM).
const iv = crypto.getRandomValues(new window.Uint8Array(16));
const key = await crypto.subtle.generateKey(KEY_ALGO, true, ["encrypt", "decrypt"]);
const algo = {
'name': 'AES-GCM',
'iv': iv,
'tagLength': TAG_LENGTH
}
const encrypted = await crypto.subtle.encrypt(algo, key, u.stringToArrayBuffer(plaintext));
const length = encrypted.byteLength - ((128 + 7) >> 3),
ciphertext = encrypted.slice(0, length),
tag = encrypted.slice(length);
const exported_key = await crypto.subtle.exportKey("raw", key)
return Promise.resolve({
'key': key,
'key_and_tag': u.appendArrayBuffer(exported_key, tag),
'payload': u.arrayBufferToBase64(ciphertext),
'iv': u.arrayBufferToBase64(iv)
});
},
decryptMessage (obj) { decryptMessage (obj) {
return crypto.subtle.importKey('raw', obj.key, KEY_ALGO, true, ['encrypt','decrypt']) return crypto.subtle.importKey('raw', obj.key, KEY_ALGO, true, ['encrypt','decrypt'])
.then(key_obj => { .then(key_obj => {
...@@ -209,15 +233,16 @@ ...@@ -209,15 +233,16 @@
'iv': u.base64ToArrayBuffer(obj.iv), 'iv': u.base64ToArrayBuffer(obj.iv),
'tagLength': TAG_LENGTH 'tagLength': TAG_LENGTH
} }
return window.crypto.subtle.decrypt(algo, key_obj, u.base64ToArrayBuffer(obj.payload)); const cipher = u.appendArrayBuffer(u.base64ToArrayBuffer(obj.payload), obj.tag);
}).then(out => (new TextDecoder()).decode(out)); return crypto.subtle.decrypt(algo, key_obj, cipher);
}).then(out => u.arrayBufferToString(out));
}, },
reportDecryptionError (e) { reportDecryptionError (e) {
const { _converse } = this.__super__, const { _converse } = this.__super__,
{ __ } = _converse; { __ } = _converse;
this.messages.create({ this.messages.create({
'message': __("Sorry, could not decrypt a received OMEMO message due to an error.") + `${e.name} ${e.message}`, 'message': __("Sorry, could not decrypt a received OMEMO message due to an error.") + ` ${e.name} ${e.message}`,
'type': 'error', 'type': 'error',
}); });
_converse.log(e, Strophe.LogLevel.ERROR); _converse.log(e, Strophe.LogLevel.ERROR);
...@@ -300,37 +325,6 @@ ...@@ -300,37 +325,6 @@
return Promise.all(devices.map(device => this.getSession(device))).then(() => devices); return Promise.all(devices.map(device => this.getSession(device))).then(() => devices);
}, },
encryptMessage (plaintext) {
// The client MUST use fresh, randomly generated key/IV pairs
// with AES-128 in Galois/Counter Mode (GCM).
const iv = window.crypto.getRandomValues(new window.Uint8Array(16));
let key;
return window.crypto.subtle.generateKey(
KEY_ALGO,
true, // extractable
["encrypt", "decrypt"] // key usages
).then(result => {
key = result;
const algo = {
'name': 'AES-GCM',
'iv': iv,
'tagLength': TAG_LENGTH
}
return window.crypto.subtle.encrypt(algo, key, new TextEncoder().encode(plaintext));
}).then(ciphertext => {
return window.crypto.subtle.exportKey("raw", key)
.then(key => {
const tag = ciphertext.slice(ciphertext.byteLength - ((TAG_LENGTH + 7) >> 3));
return Promise.resolve({
'key': key,
'key_and_tag': u.appendArrayBuffer(key, tag),
'payload': u.arrayBufferToBase64(ciphertext),
'iv': u.arrayBufferToBase64(iv)
});
});
});
},
getSessionCipher (jid, id) { getSessionCipher (jid, id) {
const { _converse } = this.__super__, const { _converse } = this.__super__,
address = new libsignal.SignalProtocolAddress(jid, id); address = new libsignal.SignalProtocolAddress(jid, id);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment