Browse Source

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

Fixes #497
JC Brand 6 years ago
parent
commit
1d5cf8eb7c
2 changed files with 57 additions and 67 deletions
  1. 28 32
      dist/converse.js
  2. 29 35
      src/converse-omemo.js

+ 28 - 32
dist/converse.js

@@ -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);

+ 29 - 35
src/converse-omemo.js

@@ -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);