Fix AES-GCM encryption/decryption so that it works with Conversations
Fixes #497
This commit is contained in:
parent
dd71d6ee9b
commit
1d5cf8eb7c
60
dist/converse.js
vendored
60
dist/converse.js
vendored
@ -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_
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
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_
|
|||||||
'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_
|
|||||||
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 @@
|
|||||||
// 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 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
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 @@
|
|||||||
'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 @@
|
|||||||
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);
|
||||||
|
Loading…
Reference in New Issue
Block a user