From fee6a69083377faa67a1da408f6e522a7e794360 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Sun, 9 Mar 2014 20:32:00 -0400 Subject: [PATCH] Optional curve25519 in js --- background.html | 1 + js/helpers.js | 123 ++++++++++++++++++++++++++++++++---------------- js/test.js | 87 +++++++++++++++------------------- options.html | 1 + popup.html | 1 + test.html | 1 + 6 files changed, 125 insertions(+), 89 deletions(-) diff --git a/background.html b/background.html index aab31779d..5de109e0c 100644 --- a/background.html +++ b/background.html @@ -5,6 +5,7 @@ + diff --git a/js/helpers.js b/js/helpers.js index d81ddc631..7bb8308e9 100644 --- a/js/helpers.js +++ b/js/helpers.js @@ -73,6 +73,8 @@ function base64EncArr (aBytes) { } +var USE_NACL = false; + /********************************* *** Type conversion utilities *** *********************************/ @@ -111,6 +113,16 @@ function toArrayBuffer(thing) { return undefined; if (thing === Object(thing) && thing.__proto__ == StaticArrayBufferProto) return thing; + + if (thing instanceof Array) { + // Assuming Uint16Array from curve25519 + var res = new ArrayBuffer(thing.length * 2); + var uint = new Uint16Array(res); + for (var i = 0; i < thing.length; i++) + uint[i] = thing[i]; + return res; + } + if (!getStringable(thing)) throw "Tried to convert a non-stringable thing of type " + typeof thing + " to an array buffer"; var str = getString(thing); @@ -339,8 +351,10 @@ function getDeviceObjectListFromNumber(number) { var onLoadCallbacks = []; var naclLoaded = 0; function registerOnLoadFunction(func) { - if (naclLoaded) + if (naclLoaded || !USE_NACL) { func(); + return; + } onLoadCallbacks[onLoadCallbacks.length] = func; } @@ -359,29 +373,13 @@ function handleMessage(message) { } function postNaclMessage(message, callback) { + if (!USE_NACL) + throw "Attempted to make NaCL call with !USE_NACL?"; + naclMessageIdCallbackMap[naclMessageNextId] = callback; - var pass = { command: message.command }; - pass.call_id = naclMessageNextId++; - if (message["priv"] !== undefined) { - pass.priv = toArrayBuffer(message.priv); - if (pass.priv.byteLength != 32) - throw "Invalid NaCL Message"; - } else - throw "Invalid NaCL Message"; - if (message["pub"] !== undefined) { - var pub = toArrayBuffer(message.pub); - var pubView = new Uint8Array(pub); - if (pub.byteLength == 33 && pubView[0] == 5) { - pass.pub = new ArrayBuffer(32); - var pubCopy = new Uint8Array(pass.pub); - for (var i = 0; i < 32; i++) - pubCopy[i] = pubView[i+1]; - } else if (pub.byteLength == 32) - pass.pub = pub; - else - throw "Invalid NaCL Message"; - } - common.naclModule.postMessage(pass); + message.call_id = naclMessageNextId++; + + common.naclModule.postMessage(message); } /******************************************* @@ -400,22 +398,42 @@ function getRandomBytes(size) { } } +// functions exposed for testing var crypto_tests = {}; (function(crypto, $, undefined) { - var createNewKeyPair = function(callback) { - var privKey = getRandomBytes(32); - postNaclMessage({command: "bytesToPriv", priv: privKey}, function(message) { - postNaclMessage({command: "privToPub", priv: message.res}, function(message) { - var origPub = new Uint8Array(message.res); - var pub = new ArrayBuffer(33); - var pubWithPrefix = new Uint8Array(pub); - for (var i = 0; i < 32; i++) - pubWithPrefix[i+1] = origPub[i]; - pubWithPrefix[0] = 5; - callback({ pubKey: message.res, privKey: privKey }); + crypto_tests.privToPub = function(privKey, callback) { + if (privKey.byteLength != 32) + throw "Invalid private key"; + + var prependVersion = function(pubKey) { + var origPub = new Uint8Array(pubKey); + var pub = new ArrayBuffer(33); + var pubWithPrefix = new Uint8Array(pub); + for (var i = 0; i < 32; i++) + pubWithPrefix[i+1] = origPub[i]; + pubWithPrefix[0] = 5; + return pub; + } + + if (USE_NACL) { + postNaclMessage({command: "bytesToPriv", priv: privKey}, function(message) { + postNaclMessage({command: "privToPub", priv: message.res}, function(message) { + callback({ pubKey: prependVersion(message.res), privKey: privKey }); + }); }); - }); + } else { + var priv = new Uint16Array(privKey); + priv[0] &= 0xFFF8; + priv[15] = (priv[15] & 0x7FFF) | 0x4000; + //TODO: fscking type conversion + callback({ pubKey: prependVersion(toArrayBuffer(curve25519(priv))), privKey: privKey}); + } + + } + + var createNewKeyPair = function(callback) { + crypto_tests.privToPub(getRandomBytes(32), callback); } var crypto_storage = {}; @@ -467,10 +485,34 @@ var crypto_tests = {}; //TODO: Think about replacing CryptoJS stuff with optional NaCL-based implementations // Probably means all of the low-level crypto stuff here needs pulled out into its own file var ECDHE = function(pubKey, privKey, callback) { - postNaclMessage({command: "ECDHE", priv: privKey, pub: pubKey}, function(message) { - callback(message.res); - }); + if (privKey !== undefined) { + privKey = toArrayBuffer(privKey); + if (privKey.byteLength != 32) + throw "Invalid private key"; + } else + throw "Invalid private key"; + + if (pubKey !== undefined) { + pubKey = toArrayBuffer(pubKey); + var pubView = new Uint8Array(pubKey); + if (pubKey.byteLength == 33 && pubView[0] == 5) { + pubKey = new ArrayBuffer(32); + var pubCopy = new Uint8Array(pubKey); + for (var i = 0; i < 32; i++) + pubCopy[i] = pubView[i+1]; + } else if (pubKey.byteLength != 32) + throw "Invalid public key"; + } + + if (USE_NACL) { + postNaclMessage({command: "ECDHE", priv: privKey, pub: pubKey}, function(message) { + callback(message.res); + }); + } else { + callback(toArrayBuffer(curve25519(new Uint16Array(privKey), new Uint16Array(pubKey)))); + } } + crypto_tests.ECDHE = ECDHE; var HMACSHA256 = function(input, key) { //TODO: Waaayyyy less type conversion here (probably just means replacing CryptoJS) @@ -518,6 +560,7 @@ var crypto_tests = {}; } var decryptAESCTR = function(ciphertext, key, counter) { + //TODO: Waaayyyy less type conversion here (probably just means replacing CryptoJS) return CryptoJS.AES.decrypt(btoa(getString(ciphertext)), CryptoJS.enc.Latin1.parse(getString(key)), {mode: CryptoJS.mode.CTR, iv: CryptoJS.enc.Latin1.parse(""), padding: CryptoJS.pad.NoPadding}) @@ -525,11 +568,11 @@ var crypto_tests = {}; } var encryptAESCTR = function(plaintext, key, counter) { + //TODO: Waaayyyy less type conversion here (probably just means replacing CryptoJS) return CryptoJS.AES.encrypt(CryptoJS.enc.Latin1.parse(getString(plaintext)), CryptoJS.enc.Latin1.parse(getString(key)), {mode: CryptoJS.mode.CTR, iv: CryptoJS.enc.Latin1.parse(""), padding: CryptoJS.pad.NoPadding}) - .ciphertext - .toString(CryptoJS.enc.Latin1); + .ciphertext.toString(CryptoJS.enc.Latin1); } var verifyMACWithVersionByte = function(data, key, mac, version) { diff --git a/js/test.js b/js/test.js index 24c73a402..1942cb595 100644 --- a/js/test.js +++ b/js/test.js @@ -117,50 +117,44 @@ registerOnLoadFunction(function() { }, "Test simple create key", true); TEST(function(callback) { - // These are just some random curve25519 test vectors I found online + // These are just some random curve25519 test vectors I found online (with a version byte prepended to pubkeys) var alice_priv = hexToArrayBuffer("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"); - var alice_pub = hexToArrayBuffer("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a"); + var alice_pub = hexToArrayBuffer("058520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a"); var bob_priv = hexToArrayBuffer("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb"); - var bob_pub = hexToArrayBuffer("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f"); + var bob_pub = hexToArrayBuffer("05de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f"); var shared_sec = hexToArrayBuffer("4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742"); - postNaclMessage({command: "bytesToPriv", priv: alice_priv}, function(message) { + crypto_tests.privToPub(alice_priv, function(aliceKeyPair) { var target = new Uint8Array(alice_priv.slice(0)); target[0] &= 248; target[31] &= 127; target[31] |= 64; - if (String.fromCharCode.apply(null, new Uint8Array(message.res)) != String.fromCharCode.apply(null, target)) + if (String.fromCharCode.apply(null, new Uint8Array(aliceKeyPair.privKey)) != String.fromCharCode.apply(null, target)) callback(false); - var alice_calc_priv = message.res; - postNaclMessage({command: "bytesToPriv", priv: bob_priv}, function(message) { + crypto_tests.privToPub(bob_priv, function(bobKeyPair) { var target = new Uint8Array(bob_priv.slice(0)); target[0] &= 248; target[31] &= 127; target[31] |= 64; - if (String.fromCharCode.apply(null, new Uint8Array(message.res)) != String.fromCharCode.apply(null, target)) + if (String.fromCharCode.apply(null, new Uint8Array(bobKeyPair.privKey)) != String.fromCharCode.apply(null, target)) callback(false); - var bob_calc_priv = message.res; - postNaclMessage({command: "privToPub", priv: alice_calc_priv}, function(message) { - if (String.fromCharCode.apply(null, new Uint16Array(message.res)) != String.fromCharCode.apply(null, new Uint16Array(alice_pub))) + if (String.fromCharCode.apply(null, new Uint8Array(aliceKeyPair.pubKey)) != String.fromCharCode.apply(null, new Uint8Array(alice_pub))) + callback(false); + + if (String.fromCharCode.apply(null, new Uint8Array(bobKeyPair.pubKey)) != String.fromCharCode.apply(null, new Uint8Array(bob_pub))) + callback(false); + + crypto_tests.ECDHE(bobKeyPair.pubKey, aliceKeyPair.privKey, function(ss) { + if (String.fromCharCode.apply(null, new Uint16Array(ss)) != String.fromCharCode.apply(null, new Uint16Array(shared_sec))) callback(false); - postNaclMessage({command: "privToPub", priv: bob_calc_priv}, function(message) { - if (String.fromCharCode.apply(null, new Uint16Array(message.res)) != String.fromCharCode.apply(null, new Uint16Array(bob_pub))) + crypto_tests.ECDHE(aliceKeyPair.pubKey, bobKeyPair.privKey, function(ss) { + if (String.fromCharCode.apply(null, new Uint16Array(ss)) != String.fromCharCode.apply(null, new Uint16Array(shared_sec))) callback(false); - - postNaclMessage({command: "ECDHE", priv: alice_calc_priv, pub: bob_pub}, function(message) { - if (String.fromCharCode.apply(null, new Uint16Array(message.res)) != String.fromCharCode.apply(null, new Uint16Array(shared_sec))) - callback(false); - - postNaclMessage({command: "ECDHE", priv: bob_calc_priv, pub: alice_pub}, function(message) { - if (String.fromCharCode.apply(null, new Uint16Array(message.res)) != String.fromCharCode.apply(null, new Uint16Array(shared_sec))) - callback(false); - else - callback(true); - }); - }); + else + callback(true); }); }); }); @@ -193,10 +187,8 @@ aliceIdentityPriv: hexToArrayBuffer("08ebc1e1fdbbc88d1a833a9d8c287328d4f749b7b7e aliceIdentityPub: hexToArrayBuffer("05b9c152cb9fefb0a12df319ae50c728c7909a8a080fcf22d5e1842352186d3870"), bobIdentityPriv: hexToArrayBuffer("08491ea8a9aff03a724cfb44411502f3e974010e62b6db2703b9506a2e18554e"), bobIdentityPub: hexToArrayBuffer("0562d9efab60407ac5f4b1bec9c3341db4b279f24a87234c9ff36a1f868fdd8104"), -alicePre0: hexToArrayBuffer("886ec9c65f0c8c1281ca4c8115e3baf5c2ac75d2762826a3cf098c2a093b2250"), bobPre0: hexToArrayBuffer("009aa1809dbd29b15d3cc4c3c04ae45413b6396f286de46775e748c6daf36545"), aliceToBob: hexToArrayBuffer("08031205414c4943452203424f4228cfb3dbeec92832860122080012210503d7fa229643c84d5b33d42e50985fc64b77e0b4ec32c52000ce81e857b1ec141a2105b9c152cb9fefb0a12df319ae50c728c7909a8a080fcf22d5e1842352186d3870223b220a21052a59b346373f79d2aee25503b071fd4704a40db12afd6288519eeccf9aacec5b10001801220917468a49c79f0588a5037512abf4f66557"), -plain: hexToArrayBuffer("0a07486920426f6221"), sessionKey: hexToArrayBuffer("3d71b56ab9763865905597a90c6746640a946bf3a11632b31a87990579925f92f2132869dbf3f22646d00a68430ecd29cb38186b"), encryptedMessage: hexToArrayBuffer("415a326e6f457937756a6c5355785876342f6b5856346970342b6d45636f636c35424d396c4978364f525948696438634f4a68374c4e2f48534b776a4755556f304e73582f634255742b6a58464b6357697368364b363441315963316f5a47304168676466734e572b53484f313131306e664b6e6c47595445723661624e57556b394c515145706b6f52385746626c5952312b636a4b576d554d5131646f477a376b345955415055544e4d474b78413349694135797575706d6544453173545359552b736133575876366f5a7a624a614275486b5044345a4f3773416b34667558434135466e724e2f462f34445a61586952696f4a76744849413d3d"), }; @@ -204,8 +196,8 @@ encryptedMessage: hexToArrayBuffer("415a326e6f457937756a6c5355785876342f6b585634 function axolotlTestVectorsAsBob(v, callback) { localStorage.clear(); storage.putEncrypted("25519KeyidentityKey", { pubKey: v.bobIdentityPub, privKey: v.bobIdentityPriv }); - postNaclMessage({command: "privToPub", priv: v.bobPre0}, function(message) { - storage.putEncrypted("25519KeypreKey0", { pubKey: message.res, privKey: v.bobPre0 }); + crypto_tests.privToPub(v.bobPre0, function(keyPair) { + storage.putEncrypted("25519KeypreKey0", { pubKey: keyPair.pubKey, privKey: keyPair.privKey }); if (v.sessionKey !== undefined) { storage.putEncrypted("signaling_key", v.sessionKey); @@ -230,25 +222,22 @@ encryptedMessage: hexToArrayBuffer("415a326e6f457937756a6c5355785876342f6b585634 v[key] = axolotlTestVectors[key]; storage.putEncrypted("25519KeyidentityKey", { pubKey: v.aliceIdentityPub, privKey: v.aliceIdentityPriv }); - postNaclMessage({command: "privToPub", priv: v.alicePre0}, function(message) { - storage.putEncrypted("25519KeypreKey0", { pubKey: message.res, privKey: v.alicePre0 }); - postNaclMessage({command: "privToPub", priv: v.bobPre0}, function(message) { - var bobsDevice = {encodedNumber: "BOB", identityKey: v.bobIdentityPub, publicKey: message.res, preKeyId: 0}; - saveDeviceObject = bobsDevice; - - var message = new PushMessageContentProtobuf(); - message.body = "Hi Bob!"; - crypto.encryptMessageFor(bobsDevice, message, function(encryptedMsg) { - var message = new IncomingPushMessageProtobuf(); - message.message = toArrayBuffer(encryptedMsg.body); - message.type = encryptedMsg.type; - if (message.type != 3) { callback(false); return; } - message.source = "ALICE"; - - delete v['sessionKey']; - v.aliceToBob = getString(message.encode()); - axolotlTestVectorsAsBob(v, callback); - }); + crypto_tests.privToPub(v.bobPre0, function(keyPair) { + var bobsDevice = {encodedNumber: "BOB", identityKey: keyPair.privKey, publicKey: keyPair.pubKey, preKeyId: 0}; + saveDeviceObject = bobsDevice; + + var message = new PushMessageContentProtobuf(); + message.body = "Hi Bob!"; + crypto.encryptMessageFor(bobsDevice, message, function(encryptedMsg) { + var message = new IncomingPushMessageProtobuf(); + message.message = toArrayBuffer(encryptedMsg.body); + message.type = encryptedMsg.type; + if (message.type != 3) { callback(false); return; } + message.source = "ALICE"; + + delete v['sessionKey']; + v.aliceToBob = getString(message.encode()); + axolotlTestVectorsAsBob(v, callback); }); }); }, "Axolotl test vectors as alice", true); @@ -272,5 +261,5 @@ encryptedMessage: hexToArrayBuffer("415a326e6f457937756a6c5355785876342f6b585634 startNextExclusiveTest(); localStorage.clear(); - }, 500); + }, 5000); }); diff --git a/options.html b/options.html index 55448b52a..cc25a5b74 100644 --- a/options.html +++ b/options.html @@ -30,6 +30,7 @@ + diff --git a/popup.html b/popup.html index 4891624c0..5065e090f 100644 --- a/popup.html +++ b/popup.html @@ -18,6 +18,7 @@ + diff --git a/test.html b/test.html index d316cf3bf..6a7ca454b 100644 --- a/test.html +++ b/test.html @@ -16,6 +16,7 @@ +