From 8db3885659ebf7ddedaa7edd4a084e6bd08ea662 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Wed, 22 Jan 2014 06:23:41 +0000 Subject: [PATCH] Updates, NaCL --- Makefile | 22 ++ background.html | 5 +- curve25519-donna-wrapper.cpp | 74 ++++ curve25519-donna.c | 734 +++++++++++++++++++++++++++++++++++ curve25519-donna.h | 16 + js/background.js | 28 +- js/helpers.js | 174 ++++++--- js/options.js | 37 +- js/popup.js | 98 ++--- js/test.js | 65 +++- manifest.json | 5 +- options.html | 6 +- popup.html | 6 +- test.html | 32 +- 14 files changed, 1128 insertions(+), 174 deletions(-) create mode 100644 Makefile create mode 100644 curve25519-donna-wrapper.cpp create mode 100644 curve25519-donna.c create mode 100644 curve25519-donna.h diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..eaea9c1a --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +VALID_TOOLCHAINS := pnacl + +include $(NACL_SDK_ROOT)/tools/common.mk + +TARGET = curve25519 +LIBS = ppapi_cpp ppapi + +CFLAGS = -Wall +SOURCES = curve25519-donna.c curve25519-donna-wrapper.cpp + +# Build rules generated by macros from common.mk: + +$(foreach src,$(SOURCES),$(eval $(call COMPILE_RULE,$(src),$(CFLAGS)))) + +ifeq ($(CONFIG),Release) +$(eval $(call LINK_RULE,$(TARGET)_unstripped,$(SOURCES),$(LIBS),$(DEPS))) +$(eval $(call STRIP_RULE,$(TARGET),$(TARGET)_unstripped)) +else +$(eval $(call LINK_RULE,$(TARGET),$(SOURCES),$(LIBS),$(DEPS))) +endif + +$(eval $(call NMF_RULE,$(TARGET),)) diff --git a/background.html b/background.html index c26bdca2..aab31779 100644 --- a/background.html +++ b/background.html @@ -1,5 +1,6 @@ + @@ -11,6 +12,8 @@ - + +
+
diff --git a/curve25519-donna-wrapper.cpp b/curve25519-donna-wrapper.cpp new file mode 100644 index 00000000..0d158b29 --- /dev/null +++ b/curve25519-donna-wrapper.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2013 Matt Corallo + */ + +#include "curve25519-donna.h" + +#include +#include +#include +#include +#include + +#include + +const unsigned char basepoint[32] = {9}; + +class Curve25519Instance : public pp::Instance { +public: + explicit Curve25519Instance(PP_Instance instance) : pp::Instance(instance) {} + + virtual void HandleMessage(const pp::Var& var_message) { + if (!var_message.is_dictionary()) + return; // Go away broken client + + pp::VarDictionary dictionary(var_message); + pp::VarArrayBuffer privArrBuff(dictionary.Get("priv")); + if (privArrBuff.is_null() || privArrBuff.ByteLength() != 32) + return; // Go away broken client + unsigned char* priv = static_cast(privArrBuff.Map()); + + pp::VarArrayBuffer resBuffer(32); + unsigned char* res = static_cast(resBuffer.Map()); + + std::string command = dictionary.Get("command").AsString(); + if (command == "bytesToPriv") { + memcpy(res, priv, 32); + res[0] &= 248; + res[31] &= 127; + res[31] |= 64; + } else if (command == "privToPub") { + curve25519_donna(res, priv, basepoint); + } else if (command == "ECDHE") { + pp::VarArrayBuffer pubArrBuff(dictionary.Get("pub")); + if (!pubArrBuff.is_null() && pubArrBuff.ByteLength() == 32) { + unsigned char* pub = static_cast(pubArrBuff.Map()); + curve25519_donna(res, priv, pub); + pubArrBuff.Unmap(); + } + } + + resBuffer.Unmap(); + privArrBuff.Unmap(); + + pp::VarDictionary returnMessage; + returnMessage.Set("call_id", dictionary.Get("call_id").AsInt()); + returnMessage.Set("res", resBuffer); + PostMessage(returnMessage); + } +}; + +class Curve25519Module : public pp::Module { +public: + Curve25519Module() : pp::Module() {} + + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new Curve25519Instance(instance); + } +}; + +namespace pp { +Module* CreateModule() { + return new Curve25519Module(); +} +} diff --git a/curve25519-donna.c b/curve25519-donna.c new file mode 100644 index 00000000..bb1262e5 --- /dev/null +++ b/curve25519-donna.c @@ -0,0 +1,734 @@ +/* Copyright 2008, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * curve25519-donna: Curve25519 elliptic curve, public key function + * + * http://code.google.com/p/curve25519-donna/ + * + * Adam Langley + * + * Derived from public domain C code by Daniel J. Bernstein + * + * More information about curve25519 can be found here + * http://cr.yp.to/ecdh.html + * + * djb's sample implementation of curve25519 is written in a special assembly + * language called qhasm and uses the floating point registers. + * + * This is, almost, a clean room reimplementation from the curve25519 paper. It + * uses many of the tricks described therein. Only the crecip function is taken + * from the sample implementation. + */ + +#include +#include + +#ifdef _MSC_VER +#define inline __inline +#endif + +typedef uint8_t u8; +typedef int32_t s32; +typedef int64_t limb; + +/* Field element representation: + * + * Field elements are written as an array of signed, 64-bit limbs, least + * significant first. The value of the field element is: + * x[0] + 2^26·x[1] + x^51·x[2] + 2^102·x[3] + ... + * + * i.e. the limbs are 26, 25, 26, 25, ... bits wide. + */ + +/* Sum two numbers: output += in */ +static void fsum(limb *output, const limb *in) { + unsigned i; + for (i = 0; i < 10; i += 2) { + output[0+i] = (output[0+i] + in[0+i]); + output[1+i] = (output[1+i] + in[1+i]); + } +} + +/* Find the difference of two numbers: output = in - output + * (note the order of the arguments!) + */ +static void fdifference(limb *output, const limb *in) { + unsigned i; + for (i = 0; i < 10; ++i) { + output[i] = (in[i] - output[i]); + } +} + +/* Multiply a number by a scalar: output = in * scalar */ +static void fscalar_product(limb *output, const limb *in, const limb scalar) { + unsigned i; + for (i = 0; i < 10; ++i) { + output[i] = in[i] * scalar; + } +} + +/* Multiply two numbers: output = in2 * in + * + * output must be distinct to both inputs. The inputs are reduced coefficient + * form, the output is not. + */ +static void fproduct(limb *output, const limb *in2, const limb *in) { + output[0] = ((limb) ((s32) in2[0])) * ((s32) in[0]); + output[1] = ((limb) ((s32) in2[0])) * ((s32) in[1]) + + ((limb) ((s32) in2[1])) * ((s32) in[0]); + output[2] = 2 * ((limb) ((s32) in2[1])) * ((s32) in[1]) + + ((limb) ((s32) in2[0])) * ((s32) in[2]) + + ((limb) ((s32) in2[2])) * ((s32) in[0]); + output[3] = ((limb) ((s32) in2[1])) * ((s32) in[2]) + + ((limb) ((s32) in2[2])) * ((s32) in[1]) + + ((limb) ((s32) in2[0])) * ((s32) in[3]) + + ((limb) ((s32) in2[3])) * ((s32) in[0]); + output[4] = ((limb) ((s32) in2[2])) * ((s32) in[2]) + + 2 * (((limb) ((s32) in2[1])) * ((s32) in[3]) + + ((limb) ((s32) in2[3])) * ((s32) in[1])) + + ((limb) ((s32) in2[0])) * ((s32) in[4]) + + ((limb) ((s32) in2[4])) * ((s32) in[0]); + output[5] = ((limb) ((s32) in2[2])) * ((s32) in[3]) + + ((limb) ((s32) in2[3])) * ((s32) in[2]) + + ((limb) ((s32) in2[1])) * ((s32) in[4]) + + ((limb) ((s32) in2[4])) * ((s32) in[1]) + + ((limb) ((s32) in2[0])) * ((s32) in[5]) + + ((limb) ((s32) in2[5])) * ((s32) in[0]); + output[6] = 2 * (((limb) ((s32) in2[3])) * ((s32) in[3]) + + ((limb) ((s32) in2[1])) * ((s32) in[5]) + + ((limb) ((s32) in2[5])) * ((s32) in[1])) + + ((limb) ((s32) in2[2])) * ((s32) in[4]) + + ((limb) ((s32) in2[4])) * ((s32) in[2]) + + ((limb) ((s32) in2[0])) * ((s32) in[6]) + + ((limb) ((s32) in2[6])) * ((s32) in[0]); + output[7] = ((limb) ((s32) in2[3])) * ((s32) in[4]) + + ((limb) ((s32) in2[4])) * ((s32) in[3]) + + ((limb) ((s32) in2[2])) * ((s32) in[5]) + + ((limb) ((s32) in2[5])) * ((s32) in[2]) + + ((limb) ((s32) in2[1])) * ((s32) in[6]) + + ((limb) ((s32) in2[6])) * ((s32) in[1]) + + ((limb) ((s32) in2[0])) * ((s32) in[7]) + + ((limb) ((s32) in2[7])) * ((s32) in[0]); + output[8] = ((limb) ((s32) in2[4])) * ((s32) in[4]) + + 2 * (((limb) ((s32) in2[3])) * ((s32) in[5]) + + ((limb) ((s32) in2[5])) * ((s32) in[3]) + + ((limb) ((s32) in2[1])) * ((s32) in[7]) + + ((limb) ((s32) in2[7])) * ((s32) in[1])) + + ((limb) ((s32) in2[2])) * ((s32) in[6]) + + ((limb) ((s32) in2[6])) * ((s32) in[2]) + + ((limb) ((s32) in2[0])) * ((s32) in[8]) + + ((limb) ((s32) in2[8])) * ((s32) in[0]); + output[9] = ((limb) ((s32) in2[4])) * ((s32) in[5]) + + ((limb) ((s32) in2[5])) * ((s32) in[4]) + + ((limb) ((s32) in2[3])) * ((s32) in[6]) + + ((limb) ((s32) in2[6])) * ((s32) in[3]) + + ((limb) ((s32) in2[2])) * ((s32) in[7]) + + ((limb) ((s32) in2[7])) * ((s32) in[2]) + + ((limb) ((s32) in2[1])) * ((s32) in[8]) + + ((limb) ((s32) in2[8])) * ((s32) in[1]) + + ((limb) ((s32) in2[0])) * ((s32) in[9]) + + ((limb) ((s32) in2[9])) * ((s32) in[0]); + output[10] = 2 * (((limb) ((s32) in2[5])) * ((s32) in[5]) + + ((limb) ((s32) in2[3])) * ((s32) in[7]) + + ((limb) ((s32) in2[7])) * ((s32) in[3]) + + ((limb) ((s32) in2[1])) * ((s32) in[9]) + + ((limb) ((s32) in2[9])) * ((s32) in[1])) + + ((limb) ((s32) in2[4])) * ((s32) in[6]) + + ((limb) ((s32) in2[6])) * ((s32) in[4]) + + ((limb) ((s32) in2[2])) * ((s32) in[8]) + + ((limb) ((s32) in2[8])) * ((s32) in[2]); + output[11] = ((limb) ((s32) in2[5])) * ((s32) in[6]) + + ((limb) ((s32) in2[6])) * ((s32) in[5]) + + ((limb) ((s32) in2[4])) * ((s32) in[7]) + + ((limb) ((s32) in2[7])) * ((s32) in[4]) + + ((limb) ((s32) in2[3])) * ((s32) in[8]) + + ((limb) ((s32) in2[8])) * ((s32) in[3]) + + ((limb) ((s32) in2[2])) * ((s32) in[9]) + + ((limb) ((s32) in2[9])) * ((s32) in[2]); + output[12] = ((limb) ((s32) in2[6])) * ((s32) in[6]) + + 2 * (((limb) ((s32) in2[5])) * ((s32) in[7]) + + ((limb) ((s32) in2[7])) * ((s32) in[5]) + + ((limb) ((s32) in2[3])) * ((s32) in[9]) + + ((limb) ((s32) in2[9])) * ((s32) in[3])) + + ((limb) ((s32) in2[4])) * ((s32) in[8]) + + ((limb) ((s32) in2[8])) * ((s32) in[4]); + output[13] = ((limb) ((s32) in2[6])) * ((s32) in[7]) + + ((limb) ((s32) in2[7])) * ((s32) in[6]) + + ((limb) ((s32) in2[5])) * ((s32) in[8]) + + ((limb) ((s32) in2[8])) * ((s32) in[5]) + + ((limb) ((s32) in2[4])) * ((s32) in[9]) + + ((limb) ((s32) in2[9])) * ((s32) in[4]); + output[14] = 2 * (((limb) ((s32) in2[7])) * ((s32) in[7]) + + ((limb) ((s32) in2[5])) * ((s32) in[9]) + + ((limb) ((s32) in2[9])) * ((s32) in[5])) + + ((limb) ((s32) in2[6])) * ((s32) in[8]) + + ((limb) ((s32) in2[8])) * ((s32) in[6]); + output[15] = ((limb) ((s32) in2[7])) * ((s32) in[8]) + + ((limb) ((s32) in2[8])) * ((s32) in[7]) + + ((limb) ((s32) in2[6])) * ((s32) in[9]) + + ((limb) ((s32) in2[9])) * ((s32) in[6]); + output[16] = ((limb) ((s32) in2[8])) * ((s32) in[8]) + + 2 * (((limb) ((s32) in2[7])) * ((s32) in[9]) + + ((limb) ((s32) in2[9])) * ((s32) in[7])); + output[17] = ((limb) ((s32) in2[8])) * ((s32) in[9]) + + ((limb) ((s32) in2[9])) * ((s32) in[8]); + output[18] = 2 * ((limb) ((s32) in2[9])) * ((s32) in[9]); +} + +/* Reduce a long form to a short form by taking the input mod 2^255 - 19. */ +static void freduce_degree(limb *output) { + /* Each of these shifts and adds ends up multiplying the value by 19. */ + output[8] += output[18] << 4; + output[8] += output[18] << 1; + output[8] += output[18]; + output[7] += output[17] << 4; + output[7] += output[17] << 1; + output[7] += output[17]; + output[6] += output[16] << 4; + output[6] += output[16] << 1; + output[6] += output[16]; + output[5] += output[15] << 4; + output[5] += output[15] << 1; + output[5] += output[15]; + output[4] += output[14] << 4; + output[4] += output[14] << 1; + output[4] += output[14]; + output[3] += output[13] << 4; + output[3] += output[13] << 1; + output[3] += output[13]; + output[2] += output[12] << 4; + output[2] += output[12] << 1; + output[2] += output[12]; + output[1] += output[11] << 4; + output[1] += output[11] << 1; + output[1] += output[11]; + output[0] += output[10] << 4; + output[0] += output[10] << 1; + output[0] += output[10]; +} + +#if (-1 & 3) != 3 +#error "This code only works on a two's complement system" +#endif + +/* return v / 2^26, using only shifts and adds. */ +static inline limb +div_by_2_26(const limb v) +{ + /* High word of v; no shift needed*/ + const uint32_t highword = (uint32_t) (((uint64_t) v) >> 32); + /* Set to all 1s if v was negative; else set to 0s. */ + const int32_t sign = ((int32_t) highword) >> 31; + /* Set to 0x3ffffff if v was negative; else set to 0. */ + const int32_t roundoff = ((uint32_t) sign) >> 6; + /* Should return v / (1<<26) */ + return (v + roundoff) >> 26; +} + +/* return v / (2^25), using only shifts and adds. */ +static inline limb +div_by_2_25(const limb v) +{ + /* High word of v; no shift needed*/ + const uint32_t highword = (uint32_t) (((uint64_t) v) >> 32); + /* Set to all 1s if v was negative; else set to 0s. */ + const int32_t sign = ((int32_t) highword) >> 31; + /* Set to 0x1ffffff if v was negative; else set to 0. */ + const int32_t roundoff = ((uint32_t) sign) >> 7; + /* Should return v / (1<<25) */ + return (v + roundoff) >> 25; +} + +static inline s32 +div_s32_by_2_25(const s32 v) +{ + const s32 roundoff = ((uint32_t)(v >> 31)) >> 7; + return (v + roundoff) >> 25; +} + +/* Reduce all coefficients of the short form input so that |x| < 2^26. + * + * On entry: |output[i]| < 2^62 + */ +static void freduce_coefficients(limb *output) { + unsigned i; + + output[10] = 0; + + for (i = 0; i < 10; i += 2) { + limb over = div_by_2_26(output[i]); + output[i] -= over << 26; + output[i+1] += over; + + over = div_by_2_25(output[i+1]); + output[i+1] -= over << 25; + output[i+2] += over; + } + /* Now |output[10]| < 2 ^ 38 and all other coefficients are reduced. */ + output[0] += output[10] << 4; + output[0] += output[10] << 1; + output[0] += output[10]; + + output[10] = 0; + + /* Now output[1..9] are reduced, and |output[0]| < 2^26 + 19 * 2^38 + * So |over| will be no more than 77825 */ + { + limb over = div_by_2_26(output[0]); + output[0] -= over << 26; + output[1] += over; + } + + /* Now output[0,2..9] are reduced, and |output[1]| < 2^25 + 77825 + * So |over| will be no more than 1. */ + { + /* output[1] fits in 32 bits, so we can use div_s32_by_2_25 here. */ + s32 over32 = div_s32_by_2_25((s32) output[1]); + output[1] -= over32 << 25; + output[2] += over32; + } + + /* Finally, output[0,1,3..9] are reduced, and output[2] is "nearly reduced": + * we have |output[2]| <= 2^26. This is good enough for all of our math, + * but it will require an extra freduce_coefficients before fcontract. */ +} + +/* A helpful wrapper around fproduct: output = in * in2. + * + * output must be distinct to both inputs. The output is reduced degree and + * reduced coefficient. + */ +static void +fmul(limb *output, const limb *in, const limb *in2) { + limb t[19]; + fproduct(t, in, in2); + freduce_degree(t); + freduce_coefficients(t); + memcpy(output, t, sizeof(limb) * 10); +} + +static void fsquare_inner(limb *output, const limb *in) { + output[0] = ((limb) ((s32) in[0])) * ((s32) in[0]); + output[1] = 2 * ((limb) ((s32) in[0])) * ((s32) in[1]); + output[2] = 2 * (((limb) ((s32) in[1])) * ((s32) in[1]) + + ((limb) ((s32) in[0])) * ((s32) in[2])); + output[3] = 2 * (((limb) ((s32) in[1])) * ((s32) in[2]) + + ((limb) ((s32) in[0])) * ((s32) in[3])); + output[4] = ((limb) ((s32) in[2])) * ((s32) in[2]) + + 4 * ((limb) ((s32) in[1])) * ((s32) in[3]) + + 2 * ((limb) ((s32) in[0])) * ((s32) in[4]); + output[5] = 2 * (((limb) ((s32) in[2])) * ((s32) in[3]) + + ((limb) ((s32) in[1])) * ((s32) in[4]) + + ((limb) ((s32) in[0])) * ((s32) in[5])); + output[6] = 2 * (((limb) ((s32) in[3])) * ((s32) in[3]) + + ((limb) ((s32) in[2])) * ((s32) in[4]) + + ((limb) ((s32) in[0])) * ((s32) in[6]) + + 2 * ((limb) ((s32) in[1])) * ((s32) in[5])); + output[7] = 2 * (((limb) ((s32) in[3])) * ((s32) in[4]) + + ((limb) ((s32) in[2])) * ((s32) in[5]) + + ((limb) ((s32) in[1])) * ((s32) in[6]) + + ((limb) ((s32) in[0])) * ((s32) in[7])); + output[8] = ((limb) ((s32) in[4])) * ((s32) in[4]) + + 2 * (((limb) ((s32) in[2])) * ((s32) in[6]) + + ((limb) ((s32) in[0])) * ((s32) in[8]) + + 2 * (((limb) ((s32) in[1])) * ((s32) in[7]) + + ((limb) ((s32) in[3])) * ((s32) in[5]))); + output[9] = 2 * (((limb) ((s32) in[4])) * ((s32) in[5]) + + ((limb) ((s32) in[3])) * ((s32) in[6]) + + ((limb) ((s32) in[2])) * ((s32) in[7]) + + ((limb) ((s32) in[1])) * ((s32) in[8]) + + ((limb) ((s32) in[0])) * ((s32) in[9])); + output[10] = 2 * (((limb) ((s32) in[5])) * ((s32) in[5]) + + ((limb) ((s32) in[4])) * ((s32) in[6]) + + ((limb) ((s32) in[2])) * ((s32) in[8]) + + 2 * (((limb) ((s32) in[3])) * ((s32) in[7]) + + ((limb) ((s32) in[1])) * ((s32) in[9]))); + output[11] = 2 * (((limb) ((s32) in[5])) * ((s32) in[6]) + + ((limb) ((s32) in[4])) * ((s32) in[7]) + + ((limb) ((s32) in[3])) * ((s32) in[8]) + + ((limb) ((s32) in[2])) * ((s32) in[9])); + output[12] = ((limb) ((s32) in[6])) * ((s32) in[6]) + + 2 * (((limb) ((s32) in[4])) * ((s32) in[8]) + + 2 * (((limb) ((s32) in[5])) * ((s32) in[7]) + + ((limb) ((s32) in[3])) * ((s32) in[9]))); + output[13] = 2 * (((limb) ((s32) in[6])) * ((s32) in[7]) + + ((limb) ((s32) in[5])) * ((s32) in[8]) + + ((limb) ((s32) in[4])) * ((s32) in[9])); + output[14] = 2 * (((limb) ((s32) in[7])) * ((s32) in[7]) + + ((limb) ((s32) in[6])) * ((s32) in[8]) + + 2 * ((limb) ((s32) in[5])) * ((s32) in[9])); + output[15] = 2 * (((limb) ((s32) in[7])) * ((s32) in[8]) + + ((limb) ((s32) in[6])) * ((s32) in[9])); + output[16] = ((limb) ((s32) in[8])) * ((s32) in[8]) + + 4 * ((limb) ((s32) in[7])) * ((s32) in[9]); + output[17] = 2 * ((limb) ((s32) in[8])) * ((s32) in[9]); + output[18] = 2 * ((limb) ((s32) in[9])) * ((s32) in[9]); +} + +static void +fsquare(limb *output, const limb *in) { + limb t[19]; + fsquare_inner(t, in); + freduce_degree(t); + freduce_coefficients(t); + memcpy(output, t, sizeof(limb) * 10); +} + +/* Take a little-endian, 32-byte number and expand it into polynomial form */ +static void +fexpand(limb *output, const u8 *input) { +#define F(n,start,shift,mask) \ + output[n] = ((((limb) input[start + 0]) | \ + ((limb) input[start + 1]) << 8 | \ + ((limb) input[start + 2]) << 16 | \ + ((limb) input[start + 3]) << 24) >> shift) & mask; + F(0, 0, 0, 0x3ffffff); + F(1, 3, 2, 0x1ffffff); + F(2, 6, 3, 0x3ffffff); + F(3, 9, 5, 0x1ffffff); + F(4, 12, 6, 0x3ffffff); + F(5, 16, 0, 0x1ffffff); + F(6, 19, 1, 0x3ffffff); + F(7, 22, 3, 0x1ffffff); + F(8, 25, 4, 0x3ffffff); + F(9, 28, 6, 0x3ffffff); +#undef F +} + +#if (-32 >> 1) != -16 +#error "This code only works when >> does sign-extension on negative numbers" +#endif + +/* Take a fully reduced polynomial form number and contract it into a + * little-endian, 32-byte array + */ +static void +fcontract(u8 *output, limb *input) { + int i; + int j; + + for (j = 0; j < 2; ++j) { + for (i = 0; i < 9; ++i) { + if ((i & 1) == 1) { + /* This calculation is a time-invariant way to make input[i] positive + by borrowing from the next-larger limb. + */ + const s32 mask = (s32)(input[i]) >> 31; + const s32 carry = -(((s32)(input[i]) & mask) >> 25); + input[i] = (s32)(input[i]) + (carry << 25); + input[i+1] = (s32)(input[i+1]) - carry; + } else { + const s32 mask = (s32)(input[i]) >> 31; + const s32 carry = -(((s32)(input[i]) & mask) >> 26); + input[i] = (s32)(input[i]) + (carry << 26); + input[i+1] = (s32)(input[i+1]) - carry; + } + } + { + const s32 mask = (s32)(input[9]) >> 31; + const s32 carry = -(((s32)(input[9]) & mask) >> 25); + input[9] = (s32)(input[9]) + (carry << 25); + input[0] = (s32)(input[0]) - (carry * 19); + } + } + + /* The first borrow-propagation pass above ended with every limb + except (possibly) input[0] non-negative. + + Since each input limb except input[0] is decreased by at most 1 + by a borrow-propagation pass, the second borrow-propagation pass + could only have wrapped around to decrease input[0] again if the + first pass left input[0] negative *and* input[1] through input[9] + were all zero. In that case, input[1] is now 2^25 - 1, and this + last borrow-propagation step will leave input[1] non-negative. + */ + { + const s32 mask = (s32)(input[0]) >> 31; + const s32 carry = -(((s32)(input[0]) & mask) >> 26); + input[0] = (s32)(input[0]) + (carry << 26); + input[1] = (s32)(input[1]) - carry; + } + + /* Both passes through the above loop, plus the last 0-to-1 step, are + necessary: if input[9] is -1 and input[0] through input[8] are 0, + negative values will remain in the array until the end. + */ + + input[1] <<= 2; + input[2] <<= 3; + input[3] <<= 5; + input[4] <<= 6; + input[6] <<= 1; + input[7] <<= 3; + input[8] <<= 4; + input[9] <<= 6; +#define F(i, s) \ + output[s+0] |= input[i] & 0xff; \ + output[s+1] = (input[i] >> 8) & 0xff; \ + output[s+2] = (input[i] >> 16) & 0xff; \ + output[s+3] = (input[i] >> 24) & 0xff; + output[0] = 0; + output[16] = 0; + F(0,0); + F(1,3); + F(2,6); + F(3,9); + F(4,12); + F(5,16); + F(6,19); + F(7,22); + F(8,25); + F(9,28); +#undef F +} + +/* Input: Q, Q', Q-Q' + * Output: 2Q, Q+Q' + * + * x2 z3: long form + * x3 z3: long form + * x z: short form, destroyed + * xprime zprime: short form, destroyed + * qmqp: short form, preserved + */ +static void fmonty(limb *x2, limb *z2, /* output 2Q */ + limb *x3, limb *z3, /* output Q + Q' */ + limb *x, limb *z, /* input Q */ + limb *xprime, limb *zprime, /* input Q' */ + const limb *qmqp /* input Q - Q' */) { + limb origx[10], origxprime[10], zzz[19], xx[19], zz[19], xxprime[19], + zzprime[19], zzzprime[19], xxxprime[19]; + + memcpy(origx, x, 10 * sizeof(limb)); + fsum(x, z); + fdifference(z, origx); // does x - z + + memcpy(origxprime, xprime, sizeof(limb) * 10); + fsum(xprime, zprime); + fdifference(zprime, origxprime); + fproduct(xxprime, xprime, z); + fproduct(zzprime, x, zprime); + freduce_degree(xxprime); + freduce_coefficients(xxprime); + freduce_degree(zzprime); + freduce_coefficients(zzprime); + memcpy(origxprime, xxprime, sizeof(limb) * 10); + fsum(xxprime, zzprime); + fdifference(zzprime, origxprime); + fsquare(xxxprime, xxprime); + fsquare(zzzprime, zzprime); + fproduct(zzprime, zzzprime, qmqp); + freduce_degree(zzprime); + freduce_coefficients(zzprime); + memcpy(x3, xxxprime, sizeof(limb) * 10); + memcpy(z3, zzprime, sizeof(limb) * 10); + + fsquare(xx, x); + fsquare(zz, z); + fproduct(x2, xx, zz); + freduce_degree(x2); + freduce_coefficients(x2); + fdifference(zz, xx); // does zz = xx - zz + memset(zzz + 10, 0, sizeof(limb) * 9); + fscalar_product(zzz, zz, 121665); + /* No need to call freduce_degree here: + fscalar_product doesn't increase the degree of its input. */ + freduce_coefficients(zzz); + fsum(zzz, xx); + fproduct(z2, zz, zzz); + freduce_degree(z2); + freduce_coefficients(z2); +} + +/* Conditionally swap two reduced-form limb arrays if 'iswap' is 1, but leave + * them unchanged if 'iswap' is 0. Runs in data-invariant time to avoid + * side-channel attacks. + * + * NOTE that this function requires that 'iswap' be 1 or 0; other values give + * wrong results. Also, the two limb arrays must be in reduced-coefficient, + * reduced-degree form: the values in a[10..19] or b[10..19] aren't swapped, + * and all all values in a[0..9],b[0..9] must have magnitude less than + * INT32_MAX. + */ +static void +swap_conditional(limb a[19], limb b[19], limb iswap) { + unsigned i; + const s32 swap = (s32) -iswap; + + for (i = 0; i < 10; ++i) { + const s32 x = swap & ( ((s32)a[i]) ^ ((s32)b[i]) ); + a[i] = ((s32)a[i]) ^ x; + b[i] = ((s32)b[i]) ^ x; + } +} + +/* Calculates nQ where Q is the x-coordinate of a point on the curve + * + * resultx/resultz: the x coordinate of the resulting curve point (short form) + * n: a little endian, 32-byte number + * q: a point of the curve (short form) + */ +static void +cmult(limb *resultx, limb *resultz, const u8 *n, const limb *q) { + limb a[19] = {0}, b[19] = {1}, c[19] = {1}, d[19] = {0}; + limb *nqpqx = a, *nqpqz = b, *nqx = c, *nqz = d, *t; + limb e[19] = {0}, f[19] = {1}, g[19] = {0}, h[19] = {1}; + limb *nqpqx2 = e, *nqpqz2 = f, *nqx2 = g, *nqz2 = h; + + unsigned i, j; + + memcpy(nqpqx, q, sizeof(limb) * 10); + + for (i = 0; i < 32; ++i) { + u8 byte = n[31 - i]; + for (j = 0; j < 8; ++j) { + const limb bit = byte >> 7; + + swap_conditional(nqx, nqpqx, bit); + swap_conditional(nqz, nqpqz, bit); + fmonty(nqx2, nqz2, + nqpqx2, nqpqz2, + nqx, nqz, + nqpqx, nqpqz, + q); + swap_conditional(nqx2, nqpqx2, bit); + swap_conditional(nqz2, nqpqz2, bit); + + t = nqx; + nqx = nqx2; + nqx2 = t; + t = nqz; + nqz = nqz2; + nqz2 = t; + t = nqpqx; + nqpqx = nqpqx2; + nqpqx2 = t; + t = nqpqz; + nqpqz = nqpqz2; + nqpqz2 = t; + + byte <<= 1; + } + } + + memcpy(resultx, nqx, sizeof(limb) * 10); + memcpy(resultz, nqz, sizeof(limb) * 10); +} + +// ----------------------------------------------------------------------------- +// Shamelessly copied from djb's code +// ----------------------------------------------------------------------------- +static void +crecip(limb *out, const limb *z) { + limb z2[10]; + limb z9[10]; + limb z11[10]; + limb z2_5_0[10]; + limb z2_10_0[10]; + limb z2_20_0[10]; + limb z2_50_0[10]; + limb z2_100_0[10]; + limb t0[10]; + limb t1[10]; + int i; + + /* 2 */ fsquare(z2,z); + /* 4 */ fsquare(t1,z2); + /* 8 */ fsquare(t0,t1); + /* 9 */ fmul(z9,t0,z); + /* 11 */ fmul(z11,z9,z2); + /* 22 */ fsquare(t0,z11); + /* 2^5 - 2^0 = 31 */ fmul(z2_5_0,t0,z9); + + /* 2^6 - 2^1 */ fsquare(t0,z2_5_0); + /* 2^7 - 2^2 */ fsquare(t1,t0); + /* 2^8 - 2^3 */ fsquare(t0,t1); + /* 2^9 - 2^4 */ fsquare(t1,t0); + /* 2^10 - 2^5 */ fsquare(t0,t1); + /* 2^10 - 2^0 */ fmul(z2_10_0,t0,z2_5_0); + + /* 2^11 - 2^1 */ fsquare(t0,z2_10_0); + /* 2^12 - 2^2 */ fsquare(t1,t0); + /* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { fsquare(t0,t1); fsquare(t1,t0); } + /* 2^20 - 2^0 */ fmul(z2_20_0,t1,z2_10_0); + + /* 2^21 - 2^1 */ fsquare(t0,z2_20_0); + /* 2^22 - 2^2 */ fsquare(t1,t0); + /* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { fsquare(t0,t1); fsquare(t1,t0); } + /* 2^40 - 2^0 */ fmul(t0,t1,z2_20_0); + + /* 2^41 - 2^1 */ fsquare(t1,t0); + /* 2^42 - 2^2 */ fsquare(t0,t1); + /* 2^50 - 2^10 */ for (i = 2;i < 10;i += 2) { fsquare(t1,t0); fsquare(t0,t1); } + /* 2^50 - 2^0 */ fmul(z2_50_0,t0,z2_10_0); + + /* 2^51 - 2^1 */ fsquare(t0,z2_50_0); + /* 2^52 - 2^2 */ fsquare(t1,t0); + /* 2^100 - 2^50 */ for (i = 2;i < 50;i += 2) { fsquare(t0,t1); fsquare(t1,t0); } + /* 2^100 - 2^0 */ fmul(z2_100_0,t1,z2_50_0); + + /* 2^101 - 2^1 */ fsquare(t1,z2_100_0); + /* 2^102 - 2^2 */ fsquare(t0,t1); + /* 2^200 - 2^100 */ for (i = 2;i < 100;i += 2) { fsquare(t1,t0); fsquare(t0,t1); } + /* 2^200 - 2^0 */ fmul(t1,t0,z2_100_0); + + /* 2^201 - 2^1 */ fsquare(t0,t1); + /* 2^202 - 2^2 */ fsquare(t1,t0); + /* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { fsquare(t0,t1); fsquare(t1,t0); } + /* 2^250 - 2^0 */ fmul(t0,t1,z2_50_0); + + /* 2^251 - 2^1 */ fsquare(t1,t0); + /* 2^252 - 2^2 */ fsquare(t0,t1); + /* 2^253 - 2^3 */ fsquare(t1,t0); + /* 2^254 - 2^4 */ fsquare(t0,t1); + /* 2^255 - 2^5 */ fsquare(t1,t0); + /* 2^255 - 21 */ fmul(out,t1,z11); +} + +int curve25519_donna(u8 *, const u8 *, const u8 *); + +int +curve25519_donna(u8 *mypublic, const u8 *secret, const u8 *basepoint) { + limb bp[10], x[10], z[11], zmone[10]; + uint8_t e[32]; + int i; + + for (i = 0; i < 32; ++i) e[i] = secret[i]; + e[0] &= 248; + e[31] &= 127; + e[31] |= 64; + + fexpand(bp, basepoint); + cmult(x, z, e, bp); + crecip(zmone, z); + fmul(z, x, zmone); + freduce_coefficients(z); + fcontract(mypublic, z); + return 0; +} diff --git a/curve25519-donna.h b/curve25519-donna.h new file mode 100644 index 00000000..8d4adc2f --- /dev/null +++ b/curve25519-donna.h @@ -0,0 +1,16 @@ +#ifndef CURVE25519_DONNA_H +#define CURVE25519_DONNA_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int curve25519_donna(uint8_t *, const uint8_t *, const uint8_t *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/js/background.js b/js/background.js index 6697a8b3..2135fa82 100644 --- a/js/background.js +++ b/js/background.js @@ -1,14 +1,16 @@ -if (!localStorage.getItem('first_install_ran')) { - localStorage.setItem('first_install_ran', 1); - chrome.tabs.create({url: "options.html"}); -} else { - if (isRegistrationDone()) { - subscribeToPush(function(message) { - console.log("Got message from " + message.source + ": \"" + getString(message.message)); - var newUnreadCount = storage.getUnencrypted("unreadCount") + 1; - storage.putUnencrypted("unreadCount", newUnreadCount); - chrome.browserAction.setBadgeText({text: newUnreadCount + ""}); - storeMessage(message); - }); +registerOnLoadFunction(function() { + if (!localStorage.getItem('first_install_ran')) { + localStorage.setItem('first_install_ran', 1); + chrome.tabs.create({url: "options.html"}); + } else { + if (isRegistrationDone()) { + subscribeToPush(function(message) { + console.log("Got message from " + message.source + ": \"" + getString(message.message)); + var newUnreadCount = storage.getUnencrypted("unreadCount") + 1; + storage.putUnencrypted("unreadCount", newUnreadCount); + chrome.browserAction.setBadgeText({text: newUnreadCount + ""}); + storeMessage(message); + }); + } } -} +}); diff --git a/js/helpers.js b/js/helpers.js index af0f7b9f..88ad8b8a 100644 --- a/js/helpers.js +++ b/js/helpers.js @@ -19,7 +19,9 @@ function b64ToUint6 (nChr) { function base64DecToArr (sBase64, nBlocksSize) { var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length, - nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2, taBytes = new Uint8Array(nOutLen); + nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2; +var aBBytes = new ArrayBuffer(nOutLen); +var taBytes = new Uint8Array(aBBytes); for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { nMod4 = nInIdx & 3; @@ -31,7 +33,7 @@ function base64DecToArr (sBase64, nBlocksSize) { nUint24 = 0; } } - return taBytes; + return aBBytes; } /********************************* @@ -39,20 +41,20 @@ function base64DecToArr (sBase64, nBlocksSize) { *********************************/ // Strings/arrays var StaticByteBufferProto = new dcodeIO.ByteBuffer().__proto__; -var StaticUint8ArrayProto = new Uint8Array().__proto__; +var StaticArrayBufferProto = new ArrayBuffer().__proto__; function getString(thing) { - if (thing.__proto__ == StaticUint8ArrayProto) + if (thing.__proto__ == StaticArrayBufferProto) return String.fromCharCode.apply(null, thing); if (thing != undefined && thing.__proto__ == StaticByteBufferProto) return thing.toString("utf8"); return thing; } -function getUint8Array(string) { +function getArrayBuffer(string) { return base64DecToArr(btoa(string)); } -function base64ToUint8Array(string) { +function base64ToArrayBuffer(string) { return base64DecToArr(string); } @@ -238,15 +240,49 @@ function getDeviceObjectListFromNumber(number) { return deviceObjectList; } +/********************** + *** NaCL Interface *** + **********************/ +var onLoadCallbacks = []; +var naclLoaded = 0; +function registerOnLoadFunction(func) { + if (naclLoaded) + func(); + onLoadCallbacks[onLoadCallbacks.length] = func; +} + +var naclMessageNextId = 0; +var naclMessageIdCallbackMap = {}; +function moduleDidLoad() { + common.hideModule(); + naclLoaded = 1; + for (var i = 0; i < onLoadCallbacks.length; i++) + onLoadCallbacks[i](); + onLoadCallbacks = []; +} + +function handleMessage(message) { + console.log("Got message"); + console.log(message); + naclMessageIdCallbackMap[message.data.call_id](message.data); +} + +function postNaclMessage(message, callback) { + naclMessageIdCallbackMap[naclMessageNextId] = callback; + message.call_id = naclMessageNextId++; + common.naclModule.postMessage(message); +} + /******************************************* *** Utilities to manage keys/randomness *** *******************************************/ function getRandomBytes(size) { //TODO: Better random (https://www.grc.com/r&d/js.htm?) try { - var array = new Uint8Array(size); + var buffer = new ArrayBuffer(size); + var array = new Uint8Array(buffer); window.crypto.getRandomValues(array); - return array; + return buffer; } catch (err) { //TODO: ummm...wat? throw err; @@ -254,23 +290,26 @@ function getRandomBytes(size) { } (function(crypto, $, undefined) { - var createNewKeyPair = function() { + var createNewKeyPair = function(callback) { //TODO var privKey = getRandomBytes(32); privKey[0] &= 248; privKey[31] &= 127; privKey[31] |= 64; - var pubKey = "BRTJzsHPUWRRBxyo5MoaBRidMk2fwDlfqvU91b6pzbED"; - var privKey = ""; - return { pubKey: pubKey, privKey: privKey }; + postNaclMessage({command: "bytesToPriv", priv: privKey}, function(message) { + postNaclMessage({command: "privToPub", priv: message.res}, function(message) { + callback({ pubKey: message.res, privKey: privKey }); + }); + }); } var crypto_storage = {}; - crypto_storage.getNewPubKeySTORINGPrivKey = function(keyName) { - var keyPair = createNewKeyPair(); - storage.putEncrypted("25519Key" + keyName, keyPair); - return keyPair.pubKey; + crypto_storage.getNewPubKeySTORINGPrivKey = function(keyName, callback) { + createNewKeyPair(function(keyPair) { + storage.putEncrypted("25519Key" + keyName, keyPair); + callback(keyPair.pubKey); + }); } crypto_storage.getStoredPubKey = function(keyName) { @@ -379,7 +418,7 @@ function getRandomBytes(size) { chain.chainKey.counter = counter; } - var maybeStepRatchet = function(session, remoteKey, previousCounter) { + var maybeStepRatchet = function(session, remoteKey, previousCounter, callback) { if (sesion[remoteKey] !== undefined) //TODO: null??? return; @@ -397,12 +436,16 @@ function getRandomBytes(size) { var masterKey = HKDF(ECDHE(remoteKey, ratchet.ephemeralKeyPair.privKey), ratchet.rootKey, "WhisperRatchet"); session[remoteKey] = { messageKeys: {}, chainKey: { counter: 0, key: masterKey.substring(32, 64) } }; - ratchet.ephemeralKeyPair = createNewKeyPair(); - masterKey = HKDF(ECDHE(remoteKey, ratchet.ephemeralKeyPair.privKey), masterKey.substring(0, 32), "WhisperRatchet"); - ratchet.rootKey = masterKey.substring(0, 32); - session[nextRatchet.ephemeralKeyPair.pubKey] = { messageKeys: {}, chainKey: { counter: 0, key: masterKey.substring(32, 64) } }; + createNewKeyPair(function(keyPair) { + ratchet.ephemeralKeyPair = keyPair; - ratchet.lastRemoteEphemeralKey = remoteKey; + masterKey = HKDF(ECDHE(remoteKey, ratchet.ephemeralKeyPair.privKey), masterKey.substring(0, 32), "WhisperRatchet"); + ratchet.rootKey = masterKey.substring(0, 32); + session[nextRatchet.ephemeralKeyPair.pubKey] = { messageKeys: {}, chainKey: { counter: 0, key: masterKey.substring(32, 64) } }; + + ratchet.lastRemoteEphemeralKey = remoteKey; + callback(); + }); } var doDecryptWhisperMessage = function(ciphertext, mac, messageKey, counter) { @@ -414,7 +457,7 @@ function getRandomBytes(size) { } // returns decrypted protobuf - var decryptWhisperMessage = function(encodedNumber, messageBytes) { + var decryptWhisperMessage = function(encodedNumber, messageBytes, callback) { var session = crypto_storage.getSession(encodedNumber); if (session === undefined) throw "No session currently open with " + encodedNumber; @@ -427,18 +470,19 @@ function getRandomBytes(size) { var message = decodeWhisperMessageProtobuf(messageProto); - maybeStepRatchet(session, getString(message.ephemeralKey), message.previousCounter); - var chain = session[getString(message.ephemeralKey)]; + maybeStepRatchet(session, getString(message.ephemeralKey), message.previousCounter, function() { + var chain = session[getString(message.ephemeralKey)]; - fillMessageKeys(chain, message.counter); + fillMessageKeys(chain, message.counter); - var plaintext = doDecryptWhisperMessage(message.ciphertext, mac, chain.messageKeys[message.counter], message.counter); - delete chain.messageKeys[message.counter]; + var plaintext = doDecryptWhisperMessage(message.ciphertext, mac, chain.messageKeys[message.counter], message.counter); + delete chain.messageKeys[message.counter]; - removeOldChains(session); + removeOldChains(session); - crypto_storage.saveSession(encodedNumber, session); - return decodePushMessageContentProtobuf(atob(plaintext)); + crypto_storage.saveSession(encodedNumber, session); + callback(decodePushMessageContentProtobuf(atob(plaintext))); + }); } /************************* @@ -477,18 +521,18 @@ function getRandomBytes(size) { return atob(plaintext.toString(CryptoJS.enc.Base64)); } - crypto.handleIncomingPushMessageProto = function(proto) { + crypto.handleIncomingPushMessageProto = function(proto, callback) { switch(proto.type) { case 0: //TYPE_MESSAGE_PLAINTEXT - proto.message = decodePushMessageContentProtobuf(getString(proto.message)); + callback(decodePushMessageContentProtobuf(getString(proto.message))); break; case 1: //TYPE_MESSAGE_CIPHERTEXT - proto.message = decryptWhisperMessage(proto.source, getString(proto.message)); + decryptWhisperMessage(proto.source, getString(proto.message), function(result) { callback(result); }); break; case 3: //TYPE_MESSAGE_PREKEY_BUNDLE var preKeyProto = decodePreKeyWhisperMessageProtobuf(getString(proto.message)); initSessionFromPreKeyWhisperMessage(proto.source, preKeyProto); - proto.message = decryptWhisperMessage(proto.source, getString(preKeyProto.message)); + decryptWhisperMessage(proto.source, getString(preKeyProto.message), function(result) { callback(result); }); break; } } @@ -499,26 +543,42 @@ function getRandomBytes(size) { } var GENERATE_KEYS_KEYS_GENERATED = 100; - crypto.generateKeys = function() { + crypto.generateKeys = function(callback) { var identityKey = crypto_storage.getStoredPubKey("identityKey"); + var identityKeyCalculated = function(pubKey) { + identityKey = pubKey; + + var firstKeyId = storage.getEncrypted("maxPreKeyId", -1) + 1; + storage.putEncrypted("maxPreKeyId", firstKeyId + GENERATE_KEYS_KEYS_GENERATED); + + if (firstKeyId > 16777000) + throw "You crazy motherfucker"; + + var keys = {}; + keys.keys = []; + var keysLeft = GENERATE_KEYS_KEYS_GENERATED; + for (var i = firstKeyId; i < firstKeyId + GENERATE_KEYS_KEYS_GENERATED; i++) { + crypto_storage.getNewPubKeySTORINGPrivKey("preKey" + i, function(pubKey) { + keys.keys[i] = {keyId: i, publicKey: pubKey, identityKey: identityKey}; + keysLeft--; + if (keysLeft == 0) { + // 0xFFFFFF == 16777215 + keys.lastResortKey = {keyId: 16777215, publicKey: crypto_storage.getStoredPubKey("preKey16777215"), identityKey: identityKey};//TODO: Rotate lastResortKey + if (keys.lastResortKey.publicKey === undefined) { + crypto_storage.getNewPubKeySTORINGPrivKey("preKey16777215", function(pubKey) { + keys.lastResortKey.publicKey = pubKey; + callback(keys); + }); + } else + callback(keys); + } + }); + } + } if (identityKey === undefined) - identityKey = crypto_storage.getNewPubKeySTORINGPrivKey("identityKey"); //TODO: should probably just throw? - - var firstKeyId = storage.getEncrypted("maxPreKeyId", -1) + 1; - storage.putEncrypted("maxPreKeyId", firstKeyId + GENERATE_KEYS_KEYS_GENERATED); - - if (firstKeyId > 16777000) - throw "You crazy motherfucker"; - - var keys = {}; - keys.keys = []; - for (var i = firstKeyId; i < firstKeyId + GENERATE_KEYS_KEYS_GENERATED; i++) - keys.keys[i] = {keyId: i, publicKey: crypto_storage.getNewPubKeySTORINGPrivKey("preKey" + i), identityKey: identityKey}; - // 0xFFFFFF == 16777215 - keys.lastResortKey = {keyId: 16777215, publicKey: crypto_storage.getStoredPubKey("preKey16777215"), identityKey: identityKey};//TODO: Rotate lastResortKey - if (keys.lastResortKey.publicKey === undefined) - keys.lastResortKey.publicKey = crypto_storage.getNewPubKeySTORINGPrivKey("preKey16777215"); - return keys; + crypto_storage.getNewPubKeySTORINGPrivKey("identityKey", function(pubKey) { identityKeyCalculated(pubKey); }); + else + identityKeyCalculated(pubKey); } }( window.crypto = window.crypto || {}, jQuery )); @@ -621,9 +681,9 @@ function subscribeToPush(message_callback) { } try { - crypto.handleIncomingPushMessageProto(proto); // Decrypts/decodes/fills in fields/etc - - message_callback(proto); + crypto.handleIncomingPushMessageProto(proto, function(decrypted) { + message_callback(decrypted); + }); // Decrypts/decodes/fills in fields/etc } catch (e) { //TODO: Tell the user decryption failed } @@ -739,8 +799,8 @@ function sendMessageToNumbers(numbers, message, success_callback, error_callback }, error_callback); } - function requestIdentityPrivKeyFromMasterDevice(number, identityKey) { sendMessageToDevices([getDeviceObject(getNumberFromString(number)) + ".1"], {message: "Identity Key request"}, function() {}, function() {});//TODO } + diff --git a/js/options.js b/js/options.js index eadccd80..5f4bb587 100644 --- a/js/options.js +++ b/js/options.js @@ -74,17 +74,18 @@ $('#init-go').click(function() { var register_keys_func = function() { $('#verify2done').html('done'); - var keys = crypto.generateKeys(); - $('#verify3done').html('done'); - doAjax({call: 'keys', httpType: 'PUT', do_auth: true, jsonData: keys, - success_callback: function(response) { - $('#complete-number').html(number); - $('#verify').hide(); - $('#setup-complete').show(); - registrationDone(); - }, error_callback: function(code) { - alert(code); //TODO - } + crypto.generateKeys(function(keys) { + $('#verify3done').html('done'); + doAjax({call: 'keys', httpType: 'PUT', do_auth: true, jsonData: keys, + success_callback: function(response) { + $('#complete-number').html(number); + $('#verify').hide(); + $('#setup-complete').show(); + registrationDone(); + }, error_callback: function(code) { + alert(code); //TODO + } + }); }); } @@ -120,9 +121,11 @@ $('#init-go').click(function() { } }); -if (!isRegistrationDone()) { - $('#init-setup').show(); -} else { - $('#complete-number').html(storage.getUnencrypted("number_id").split(".")[0]); - $('#setup-complete').show(); -} +registerOnLoadFunction(function() { + if (!isRegistrationDone()) { + $('#init-setup').show(); + } else { + $('#complete-number').html(storage.getUnencrypted("number_id").split(".")[0]); + $('#setup-complete').show(); + } +}); diff --git a/js/popup.js b/js/popup.js index feea9bfa..7fe0d000 100644 --- a/js/popup.js +++ b/js/popup.js @@ -7,55 +7,57 @@ $('#send_link').onclick = function() { $('#send').show(); } -if (storage.getUnencrypted("number_id") === undefined) { - chrome.tabs.create({url: "options.html"}); -} else { - function fillMessages() { - var MAX_MESSAGES_PER_CONVERSATION = 4; - var MAX_CONVERSATIONS = 5; +registerOnLoadFunction(function() { + if (storage.getUnencrypted("number_id") === undefined) { + chrome.tabs.create({url: "options.html"}); + } else { + function fillMessages() { + var MAX_MESSAGES_PER_CONVERSATION = 4; + var MAX_CONVERSATIONS = 5; - var conversations = []; + var conversations = []; - var messageMap = getMessageMap(); - for (conversation in messageMap) { - var messages = messageMap[conversation]; - messages.sort(function(a, b) { return b.timestamp - a.timestamp; }); - conversations[conversations.length] = messages; - } - - conversations.sort(function(a, b) { return b[0].timestamp - a[0].timestamp }); - - var ul = $('#messages'); - ul.html(''); - for (var i = 0; i < MAX_CONVERSATIONS && i < conversations.length; i++) { - var conversation = conversations[i]; - ul.append('
  • '); - for (var j = 0; j < MAX_MESSAGES_PER_CONVERSATION && j < conversation.length; j++) { - var message = conversation[j]; - ul.append("From: " + message.sender + ", at: " + timestampToHumanReadable(message.timestamp) + "
    "); - ul.append("Message: " + message.message + "

    "); + var messageMap = getMessageMap(); + for (conversation in messageMap) { + var messages = messageMap[conversation]; + messages.sort(function(a, b) { return b.timestamp - a.timestamp; }); + conversations[conversations.length] = messages; } - ul.append("
    "); - $('#button' + i).click(function() { - var sendDestinations = [conversation[0].sender]; - for (var j = 0; j < conversation[0].destinations.length; j++) - sendDestinations[sendDestinations.length] = conversation[0].destinations[j]; - sendMessageToNumbers(sendDestinations, { message: $('#text' + i).val() }, function(result) { - console.log("Sent message: " + JSON.stringify(result)); - }, function(error_msg) { - alert(error_msg); //TODO - }); - }); - ul.append('
  • '); - } - } - $(window).bind('storage', function(e) { - console.log("Got localStorage update for key " + e.key); - if (event.key == "emessageMap")//TODO: Fix when we get actual encryption - fillMessages(); - }); - fillMessages(); - storage.putUnencrypted("unreadCount", 0); - chrome.browserAction.setBadgeText({text: ""}); -} + conversations.sort(function(a, b) { return b[0].timestamp - a[0].timestamp }); + + var ul = $('#messages'); + ul.html(''); + for (var i = 0; i < MAX_CONVERSATIONS && i < conversations.length; i++) { + var conversation = conversations[i]; + ul.append('
  • '); + for (var j = 0; j < MAX_MESSAGES_PER_CONVERSATION && j < conversation.length; j++) { + var message = conversation[j]; + ul.append("From: " + message.sender + ", at: " + timestampToHumanReadable(message.timestamp) + "
    "); + ul.append("Message: " + message.message + "

    "); + } + ul.append("
    "); + $('#button' + i).click(function() { + var sendDestinations = [conversation[0].sender]; + for (var j = 0; j < conversation[0].destinations.length; j++) + sendDestinations[sendDestinations.length] = conversation[0].destinations[j]; + sendMessageToNumbers(sendDestinations, { message: $('#text' + i).val() }, function(result) { + console.log("Sent message: " + JSON.stringify(result)); + }, function(error_msg) { + alert(error_msg); //TODO + }); + }); + ul.append('
  • '); + } + } + + $(window).bind('storage', function(e) { + console.log("Got localStorage update for key " + e.key); + if (event.key == "emessageMap")//TODO: Fix when we get actual encryption + fillMessages(); + }); + fillMessages(); + storage.putUnencrypted("unreadCount", 0); + chrome.browserAction.setBadgeText({text: ""}); + } +}); diff --git a/js/test.js b/js/test.js index aaf67885..7c5165d6 100644 --- a/js/test.js +++ b/js/test.js @@ -1,37 +1,62 @@ // Setup dumb test wrapper var testsdiv = $('#tests'); +var testsOutstanding = []; function TEST(func, name) { var funcName = name === undefined ? func + "" : name; - try { - if (func()) + var testIndex = testsOutstanding.length; + function callback(result) { + if (result) testsdiv.append('

    ' + funcName + ' passed

    '); else testsdiv.append('

    ' + funcName + ' returned false

    '); + delete testsOutstanding[testIndex]; + } + try { + testsOutstanding[testIndex] = funcName; + func(callback); } catch (e) { testsdiv.append('

    ' + funcName + ' threw ' + e + '

    '); } } -// Random tests to check my JS knowledge -TEST(function() { return !objectContainsKeys({}); }); -TEST(function() { return objectContainsKeys({ a: undefined }); }); -TEST(function() { return objectContainsKeys({ a: null }); }); +registerOnLoadFunction(function() { + localStorage.clear(); -// Basic sanity-checks on the crypto library -TEST(function() { - var PushMessageProto = dcodeIO.ProtoBuf.loadProtoFile("IncomingPushMessageSignal.proto").build("textsecure.PushMessageContent"); - var IncomingMessageProto = dcodeIO.ProtoBuf.loadProtoFile("IncomingPushMessageSignal.proto").build("textsecure.IncomingPushMessageSignal"); + // Random tests to check my JS knowledge + TEST(function(callback) { callback(!objectContainsKeys({})); }); + TEST(function(callback) { callback(objectContainsKeys({ a: undefined })); }); + TEST(function(callback) { callback(objectContainsKeys({ a: null })); }); - var text_message = new PushMessageProto(); - text_message.body = "Hi Mom"; - var server_message = {type: 0, // unencrypted - source: "+19999999999", timestamp: 42, message: text_message.encode() }; + // Basic sanity-checks on the crypto library + TEST(function(callback) { + var PushMessageProto = dcodeIO.ProtoBuf.loadProtoFile("protos/IncomingPushMessageSignal.proto").build("textsecure.PushMessageContent"); + var IncomingMessageProto = dcodeIO.ProtoBuf.loadProtoFile("protos/IncomingPushMessageSignal.proto").build("textsecure.IncomingPushMessageSignal"); - crypto.handleIncomingPushMessageProto(server_message); - return server_message.message.body == text_message.body && - server_message.message.attachments.length == text_message.attachments.length && - text_message.attachments.length == 0; -}, 'Unencrypted PushMessageProto "decrypt"'); + var text_message = new PushMessageProto(); + text_message.body = "Hi Mom"; + var server_message = {type: 0, // unencrypted + source: "+19999999999", timestamp: 42, message: text_message.encode() }; -// TODO: Run through the test vectors for the axolotl ratchet + crypto.handleIncomingPushMessageProto(server_message, function(message) { + callback(message.body == text_message.body && + message.attachments.length == text_message.attachments.length && + text_message.attachments.length == 0); + }); + }, 'Unencrypted PushMessageProto "decrypt"'); + TEST(function(callback) { + crypto.generateKeys(function() { + callback(true); + }); + }, "Test simple create key"); + + // TODO: Run through the test vectors for the axolotl ratchet + + window.setTimeout(function() { + for (var i = 0; i < testsOutstanding.length; i++) + if (testsOutstanding[i] !== undefined) + testsdiv.append('

    ' + testsOutstanding[i] + ' timed out

    '); + + localStorage.clear(); + }, 1000); +}); diff --git a/manifest.json b/manifest.json index 69b48363..1e633d53 100644 --- a/manifest.json +++ b/manifest.json @@ -16,8 +16,9 @@ }, "background": { - "scripts": [ "js-deps/jquery.js", "js-deps/jquery.atmosphere.js", "js-deps/aes.js", "js-deps/hmac-sha256.js", "js-deps/lib-typedarrays.js", - "js-deps/Long.min.js", "js-deps/ByteBuffer.min.js", "js-deps/ProtoBuf.min.js", "js/helpers.js", "js/background.js" ] +// "scripts": [ "js-deps/jquery.js", "js-deps/jquery.atmosphere.js", "js-deps/aes.js", "js-deps/hmac-sha256.js", "js-deps/lib-typedarrays.js", +// "js-deps/Long.min.js", "js-deps/ByteBuffer.min.js", "js-deps/ProtoBuf.min.js", "js/helpers.js", "js/background.js" ] + "page": "background.html" }, "options_page": "options.html" diff --git a/options.html b/options.html index d655c911..55448b52 100644 --- a/options.html +++ b/options.html @@ -3,7 +3,10 @@ TextSecure Options - + +
    +
    +

    TextSecure

    + diff --git a/popup.html b/popup.html index b367f6eb..4891624c 100644 --- a/popup.html +++ b/popup.html @@ -1,7 +1,10 @@ - + +
    +
    + Inbox Send
    @@ -10,6 +13,7 @@
    + diff --git a/test.html b/test.html index 841cfbc0..d316cf3b 100644 --- a/test.html +++ b/test.html @@ -1,21 +1,25 @@ TextSecure test runner - + +
    +
    -

    Run this out of the chrome-plugin:// namespace (and expect plugin state to be cleared/corrupted), not file://

    -
    -
    +

    Run this out of the chrome-plugin:// namespace (and expect plugin state to be cleared/corrupted), not file://

    +
    +
    + + + + + + + + + + + + - - - - - - - - - -