FIX: Replace hardcoded salt with a proper random one.

FIX: Replace hardcoded salt with a proper random one.

From 74d273bec9b329d57d6ae699f67be87bafa38809 Mon Sep 17 00:00:00 2001
From: Dan Ungureanu <dan@ungureanu.me>
Date: Fri, 30 Nov 2018 19:12:58 +0200
Subject: [PATCH] FIX: Replace hardcoded salt with a proper random one.


diff --git a/assets/javascripts/discourse/connectors/user-preferences-account/encrypt.js.es6 b/assets/javascripts/discourse/connectors/user-preferences-account/encrypt.js.es6
index 3a9ece5..fd0b69a 100644
--- a/assets/javascripts/discourse/connectors/user-preferences-account/encrypt.js.es6
+++ b/assets/javascripts/discourse/connectors/user-preferences-account/encrypt.js.es6
@@ -5,6 +5,7 @@ import {
   exportPrivateKey,
   exportPublicKey,
   generateKeyPair,
+  generateSalt,
   generatePassphraseKey,
   importPrivateKey,
   importPublicKey
@@ -92,32 +93,34 @@ export default {
           const [publicKey, privateKey] = keyPair;
 
           const passphrase = this.get("passphrase");
+          const salt = generateSalt();
           const publicStr = exportPublicKey(publicKey);
-          const privateStr = generatePassphraseKey(passphrase).then(
+          const privateStr = generatePassphraseKey(passphrase, salt).then(
             passphraseKey => exportPrivateKey(privateKey, passphraseKey)
           );
 
-          return Promise.all([publicStr, privateStr]);
+          return Promise.all([publicStr, privateStr, salt]);
         })
 
         // 3. Save keys to server.
-        .then(([publicStr, privateStr]) => {
+        .then(([publicStr, privateStr, salt]) => {
           this.set("model.custom_fields.encrypt_public_key", publicStr);
           this.set("model.custom_fields.encrypt_private_key", privateStr);
+          this.set("model.custom_fields.encrypt_salt", salt);
           const saveKeys = ajax("/encrypt/keys", {
             type: "PUT",
-            data: { public_key: publicStr, private_key: privateStr }
+            data: { public_key: publicStr, private_key: privateStr, salt }
           });
 
-          return Promise.all([publicStr, privateStr, saveKeys]);
+          return Promise.all([publicStr, privateStr, salt, saveKeys]);
         })
 
         // 4. Re-import keys but this time as `unextractable`.
-        .then(([publicStr, privateStr]) =>
+        .then(([publicStr, privateStr, salt]) =>
           Promise.all([
             importPublicKey(publicStr),
-            generatePassphraseKey(this.get("passphrase")).then(passphraseKey =>
-              importPrivateKey(privateStr, passphraseKey)
+            generatePassphraseKey(this.get("passphrase"), salt).then(
+              passphraseKey => importPrivateKey(privateStr, passphraseKey)
             )
           ])
         )
@@ -145,13 +148,14 @@ export default {
 
       const publicStr = this.get("model.custom_fields.encrypt_public_key");
       const privateStr = this.get("model.custom_fields.encrypt_private_key");
+      const salt = this.get("model.custom_fields.encrypt_salt");
       const passphrase = this.get("passphrase");
 
       // 1. a. Import public key from string.
       // 1. b. Import private from string (using passphrase).
       const importPub = importPublicKey(publicStr);
-      const importPrv = generatePassphraseKey(passphrase).then(passphraseKey =>
-        importPrivateKey(privateStr, passphraseKey)
+      const importPrv = generatePassphraseKey(passphrase, salt).then(
+        passphraseKey => importPrivateKey(privateStr, passphraseKey)
       );
 
       Promise.all([importPub, importPrv])
@@ -184,17 +188,18 @@ export default {
 
       const oldPublicStr = this.get("model.custom_fields.encrypt_public_key");
       const oldPrivateStr = this.get("model.custom_fields.encrypt_private_key");
-
+      const oldSalt = this.get("model.custom_fields.encrypt_salt");
       const oldPassphrase = this.get("oldPassphrase");
+      const salt = generateSalt();
       const passphrase = this.get("passphrase");
 
       // 1. a. Decrypt private key with old passphrase.
       // 1. b. Generate new passphrase key.
-      const p0 = generatePassphraseKey(oldPassphrase).then(passphraseKey =>
+      const p0 = generatePassphraseKey(oldPassphrase, oldSalt).then(
         // Import key as extractable so it can be later exported.
-        importPrivateKey(oldPrivateStr, passphraseKey, true)
+        passphraseKey => importPrivateKey(oldPrivateStr, passphraseKey, true)
       );
-      const p1 = generatePassphraseKey(passphrase);
+      const p1 = generatePassphraseKey(passphrase, salt);
 
       Promise.all([p0, p1])
 
@@ -207,9 +212,10 @@ export default {
         // server.
         .then(privateStr => {
           this.set("model.custom_fields.encrypt_private_key", privateStr);
+          this.set("model.custom_fields.encrypt_salt", salt);
           return ajax("/encrypt/keys", {
             type: "PUT",
-            data: { public_key: oldPublicStr, private_key: privateStr }
+            data: { public_key: oldPublicStr, private_key: privateStr, salt }
           });
         })
 
diff --git a/assets/javascripts/discourse/controllers/activate-encrypt.js.es6 b/assets/javascripts/discourse/controllers/activate-encrypt.js.es6
index 5c5c0cf..6a3d24a 100644
--- a/assets/javascripts/discourse/controllers/activate-encrypt.js.es6
+++ b/assets/javascripts/discourse/controllers/activate-encrypt.js.es6
@@ -35,13 +35,14 @@ export default Ember.Controller.extend(ModalFunctionality, {
       const user = Discourse.User.current();
       const publicStr = user.get("custom_fields.encrypt_public_key");
       const privateStr = user.get("custom_fields.encrypt_private_key");
+      const salt = user.get("custom_fields.encrypt_salt");
       const passphrase = this.get("passphrase");
 
       // 1. a. Import public key from string.
       // 1. b. Import private from string (using passphrase).
       const importPub = importPublicKey(publicStr);
-      const importPrv = generatePassphraseKey(passphrase).then(passphraseKey =>
-        importPrivateKey(privateStr, passphraseKey)
+      const importPrv = generatePassphraseKey(passphrase, salt).then(
+        passphraseKey => importPrivateKey(privateStr, passphraseKey)
       );
 
       Promise.all([importPub, importPrv])
diff --git a/assets/javascripts/lib/buffers.js.es6 b/assets/javascripts/lib/buffers.js.es6
index 13a8ba3..bedad3f 100644
--- a/assets/javascripts/lib/buffers.js.es6
+++ b/assets/javascripts/lib/buffers.js.es6
@@ -24,19 +24,3 @@ export function stringToBuffer(string) {
 export function bufferToString(buffer) {
   return new TextDecoder("UTF-16").decode(buffer);
 }
-
-/**
- * Converts a hex string into an `ArrayBuffer`.
- *
- * @param string
- *
- * @return
- */
-export function hexToBuffer(string) {
-  let buffer = new ArrayBuffer(string.length / 2);
-  let array = new Uint8Array(buffer);
-  for (let i = 0; i < string.length; i += 2) {
-    array[i] = parseInt(`${string[i]}${string[i + 1]}`, 16);
-  }
-  return buffer;
-}
diff --git a/assets/javascripts/lib/keys.js.es6 b/assets/javascripts/lib/keys.js.es6
index 178ff6b..d2415b5 100644
--- a/assets/javascripts/lib/keys.js.es6
+++ b/assets/javascripts/lib/keys.js.es6
@@ -5,21 +5,11 @@ import {
 
 import {
   stringToBuffer,
-  bufferToString,
-  hexToBuffer
+  bufferToString
 } from "discourse/plugins/discourse-encrypt/lib/buffers";
 
 import { isSafari } from "discourse/plugins/discourse-encrypt/lib/keys_db";
 
-/**
- * Salt used in generating passphrase keys.
- *
- * The salt must be a string of 16-bytes in hex format.
- *
- * @var String
- */
-const PASSPHRASE_SALT = "e85c53e7f119d41fd7895cdc9d7bb9dd"; // TODO
-
 /*
  * Utilities
  * =========
@@ -157,12 +147,24 @@ export function importPrivateKey(privateKey, key, extractable) {
  */
 
 /**
+ * Generates a random passphrase salt.
+ *
+ * @return String
+ */
+export function generateSalt() {
+  return bufferToBase64(window.crypto.getRandomValues(new Uint8Array(16)));
+}
+
+/**
  * Generates a key out of a passphrase, used to encrypt the private key of a
  * user's key pair.
  *
+ * @param passph

GitHub

1 Like