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

Fixes #497
This commit is contained in:
JC Brand 2018-08-31 21:11:05 +02:00
parent dd71d6ee9b
commit 1d5cf8eb7c
2 changed files with 57 additions and 67 deletions

60
dist/converse.js vendored
View File

@ -71570,7 +71570,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
// Copyright (c) 2013-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2)
/* global libsignal, ArrayBuffer, parseInt */
/* global libsignal, ArrayBuffer, parseInt, crypto */
(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_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
@ -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) {
return crypto.subtle.importKey('raw', obj.key, KEY_ALGO, true, ['encrypt', 'decrypt']).then(key_obj => {
const algo = {
@ -71776,15 +71799,16 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
'iv': u.base64ToArrayBuffer(obj.iv),
'tagLength': TAG_LENGTH
};
return window.crypto.subtle.decrypt(algo, key_obj, u.base64ToArrayBuffer(obj.payload));
}).then(out => new TextDecoder().decode(out));
const cipher = u.appendArrayBuffer(u.base64ToArrayBuffer(obj.payload), obj.tag);
return crypto.subtle.decrypt(algo, key_obj, cipher);
}).then(out => u.arrayBufferToString(out));
},
reportDecryptionError(e) {
const _converse = this.__super__._converse,
__ = _converse.__;
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'
});
@ -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);
},
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) {
const _converse = this.__super__._converse,
address = new libsignal.SignalProtocolAddress(jid, id);

View File

@ -4,7 +4,7 @@
// Copyright (c) 2013-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2)
/* global libsignal, ArrayBuffer, parseInt */
/* global libsignal, ArrayBuffer, parseInt, crypto */
(function (root, factory) {
define([
@ -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) {
return crypto.subtle.importKey('raw', obj.key, KEY_ALGO, true, ['encrypt','decrypt'])
.then(key_obj => {
@ -209,15 +233,16 @@
'iv': u.base64ToArrayBuffer(obj.iv),
'tagLength': TAG_LENGTH
}
return window.crypto.subtle.decrypt(algo, key_obj, u.base64ToArrayBuffer(obj.payload));
}).then(out => (new TextDecoder()).decode(out));
const cipher = u.appendArrayBuffer(u.base64ToArrayBuffer(obj.payload), obj.tag);
return crypto.subtle.decrypt(algo, key_obj, cipher);
}).then(out => u.arrayBufferToString(out));
},
reportDecryptionError (e) {
const { _converse } = this.__super__,
{ __ } = _converse;
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',
});
_converse.log(e, Strophe.LogLevel.ERROR);
@ -300,37 +325,6 @@
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) {
const { _converse } = this.__super__,
address = new libsignal.SignalProtocolAddress(jid, id);