Optional curve25519 in js
This commit is contained in:
parent
2c83fd9b5b
commit
fee6a69083
6 changed files with 123 additions and 87 deletions
|
@ -5,6 +5,7 @@
|
|||
<script type="text/javascript" src="js-deps/jquery.atmosphere.js"></script>
|
||||
<script type="text/javascript" src="js-deps/aes.js"></script>
|
||||
<script type="text/javascript" src="js-deps/hmac-sha256.js"></script>
|
||||
<script type="text/javascript" src="js-deps/curve255.js"></script>
|
||||
<script type="text/javascript" src="js-deps/lib-typedarrays.js"></script>
|
||||
<script type="text/javascript" src="js-deps/Long.min.js"></script>
|
||||
<script type="text/javascript" src="js-deps/ByteBuffer.min.js"></script>
|
||||
|
|
123
js/helpers.js
123
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) {
|
||||
|
|
83
js/test.js
83
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;
|
||||
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";
|
||||
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);
|
||||
});
|
||||
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);
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
<script type="text/javascript" src="js-deps/jquery.atmosphere.js"></script>
|
||||
<script type="text/javascript" src="js-deps/aes.js"></script>
|
||||
<script type="text/javascript" src="js-deps/hmac-sha256.js"></script>
|
||||
<script type="text/javascript" src="js-deps/curve255.js"></script>
|
||||
<script type="text/javascript" src="js-deps/lib-typedarrays.js"></script>
|
||||
<script type="text/javascript" src="js-deps/Long.min.js"></script>
|
||||
<script type="text/javascript" src="js-deps/ByteBuffer.min.js"></script>
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
<script type="text/javascript" src="js-deps/jquery.atmosphere.js"></script>
|
||||
<script type="text/javascript" src="js-deps/aes.js"></script>
|
||||
<script type="text/javascript" src="js-deps/hmac-sha256.js"></script>
|
||||
<script type="text/javascript" src="js-deps/curve255.js"></script>
|
||||
<script type="text/javascript" src="js-deps/lib-typedarrays.js"></script>
|
||||
<script type="text/javascript" src="js-deps/Long.min.js"></script>
|
||||
<script type="text/javascript" src="js-deps/ByteBuffer.min.js"></script>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<script type="text/javascript" src="js-deps/aes.js"></script>
|
||||
<script type="text/javascript" src="js-deps/hmac-sha256.js"></script>
|
||||
<script type="text/javascript" src="js-deps/lib-typedarrays.js"></script>
|
||||
<script type="text/javascript" src="js-deps/curve255.js"></script>
|
||||
<script type="text/javascript" src="js-deps/Long.min.js"></script>
|
||||
<script type="text/javascript" src="js-deps/ByteBuffer.min.js"></script>
|
||||
<script type="text/javascript" src="js-deps/ProtoBuf.min.js"></script>
|
||||
|
|
Loading…
Reference in a new issue