diff --git a/.gitignore b/.gitignore index a3758d8db8..5c22dd338c 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ signing.properties gradle gradlew gradlew.bat +library/lib/ +library/obj/ diff --git a/library/build.gradle b/library/build.gradle index 46facf9be9..cd7eab85fc 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -22,7 +22,8 @@ dependencies { compile 'com.google.protobuf:protobuf-java:2.4.1' compile 'com.madgag:sc-light-jdk15on:1.47.0.2' compile 'com.googlecode.libphonenumber:libphonenumber:5.3' - compile 'org.whispersystems:gson:2.1' + compile 'org.whispersystems:gson:2.2.4' + compile fileTree(dir: 'libs', include: 'armeabi.jar') } android { diff --git a/library/jni/Android.mk b/library/jni/Android.mk new file mode 100644 index 0000000000..20211e82e7 --- /dev/null +++ b/library/jni/Android.mk @@ -0,0 +1,17 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := libcurve25519-donna +LOCAL_SRC_FILES := curve25519-donna.c + +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_MODULE := libcurve25519 +LOCAL_SRC_FILES := curve25519-donna-jni.c + +LOCAL_STATIC_LIBRARIES := libcurve25519-donna + +include $(BUILD_SHARED_LIBRARY) \ No newline at end of file diff --git a/library/jni/curve25519-donna-jni.c b/library/jni/curve25519-donna-jni.c new file mode 100644 index 0000000000..bcda8a4a5a --- /dev/null +++ b/library/jni/curve25519-donna-jni.c @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include +#include "curve25519-donna.h" + +JNIEXPORT jbyteArray JNICALL Java_org_whispersystems_textsecure_crypto_ecc_Curve25519_generatePrivateKey + (JNIEnv *env, jclass clazz, jbyteArray random) +{ + uint8_t* privateKey = (uint8_t*)(*env)->GetByteArrayElements(env, random, 0); + + privateKey[0] &= 248; + privateKey[31] &= 127; + privateKey[31] |= 64; + + (*env)->ReleaseByteArrayElements(env, random, privateKey, 0); + + return random; +} + +JNIEXPORT jbyteArray JNICALL Java_org_whispersystems_textsecure_crypto_ecc_Curve25519_generatePublicKey + (JNIEnv *env, jclass clazz, jbyteArray privateKey) +{ + static const uint8_t basepoint[32] = {9}; + + jbyteArray publicKey = (*env)->NewByteArray(env, 32); + uint8_t* publicKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, publicKey, 0); + uint8_t* privateKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, privateKey, 0); + + curve25519_donna(publicKeyBytes, privateKeyBytes, basepoint); + + (*env)->ReleaseByteArrayElements(env, publicKey, publicKeyBytes, 0); + (*env)->ReleaseByteArrayElements(env, privateKey, privateKeyBytes, 0); + + return publicKey; +} + +JNIEXPORT jbyteArray JNICALL Java_org_whispersystems_textsecure_crypto_ecc_Curve25519_calculateAgreement + (JNIEnv *env, jclass clazz, jbyteArray privateKey, jbyteArray publicKey) +{ + jbyteArray sharedKey = (*env)->NewByteArray(env, 32); + uint8_t* sharedKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, sharedKey, 0); + uint8_t* privateKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, privateKey, 0); + uint8_t* publicKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, publicKey, 0); + + curve25519_donna(sharedKeyBytes, privateKeyBytes, publicKeyBytes); + + (*env)->ReleaseByteArrayElements(env, sharedKey, sharedKeyBytes, 0); + (*env)->ReleaseByteArrayElements(env, publicKey, publicKeyBytes, 0); + (*env)->ReleaseByteArrayElements(env, privateKey, privateKeyBytes, 0); + + return sharedKey; +} diff --git a/library/jni/curve25519-donna.c b/library/jni/curve25519-donna.c new file mode 100644 index 0000000000..bb1262e5e9 --- /dev/null +++ b/library/jni/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/library/jni/curve25519-donna.h b/library/jni/curve25519-donna.h new file mode 100644 index 0000000000..3e4dfcee04 --- /dev/null +++ b/library/jni/curve25519-donna.h @@ -0,0 +1,6 @@ +#ifndef CURVE25519_DONNA_H +#define CURVE25519_DONNA_H + +extern int curve25519_donna(uint8_t *, const uint8_t *, const uint8_t *); + +#endif diff --git a/library/libs/armeabi.jar b/library/libs/armeabi.jar new file mode 100644 index 0000000000..b34bd1237c Binary files /dev/null and b/library/libs/armeabi.jar differ diff --git a/library/libs/libphonenumber-5.3.jar b/library/libs/libphonenumber-5.3.jar deleted file mode 100644 index 7be725cc63..0000000000 Binary files a/library/libs/libphonenumber-5.3.jar and /dev/null differ diff --git a/library/libs/protobuf-java-2.4.1.jar b/library/libs/protobuf-java-2.4.1.jar deleted file mode 100644 index 1373fa424d..0000000000 Binary files a/library/libs/protobuf-java-2.4.1.jar and /dev/null differ diff --git a/library/libs/sc-light-jdk15on-1.47.0.2.jar b/library/libs/sc-light-jdk15on-1.47.0.2.jar deleted file mode 100644 index 7cdbf856e5..0000000000 Binary files a/library/libs/sc-light-jdk15on-1.47.0.2.jar and /dev/null differ diff --git a/library/libs/thoughtcrimegson-2.1.jar b/library/libs/thoughtcrimegson-2.1.jar deleted file mode 100644 index 8406d51d03..0000000000 Binary files a/library/libs/thoughtcrimegson-2.1.jar and /dev/null differ diff --git a/library/src/org/whispersystems/textsecure/crypto/IdentityKey.java b/library/src/org/whispersystems/textsecure/crypto/IdentityKey.java index 2ea71a7fa2..742d716d58 100644 --- a/library/src/org/whispersystems/textsecure/crypto/IdentityKey.java +++ b/library/src/org/whispersystems/textsecure/crypto/IdentityKey.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Whisper Systems + * Copyright (C) 2013 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,8 +20,10 @@ package org.whispersystems.textsecure.crypto; import android.os.Parcel; import android.os.Parcelable; -import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; import org.whispersystems.textsecure.util.Hex; +import org.whispersystems.textsecure.util.Util; /** * A class for representing an identity key. @@ -44,15 +47,15 @@ public class IdentityKey implements Parcelable, SerializableKey { } }; - public static final int SIZE = 1 + KeyUtil.POINT_SIZE; - private static final int VERSION = 1; - - private ECPublicKeyParameters publicKey; - - public IdentityKey(ECPublicKeyParameters publicKey) { + public static final int SIZE = 1 + ECPublicKey.KEY_SIZE; + private static final int CURRENT_VESION = 1; + + private ECPublicKey publicKey; + + public IdentityKey(ECPublicKey publicKey) { this.publicKey = publicKey; } - + public IdentityKey(Parcel in) throws InvalidKeyException { int length = in.readInt(); byte[] serialized = new byte[length]; @@ -64,43 +67,42 @@ public class IdentityKey implements Parcelable, SerializableKey { public IdentityKey(byte[] bytes, int offset) throws InvalidKeyException { initializeFromSerialized(bytes, offset); } - - public ECPublicKeyParameters getPublicKeyParameters() { - return this.publicKey; + + public ECPublicKey getPublicKey() { + return publicKey; } - + private void initializeFromSerialized(byte[] bytes, int offset) throws InvalidKeyException { - int version = bytes[offset] & 0xff; + int version = bytes[offset] & 0xff; - if (version > VERSION) + if (version > CURRENT_VESION) throw new InvalidKeyException("Unsupported key version: " + version); - this.publicKey = KeyUtil.decodePoint(bytes, offset+1); + this.publicKey = Curve.decodePoint(bytes, offset + 1); } public byte[] serialize() { - byte[] encodedKey = KeyUtil.encodePoint(publicKey.getQ()); - byte[] combined = new byte[1 + encodedKey.length]; - - combined[0] = (byte)VERSION; - System.arraycopy(encodedKey, 0, combined, 1, encodedKey.length); - - return combined; + byte[] versionBytes = {(byte)CURRENT_VESION}; + byte[] encodedKey = publicKey.serialize(); + + return Util.combine(versionBytes, encodedKey); } - + public String getFingerprint() { - return Hex.toString(serialize()); + return Hex.toString(publicKey.serialize()); } @Override public boolean equals(Object other) { + if (other == null) return false; if (!(other instanceof IdentityKey)) return false; - return publicKey.getQ().equals(((IdentityKey)other).publicKey.getQ()); + + return publicKey.equals(((IdentityKey) other).getPublicKey()); } @Override public int hashCode() { - return publicKey.getQ().hashCode(); + return publicKey.hashCode(); } public int describeContents() { diff --git a/library/src/org/whispersystems/textsecure/crypto/IdentityKeyPair.java b/library/src/org/whispersystems/textsecure/crypto/IdentityKeyPair.java index 643e752f59..390fbe208f 100644 --- a/library/src/org/whispersystems/textsecure/crypto/IdentityKeyPair.java +++ b/library/src/org/whispersystems/textsecure/crypto/IdentityKeyPair.java @@ -16,7 +16,7 @@ */ package org.whispersystems.textsecure.crypto; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey; /** * Holder for public and private identity key pair. @@ -26,9 +26,9 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters; public class IdentityKeyPair { private final IdentityKey publicKey; - private final ECPrivateKeyParameters privateKey; + private final ECPrivateKey privateKey; - public IdentityKeyPair(IdentityKey publicKey, ECPrivateKeyParameters privateKey) { + public IdentityKeyPair(IdentityKey publicKey, ECPrivateKey privateKey) { this.publicKey = publicKey; this.privateKey = privateKey; } @@ -37,7 +37,7 @@ public class IdentityKeyPair { return publicKey; } - public ECPrivateKeyParameters getPrivateKey() { + public ECPrivateKey getPrivateKey() { return privateKey; } } diff --git a/library/src/org/whispersystems/textsecure/crypto/KeyPair.java b/library/src/org/whispersystems/textsecure/crypto/KeyPair.java index 4795e637e2..fb8c1d748e 100644 --- a/library/src/org/whispersystems/textsecure/crypto/KeyPair.java +++ b/library/src/org/whispersystems/textsecure/crypto/KeyPair.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Whisper Systems + * Copyright (C) 2013 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,13 +17,13 @@ */ package org.whispersystems.textsecure.crypto; -import org.spongycastle.crypto.AsymmetricCipherKeyPair; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; -import org.spongycastle.crypto.params.ECPublicKeyParameters; -import org.whispersystems.textsecure.util.Hex; - import android.util.Log; +import org.whispersystems.textsecure.crypto.ecc.ECKeyPair; +import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey; +import org.whispersystems.textsecure.util.Hex; +import org.whispersystems.textsecure.util.Util; + /** * Represents a session's active KeyPair. * @@ -31,15 +32,15 @@ import android.util.Log; public class KeyPair { - private ECPrivateKeyParameters privateKey; - private PublicKey publicKey; - + private PublicKey publicKey; + private ECPrivateKey privateKey; + private final MasterCipher masterCipher; - public KeyPair(int keyPairId, AsymmetricCipherKeyPair keyPair, MasterSecret masterSecret) { + public KeyPair(int keyPairId, ECKeyPair keyPair, MasterSecret masterSecret) { this.masterCipher = new MasterCipher(masterSecret); - this.publicKey = new PublicKey(keyPairId, (ECPublicKeyParameters)keyPair.getPublic()); - this.privateKey = (ECPrivateKeyParameters)keyPair.getPrivate(); + this.publicKey = new PublicKey(keyPairId, keyPair.getPublicKey()); + this.privateKey = keyPair.getPrivateKey(); } public KeyPair(byte[] bytes, MasterCipher masterCipher) throws InvalidKeyException { @@ -54,11 +55,11 @@ public class KeyPair { public PublicKey getPublicKey() { return publicKey; } - - public AsymmetricCipherKeyPair getKeyPair() { - return new AsymmetricCipherKeyPair(publicKey.getKey(), privateKey); + + public ECPrivateKey getPrivateKey() { + return privateKey; } - + public byte[] toBytes() { return serialize(); } @@ -67,18 +68,14 @@ public class KeyPair { this.publicKey = new PublicKey(bytes); byte[] privateKeyBytes = new byte[bytes.length - PublicKey.KEY_SIZE]; System.arraycopy(bytes, PublicKey.KEY_SIZE, privateKeyBytes, 0, privateKeyBytes.length); - this.privateKey = masterCipher.decryptKey(privateKeyBytes); + this.privateKey = masterCipher.decryptKey(this.publicKey.getType(), privateKeyBytes); } public byte[] serialize() { byte[] publicKeyBytes = publicKey.serialize(); Log.w("KeyPair", "Serialized public key bytes: " + Hex.toString(publicKeyBytes)); - byte[] privateKeyBytes = masterCipher.encryptKey(privateKey); - byte[] combined = new byte[publicKeyBytes.length + privateKeyBytes.length]; - System.arraycopy(publicKeyBytes, 0, combined, 0, publicKeyBytes.length); - System.arraycopy(privateKeyBytes, 0, combined, publicKeyBytes.length, privateKeyBytes.length); - - return combined; + byte[] privateKeyBytes = masterCipher.encryptKey(privateKey); + return Util.combine(publicKeyBytes, privateKeyBytes); } } diff --git a/library/src/org/whispersystems/textsecure/crypto/KeyUtil.java b/library/src/org/whispersystems/textsecure/crypto/KeyUtil.java index a08c18d83a..e720d1b7a7 100644 --- a/library/src/org/whispersystems/textsecure/crypto/KeyUtil.java +++ b/library/src/org/whispersystems/textsecure/crypto/KeyUtil.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2011 Whisper Systems + * Copyright (C) 2013 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,26 +16,17 @@ */ package org.whispersystems.textsecure.crypto; -import java.math.BigInteger; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; +import android.content.Context; +import android.util.Log; -import org.spongycastle.crypto.AsymmetricCipherKeyPair; -import org.spongycastle.crypto.agreement.ECDHBasicAgreement; -import org.spongycastle.crypto.generators.ECKeyPairGenerator; -import org.spongycastle.crypto.params.ECDomainParameters; -import org.spongycastle.crypto.params.ECKeyGenerationParameters; -import org.spongycastle.crypto.params.ECPublicKeyParameters; -import org.spongycastle.math.ec.ECCurve; -import org.spongycastle.math.ec.ECFieldElement; -import org.spongycastle.math.ec.ECPoint; +import org.whispersystems.textsecure.crypto.ecc.Curve; import org.whispersystems.textsecure.storage.CanonicalRecipientAddress; import org.whispersystems.textsecure.storage.LocalKeyRecord; import org.whispersystems.textsecure.storage.RemoteKeyRecord; import org.whispersystems.textsecure.storage.SessionRecord; -import android.content.Context; -import android.util.Log; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; /** * Helper class for generating key pairs and calculating ECDH agreements. @@ -45,52 +36,6 @@ import android.util.Log; public class KeyUtil { - public static final BigInteger q = new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16); - private static final BigInteger a = new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16); - private static final BigInteger b = new BigInteger("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", 16); - private static final BigInteger n = new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16); - - private static final ECFieldElement x = new ECFieldElement.Fp(q, new BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16)); - private static final ECFieldElement y = new ECFieldElement.Fp(q, new BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16)); - - private static final ECCurve curve = new ECCurve.Fp(q, a, b); - private static final ECPoint g = new ECPoint.Fp(curve, x, y, true); - - public static final int POINT_SIZE = 33; - - public static final ECDomainParameters domainParameters = new ECDomainParameters(curve, g, n); - - public static byte[] encodePoint(ECPoint point) { - synchronized (curve) { - return point.getEncoded(); - } - } - - public static ECPublicKeyParameters decodePoint(byte[] encoded, int offset) - throws InvalidKeyException - { - byte[] pointBytes = new byte[POINT_SIZE]; - System.arraycopy(encoded, offset, pointBytes, 0, pointBytes.length); - - synchronized (curve) { - ECPoint Q; - - try { - Q = curve.decodePoint(pointBytes); - } catch (RuntimeException re) { - throw new InvalidKeyException(re); - } - - return new ECPublicKeyParameters(Q, KeyUtil.domainParameters); - } - } - - public static BigInteger calculateAgreement(ECDHBasicAgreement agreement, ECPublicKeyParameters remoteKey) { - synchronized (curve) { - return agreement.calculateAgreement(remoteKey); - } - } - public static void abortSessionFor(Context context, CanonicalRecipientAddress recipient) { //XXX Obviously we should probably do something more thorough here eventually. Log.w("KeyUtil", "Aborting session, deleting keys..."); @@ -120,17 +65,18 @@ public class KeyUtil { new SessionRecord(context, masterSecret, recipient).getIdentityKey() != null; } - public static LocalKeyRecord initializeRecordFor(CanonicalRecipientAddress recipient, - Context context, - MasterSecret masterSecret) + public static LocalKeyRecord initializeRecordFor(Context context, + MasterSecret masterSecret, + CanonicalRecipientAddress recipient, + int sessionVersion) { Log.w("KeyUtil", "Initializing local key pairs..."); try { SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); int initialId = secureRandom.nextInt(4094) + 1; - KeyPair currentPair = new KeyPair(initialId, KeyUtil.generateKeyPair(), masterSecret); - KeyPair nextPair = new KeyPair(initialId + 1, KeyUtil.generateKeyPair(), masterSecret); + KeyPair currentPair = new KeyPair(initialId, Curve.generateKeyPairForSession(sessionVersion), masterSecret); + KeyPair nextPair = new KeyPair(initialId + 1, Curve.generateKeyPairForSession(sessionVersion), masterSecret); LocalKeyRecord record = new LocalKeyRecord(context, masterSecret, recipient); record.setCurrentKeyPair(currentPair); @@ -143,30 +89,4 @@ public class KeyUtil { } } - public static AsymmetricCipherKeyPair generateKeyPair() { - try { - synchronized (curve) { - ECKeyGenerationParameters keyParamters = new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")); - ECKeyPairGenerator generator = new ECKeyPairGenerator(); - generator.init(keyParamters); - - AsymmetricCipherKeyPair keyPair = generator.generateKeyPair(); - - return cloneKeyPairWithPointCompression(keyPair); - } - } catch (NoSuchAlgorithmException nsae) { - Log.w("keyutil", nsae); - return null; - } - } - - // This is dumb, but the ECPublicKeys that the generator makes by default don't have point compression - // turned on, and there's no setter. Great. - private static AsymmetricCipherKeyPair cloneKeyPairWithPointCompression(AsymmetricCipherKeyPair keyPair) { - ECPublicKeyParameters publicKey = (ECPublicKeyParameters)keyPair.getPublic(); - ECPoint q = publicKey.getQ(); - - return new AsymmetricCipherKeyPair(new ECPublicKeyParameters(new ECPoint.Fp(q.getCurve(), q.getX(), q.getY(), true), publicKey.getParameters()), keyPair.getPrivate()); - } - } diff --git a/library/src/org/whispersystems/textsecure/crypto/MasterCipher.java b/library/src/org/whispersystems/textsecure/crypto/MasterCipher.java index 8390c42a79..358563b9f8 100644 --- a/library/src/org/whispersystems/textsecure/crypto/MasterCipher.java +++ b/library/src/org/whispersystems/textsecure/crypto/MasterCipher.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Whisper Systems + * Copyright (C) 2013 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,8 +17,14 @@ */ package org.whispersystems.textsecure.crypto; +import android.util.Log; + +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey; +import org.whispersystems.textsecure.util.Base64; +import org.whispersystems.textsecure.util.Hex; + import java.io.IOException; -import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -32,12 +39,6 @@ import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; -import org.whispersystems.textsecure.util.Base64; -import org.whispersystems.textsecure.util.Hex; - -import android.util.Log; - /** * Class that handles encryption for local storage. * @@ -69,13 +70,11 @@ public class MasterCipher { throw new AssertionError(e); } } - - public byte[] encryptKey(ECPrivateKeyParameters params) { - BigInteger d = params.getD(); - byte[] dBytes = d.toByteArray(); - return encryptBytes(dBytes); + + public byte[] encryptKey(ECPrivateKey privateKey) { + return encryptBytes(privateKey.serialize()); } - + public String encryptBody(String body) { return encryptAndEncodeBytes(body.getBytes()); } @@ -84,13 +83,13 @@ public class MasterCipher { return new String(decodeAndDecryptBytes(body)); } - public ECPrivateKeyParameters decryptKey(byte[] key) { + public ECPrivateKey decryptKey(int type, byte[] key) + throws org.whispersystems.textsecure.crypto.InvalidKeyException + { try { - BigInteger d = new BigInteger(decryptBytes(key)); - return new ECPrivateKeyParameters(d, KeyUtil.domainParameters); + return Curve.decodePrivatePoint(type, decryptBytes(key)); } catch (InvalidMessageException ime) { - Log.w("bodycipher", ime); - return null; // XXX + throw new org.whispersystems.textsecure.crypto.InvalidKeyException(ime); } } diff --git a/library/src/org/whispersystems/textsecure/crypto/MessageCipher.java b/library/src/org/whispersystems/textsecure/crypto/MessageCipher.java index b1b654e684..79755a54a4 100644 --- a/library/src/org/whispersystems/textsecure/crypto/MessageCipher.java +++ b/library/src/org/whispersystems/textsecure/crypto/MessageCipher.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Whisper Systems + * Copyright (C) 2013 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -57,14 +58,12 @@ public class MessageCipher { try { CiphertextMessage message = new CiphertextMessage(ciphertext); - int messageVersion = message.getCurrentVersion(); - int supportedVersion = message.getSupportedVersion(); - int negotiatedVersion = Math.min(supportedVersion, CiphertextMessage.SUPPORTED_VERSION); - int senderKeyId = message.getSenderKeyId(); - int receiverKeyId = message.getReceiverKeyId(); - PublicKey nextRemoteKey = new PublicKey(message.getNextKeyBytes()); - int counter = message.getCounter(); - byte[] body = message.getBody(); + int messageVersion = message.getCurrentVersion(); + int senderKeyId = message.getSenderKeyId(); + int receiverKeyId = message.getReceiverKeyId(); + PublicKey nextRemoteKey = new PublicKey(message.getNextKeyBytes()); + int counter = message.getCounter(); + byte[] body = message.getBody(); SessionCipher sessionCipher = new SessionCipher(); SessionCipherContext sessionContext = sessionCipher.getDecryptionContext(context, masterSecret, @@ -73,8 +72,7 @@ public class MessageCipher { receiverKeyId, nextRemoteKey, counter, - messageVersion, - negotiatedVersion); + messageVersion); message.verifyMac(sessionContext); @@ -84,5 +82,4 @@ public class MessageCipher { } } } - } diff --git a/library/src/org/whispersystems/textsecure/crypto/PreKeyPair.java b/library/src/org/whispersystems/textsecure/crypto/PreKeyPair.java index 3b03500560..e4429b584d 100644 --- a/library/src/org/whispersystems/textsecure/crypto/PreKeyPair.java +++ b/library/src/org/whispersystems/textsecure/crypto/PreKeyPair.java @@ -1,40 +1,53 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.whispersystems.textsecure.crypto; -import org.spongycastle.crypto.AsymmetricCipherKeyPair; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; -import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.whispersystems.textsecure.crypto.ecc.ECKeyPair; +import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey; import org.whispersystems.textsecure.util.Util; public class PreKeyPair { - private final MasterCipher masterCipher; - private final ECPrivateKeyParameters privateKey; - private final PreKeyPublic publicKey; + private final MasterCipher masterCipher; + private final PreKeyPublic publicKey; + private final ECPrivateKey privateKey; - public PreKeyPair(MasterSecret masterSecret, AsymmetricCipherKeyPair keyPair) { + public PreKeyPair(MasterSecret masterSecret, ECKeyPair keyPair) { this.masterCipher = new MasterCipher(masterSecret); - this.publicKey = new PreKeyPublic((ECPublicKeyParameters)keyPair.getPublic()); - this.privateKey = (ECPrivateKeyParameters)keyPair.getPrivate(); + this.publicKey = new PreKeyPublic(keyPair.getPublicKey()); + this.privateKey = keyPair.getPrivateKey(); } public PreKeyPair(MasterSecret masterSecret, byte[] serialized) throws InvalidKeyException { - if (serialized.length < KeyUtil.POINT_SIZE + 1) - throw new InvalidKeyException("Serialized length: " + serialized.length); - - byte[] privateKeyBytes = new byte[serialized.length - KeyUtil.POINT_SIZE]; - System.arraycopy(serialized, KeyUtil.POINT_SIZE, privateKeyBytes, 0, privateKeyBytes.length); + byte[] privateKeyBytes = new byte[serialized.length - PreKeyPublic.KEY_SIZE]; + System.arraycopy(serialized, PreKeyPublic.KEY_SIZE, privateKeyBytes, 0, privateKeyBytes.length); this.masterCipher = new MasterCipher(masterSecret); this.publicKey = new PreKeyPublic(serialized, 0); - this.privateKey = masterCipher.decryptKey(privateKeyBytes); + this.privateKey = masterCipher.decryptKey(this.publicKey.getType(), privateKeyBytes); } public PreKeyPublic getPublicKey() { return publicKey; } - public AsymmetricCipherKeyPair getKeyPair() { - return new AsymmetricCipherKeyPair(publicKey.getPublicKey(), privateKey); + public ECKeyPair getKeyPair() { + return new ECKeyPair(publicKey.getPublicKey(), privateKey); } public byte[] serialize() { diff --git a/library/src/org/whispersystems/textsecure/crypto/PreKeyPublic.java b/library/src/org/whispersystems/textsecure/crypto/PreKeyPublic.java index e4f4e436c5..8b889b317d 100644 --- a/library/src/org/whispersystems/textsecure/crypto/PreKeyPublic.java +++ b/library/src/org/whispersystems/textsecure/crypto/PreKeyPublic.java @@ -1,25 +1,49 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.whispersystems.textsecure.crypto; -import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; +import org.whispersystems.textsecure.util.Util; public class PreKeyPublic { - private final ECPublicKeyParameters publicKey; + public static final int KEY_SIZE = ECPublicKey.KEY_SIZE; - public PreKeyPublic(ECPublicKeyParameters publicKey) { + private final ECPublicKey publicKey; + + public PreKeyPublic(ECPublicKey publicKey) { this.publicKey = publicKey; } public PreKeyPublic(byte[] serialized, int offset) throws InvalidKeyException { - this.publicKey = KeyUtil.decodePoint(serialized, offset); + this.publicKey = Curve.decodePoint(serialized, offset); } public byte[] serialize() { - return KeyUtil.encodePoint(publicKey.getQ()); + return publicKey.serialize(); } - public ECPublicKeyParameters getPublicKey() { + public ECPublicKey getPublicKey() { return publicKey; } + public int getType() { + return this.publicKey.getType(); + } } diff --git a/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java b/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java index fddbdca965..9c97a155db 100644 --- a/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java +++ b/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java @@ -1,9 +1,28 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.whispersystems.textsecure.crypto; import android.content.Context; import android.util.Log; import com.google.thoughtcrimegson.Gson; + +import org.whispersystems.textsecure.crypto.ecc.Curve25519; import org.whispersystems.textsecure.storage.InvalidKeyIdException; import org.whispersystems.textsecure.storage.PreKeyRecord; import org.whispersystems.textsecure.util.Medium; @@ -21,7 +40,7 @@ import java.util.List; public class PreKeyUtil { - public static final int BATCH_SIZE = 70; + public static final int BATCH_SIZE = 20; public static List generatePreKeys(Context context, MasterSecret masterSecret) { List records = new LinkedList(); @@ -29,7 +48,7 @@ public class PreKeyUtil { for (int i=0;i. + */ + package org.whispersystems.textsecure.crypto; import android.util.Log; -import org.spongycastle.crypto.CipherParameters; -import org.spongycastle.crypto.agreement.ECDHBasicAgreement; -import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets; import org.whispersystems.textsecure.crypto.kdf.HKDF; import org.whispersystems.textsecure.crypto.kdf.KDF; @@ -12,7 +28,6 @@ import org.whispersystems.textsecure.crypto.kdf.NKDF; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.util.Conversions; -import java.math.BigInteger; import java.util.LinkedList; import java.util.List; @@ -21,34 +36,34 @@ public class SharedSecretCalculator { public static DerivedSecrets calculateSharedSecret(boolean isLowEnd, KeyPair localKeyPair, int localKeyId, IdentityKeyPair localIdentityKeyPair, - ECPublicKeyParameters remoteKey, + ECPublicKey remoteKey, int remoteKeyId, IdentityKey remoteIdentityKey) + throws InvalidKeyException { - Log.w("SharedSecretCalculator", "Calculating shared secret with cradle agreement..."); - KDF kdf = new HKDF(); - List results = new LinkedList(); + Log.w("SharedSecretCalculator", "Calculating shared secret with 3DHE agreement..."); + KDF kdf = new HKDF(); + List results = new LinkedList(); if (isSmaller(localKeyPair.getPublicKey().getKey(), remoteKey)) { - results.add(calculateAgreement(localIdentityKeyPair.getPrivateKey(), remoteKey)); - - results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(), - remoteIdentityKey.getPublicKeyParameters())); + results.add(Curve.calculateAgreement(remoteKey, localIdentityKeyPair.getPrivateKey())); + results.add(Curve.calculateAgreement(remoteIdentityKey.getPublicKey(), + localKeyPair.getPrivateKey())); } else { - results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(), - remoteIdentityKey.getPublicKeyParameters())); - - results.add(calculateAgreement(localIdentityKeyPair.getPrivateKey(), remoteKey)); + results.add(Curve.calculateAgreement(remoteIdentityKey.getPublicKey(), + localKeyPair.getPrivateKey())); + results.add(Curve.calculateAgreement(remoteKey, localIdentityKeyPair.getPrivateKey())); } - results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(), remoteKey)); + results.add(Curve.calculateAgreement(remoteKey, localKeyPair.getPrivateKey())); - return kdf.deriveSecrets(results, isLowEnd, getInfo(localKeyId,remoteKeyId)); + return kdf.deriveSecrets(results, isLowEnd, getInfo(localKeyId, remoteKeyId)); } public static DerivedSecrets calculateSharedSecret(int messageVersion, boolean isLowEnd, KeyPair localKeyPair, int localKeyId, - ECPublicKeyParameters remoteKey, int remoteKeyId) + ECPublicKey remoteKey, int remoteKeyId) + throws InvalidKeyException { Log.w("SharedSecretCalculator", "Calculating shared secret with standard agreement..."); KDF kdf; @@ -58,8 +73,8 @@ public class SharedSecretCalculator { Log.w("SharedSecretCalculator", "Using kdf: " + kdf); - List results = new LinkedList(); - results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(), remoteKey)); + List results = new LinkedList(); + results.add(Curve.calculateAgreement(remoteKey, localKeyPair.getPrivateKey())); return kdf.deriveSecrets(results, isLowEnd, getInfo(localKeyId, remoteKeyId)); } @@ -78,23 +93,10 @@ public class SharedSecretCalculator { return info; } - private static BigInteger calculateAgreement(CipherParameters privateKey, - ECPublicKeyParameters publicKey) + private static boolean isSmaller(ECPublicKey localPublic, + ECPublicKey remotePublic) { - ECDHBasicAgreement agreement = new ECDHBasicAgreement(); - agreement.init(privateKey); - - return KeyUtil.calculateAgreement(agreement, publicKey); - } - - - private static boolean isSmaller(ECPublicKeyParameters localPublic, - ECPublicKeyParameters remotePublic) - { - BigInteger local = localPublic.getQ().getX().toBigInteger(); - BigInteger remote = remotePublic.getQ().getX().toBigInteger(); - - return local.compareTo(remote) < 0; + return localPublic.compareTo(remotePublic) < 0; } } diff --git a/library/src/org/whispersystems/textsecure/crypto/ecc/Curve.java b/library/src/org/whispersystems/textsecure/crypto/ecc/Curve.java new file mode 100644 index 0000000000..763c9e4efd --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/ecc/Curve.java @@ -0,0 +1,85 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.whispersystems.textsecure.crypto.ecc; + +import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; + +public class Curve { + + public static final int NIST_TYPE = 0x02; + private static final int NIST_TYPE2 = 0x03; + public static final int DJB_TYPE = 0x04; + + public static ECKeyPair generateKeyPairForType(int keyType) { + if (keyType == DJB_TYPE) { + return Curve25519.generateKeyPair(); + } else if (keyType == NIST_TYPE || keyType == NIST_TYPE2) { + return CurveP256.generateKeyPair(); + } else { + throw new AssertionError("Bad key type: " + keyType); + } + } + + public static ECKeyPair generateKeyPairForSession(int messageVersion) { + if (messageVersion >= CiphertextMessage.CURVE25519_INTRODUCED_VERSION) { + return generateKeyPairForType(DJB_TYPE); + } else { + return generateKeyPairForType(NIST_TYPE); + } + } + + public static ECPublicKey decodePoint(byte[] bytes, int offset) + throws InvalidKeyException + { + int type = bytes[offset]; + + if (type == DJB_TYPE) { + return Curve25519.decodePoint(bytes, offset); + } else if (type == NIST_TYPE || type == NIST_TYPE2) { + return CurveP256.decodePoint(bytes, offset); + } else { + throw new InvalidKeyException("Unknown key type: " + type); + } + } + + public static ECPrivateKey decodePrivatePoint(int type, byte[] bytes) { + if (type == DJB_TYPE) { + return new DjbECPrivateKey(bytes); + } else if (type == NIST_TYPE || type == NIST_TYPE2) { + return CurveP256.decodePrivatePoint(bytes); + } else { + throw new AssertionError("Bad key type: " + type); + } + } + + public static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey) + throws InvalidKeyException + { + if (publicKey.getType() != privateKey.getType()) { + throw new InvalidKeyException("Public and private keys must be of the same type!"); + } + + if (publicKey.getType() == DJB_TYPE) { + return Curve25519.calculateAgreement(publicKey, privateKey); + } else if (publicKey.getType() == NIST_TYPE || publicKey.getType() == NIST_TYPE2) { + return CurveP256.calculateAgreement(publicKey, privateKey); + } else { + throw new InvalidKeyException("Unknown type: " + publicKey.getType()); + } + } +} diff --git a/library/src/org/whispersystems/textsecure/crypto/ecc/Curve25519.java b/library/src/org/whispersystems/textsecure/crypto/ecc/Curve25519.java new file mode 100644 index 0000000000..64dc5e7b27 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/ecc/Curve25519.java @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.whispersystems.textsecure.crypto.ecc; + +import org.whispersystems.textsecure.crypto.InvalidKeyException; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +public class Curve25519 { + + static { + System.loadLibrary("curve25519"); + + try { + random = SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + + private static final SecureRandom random; + + private static native byte[] calculateAgreement(byte[] ourPrivate, byte[] theirPublic); + private static native byte[] generatePublicKey(byte[] privateKey); + private static native byte[] generatePrivateKey(byte[] random); + + public static ECKeyPair generateKeyPair() { + byte[] privateKey = generatePrivateKey(); + byte[] publicKey = generatePublicKey(privateKey); + + return new ECKeyPair(new DjbECPublicKey(publicKey), new DjbECPrivateKey(privateKey)); + } + + static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey) { + return calculateAgreement(((DjbECPrivateKey)privateKey).getPrivateKey(), + ((DjbECPublicKey)publicKey).getPublicKey()); + } + + static ECPublicKey decodePoint(byte[] encoded, int offset) + throws InvalidKeyException + { + int type = encoded[offset] & 0xFF; + byte[] keyBytes = new byte[32]; + System.arraycopy(encoded, offset+1, keyBytes, 0, keyBytes.length); + + if (type != Curve.DJB_TYPE) { + throw new InvalidKeyException("Bad key type: " + type); + } + + return new DjbECPublicKey(keyBytes); + } + + private static byte[] generatePrivateKey() { + byte[] privateKey = new byte[32]; + random.nextBytes(privateKey); + + return generatePrivateKey(privateKey); + } + +} diff --git a/library/src/org/whispersystems/textsecure/crypto/ecc/CurveP256.java b/library/src/org/whispersystems/textsecure/crypto/ecc/CurveP256.java new file mode 100644 index 0000000000..edc29eb169 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/ecc/CurveP256.java @@ -0,0 +1,122 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.whispersystems.textsecure.crypto.ecc; + +import android.util.Log; + +import org.spongycastle.crypto.AsymmetricCipherKeyPair; +import org.spongycastle.crypto.agreement.ECDHBasicAgreement; +import org.spongycastle.crypto.generators.ECKeyPairGenerator; +import org.spongycastle.crypto.params.ECDomainParameters; +import org.spongycastle.crypto.params.ECKeyGenerationParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.math.ec.ECCurve; +import org.spongycastle.math.ec.ECFieldElement; +import org.spongycastle.math.ec.ECPoint; +import org.whispersystems.textsecure.crypto.InvalidKeyException; + +import java.math.BigInteger; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +public class CurveP256 { + + private static final BigInteger q = new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16); + private static final BigInteger a = new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16); + private static final BigInteger b = new BigInteger("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", 16); + private static final BigInteger n = new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16); + + private static final ECFieldElement x = new ECFieldElement.Fp(q, new BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16)); + private static final ECFieldElement y = new ECFieldElement.Fp(q, new BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16)); + + private static final ECCurve curve = new ECCurve.Fp(q, a, b); + private static final ECPoint g = new ECPoint.Fp(curve, x, y, true); + + private static final ECDomainParameters domainParameters = new ECDomainParameters(curve, g, n); + + public static final int P256_POINT_SIZE = 33; + + static byte[] encodePoint(ECPoint point) { + synchronized (curve) { + return point.getEncoded(); + } + } + + static ECPublicKey decodePoint(byte[] encoded, int offset) + throws InvalidKeyException + { + byte[] pointBytes = new byte[P256_POINT_SIZE]; + System.arraycopy(encoded, offset, pointBytes, 0, pointBytes.length); + + synchronized (curve) { + ECPoint Q; + + try { + Q = curve.decodePoint(pointBytes); + } catch (RuntimeException re) { + throw new InvalidKeyException(re); + } + + return new NistECPublicKey(new ECPublicKeyParameters(Q, domainParameters)); + } + } + + static ECPrivateKey decodePrivatePoint(byte[] encoded) { + BigInteger d = new BigInteger(encoded); + return new NistECPrivateKey(new ECPrivateKeyParameters(d, domainParameters)); + } + + static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey) { + ECDHBasicAgreement agreement = new ECDHBasicAgreement(); + agreement.init(((NistECPrivateKey)privateKey).getParameters()); + + synchronized (curve) { + return agreement.calculateAgreement(((NistECPublicKey)publicKey).getParameters()).toByteArray(); + } + } + + public static ECKeyPair generateKeyPair() { + try { + synchronized (curve) { + ECKeyGenerationParameters keyParamters = new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")); + ECKeyPairGenerator generator = new ECKeyPairGenerator(); + generator.init(keyParamters); + + AsymmetricCipherKeyPair keyPair = generator.generateKeyPair(); + keyPair = cloneKeyPairWithPointCompression(keyPair); + + return new ECKeyPair(new NistECPublicKey((ECPublicKeyParameters)keyPair.getPublic()), + new NistECPrivateKey((ECPrivateKeyParameters)keyPair.getPrivate())); + } + } catch (NoSuchAlgorithmException nsae) { + Log.w("CurveP256", nsae); + throw new AssertionError(nsae); + } + } + + // This is dumb, but the ECPublicKeys that the generator makes by default don't have point compression + // turned on, and there's no setter. Great. + private static AsymmetricCipherKeyPair cloneKeyPairWithPointCompression(AsymmetricCipherKeyPair keyPair) { + ECPublicKeyParameters publicKey = (ECPublicKeyParameters)keyPair.getPublic(); + ECPoint q = publicKey.getQ(); + + return new AsymmetricCipherKeyPair(new ECPublicKeyParameters(new ECPoint.Fp(q.getCurve(), q.getX(), q.getY(), true), + publicKey.getParameters()), keyPair.getPrivate()); + } +} diff --git a/library/src/org/whispersystems/textsecure/crypto/ecc/DjbECPrivateKey.java b/library/src/org/whispersystems/textsecure/crypto/ecc/DjbECPrivateKey.java new file mode 100644 index 0000000000..fbf37a2232 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/ecc/DjbECPrivateKey.java @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.whispersystems.textsecure.crypto.ecc; + +public class DjbECPrivateKey implements ECPrivateKey { + + private final byte[] privateKey; + + DjbECPrivateKey(byte[] privateKey) { + this.privateKey = privateKey; + } + + @Override + public byte[] serialize() { + return privateKey; + } + + @Override + public int getType() { + return Curve.DJB_TYPE; + } + + public byte[] getPrivateKey() { + return privateKey; + } +} diff --git a/library/src/org/whispersystems/textsecure/crypto/ecc/DjbECPublicKey.java b/library/src/org/whispersystems/textsecure/crypto/ecc/DjbECPublicKey.java new file mode 100644 index 0000000000..bd6020741d --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/ecc/DjbECPublicKey.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.whispersystems.textsecure.crypto.ecc; + +import org.whispersystems.textsecure.util.Util; + +import java.math.BigInteger; +import java.util.Arrays; + +public class DjbECPublicKey implements ECPublicKey { + + private final byte[] publicKey; + + DjbECPublicKey(byte[] publicKey) { + this.publicKey = publicKey; + } + + @Override + public byte[] serialize() { + byte[] type = {Curve.DJB_TYPE}; + return Util.combine(type, publicKey); + } + + @Override + public int getType() { + return Curve.DJB_TYPE; + } + + @Override + public boolean equals(Object other) { + if (other == null) return false; + if (!(other instanceof DjbECPublicKey)) return false; + + DjbECPublicKey that = (DjbECPublicKey)other; + return Arrays.equals(this.publicKey, that.publicKey); + } + + @Override + public int hashCode() { + return Arrays.hashCode(publicKey); + } + + @Override + public int compareTo(ECPublicKey another) { + return new BigInteger(publicKey).compareTo(new BigInteger(((DjbECPublicKey)another).publicKey)); + } + + public byte[] getPublicKey() { + return publicKey; + } +} diff --git a/library/src/org/whispersystems/textsecure/crypto/ecc/ECKeyPair.java b/library/src/org/whispersystems/textsecure/crypto/ecc/ECKeyPair.java new file mode 100644 index 0000000000..2fc454c73c --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/ecc/ECKeyPair.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.whispersystems.textsecure.crypto.ecc; + +public class ECKeyPair { + + private final ECPublicKey publicKey; + private final ECPrivateKey privateKey; + + public ECKeyPair(ECPublicKey publicKey, ECPrivateKey privateKey) { + this.publicKey = publicKey; + this.privateKey = privateKey; + } + + + public ECPublicKey getPublicKey() { + return publicKey; + } + + public ECPrivateKey getPrivateKey() { + return privateKey; + } +} diff --git a/library/src/org/whispersystems/textsecure/crypto/ecc/ECPrivateKey.java b/library/src/org/whispersystems/textsecure/crypto/ecc/ECPrivateKey.java new file mode 100644 index 0000000000..4892d0e658 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/ecc/ECPrivateKey.java @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.whispersystems.textsecure.crypto.ecc; + +public interface ECPrivateKey { + public byte[] serialize(); + public int getType(); +} diff --git a/library/src/org/whispersystems/textsecure/crypto/ecc/ECPublicKey.java b/library/src/org/whispersystems/textsecure/crypto/ecc/ECPublicKey.java new file mode 100644 index 0000000000..42a9ce86d5 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/ecc/ECPublicKey.java @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.whispersystems.textsecure.crypto.ecc; + +public interface ECPublicKey extends Comparable { + + public static final int KEY_SIZE = 33; + + public byte[] serialize(); + + public int getType(); +} diff --git a/library/src/org/whispersystems/textsecure/crypto/ecc/NistECPrivateKey.java b/library/src/org/whispersystems/textsecure/crypto/ecc/NistECPrivateKey.java new file mode 100644 index 0000000000..c4fa5a6879 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/ecc/NistECPrivateKey.java @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.whispersystems.textsecure.crypto.ecc; + +import org.spongycastle.crypto.params.ECPrivateKeyParameters; + +public class NistECPrivateKey implements ECPrivateKey { + + private final ECPrivateKeyParameters privateKey; + + public NistECPrivateKey(ECPrivateKeyParameters privateKey) { + this.privateKey = privateKey; + } + + @Override + public byte[] serialize() { + return privateKey.getD().toByteArray(); + } + + @Override + public int getType() { + return Curve.NIST_TYPE; + } + + public ECPrivateKeyParameters getParameters() { + return privateKey; + } +} diff --git a/library/src/org/whispersystems/textsecure/crypto/ecc/NistECPublicKey.java b/library/src/org/whispersystems/textsecure/crypto/ecc/NistECPublicKey.java new file mode 100644 index 0000000000..1c54fec1bb --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/ecc/NistECPublicKey.java @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.whispersystems.textsecure.crypto.ecc; + +import org.spongycastle.crypto.params.ECPublicKeyParameters; + +public class NistECPublicKey implements ECPublicKey { + + private final ECPublicKeyParameters publicKey; + + NistECPublicKey(ECPublicKeyParameters publicKey) { + this.publicKey = publicKey; + } + + @Override + public byte[] serialize() { + return CurveP256.encodePoint(publicKey.getQ()); + } + + @Override + public int getType() { + return Curve.NIST_TYPE; + } + + @Override + public boolean equals(Object other) { + if (other == null) return false; + if (!(other instanceof NistECPublicKey)) return false; + + NistECPublicKey that = (NistECPublicKey)other; + return publicKey.getQ().equals(that.publicKey.getQ()); + } + + @Override + public int hashCode() { + return publicKey.getQ().hashCode(); + } + + @Override + public int compareTo(ECPublicKey another) { + return publicKey.getQ().getX().toBigInteger() + .compareTo(((NistECPublicKey) another).publicKey.getQ().getX().toBigInteger()); + } + + public ECPublicKeyParameters getParameters() { + return publicKey; + } +} diff --git a/library/src/org/whispersystems/textsecure/crypto/kdf/DerivedSecrets.java b/library/src/org/whispersystems/textsecure/crypto/kdf/DerivedSecrets.java index 74f8b82e67..294b1b27c1 100644 --- a/library/src/org/whispersystems/textsecure/crypto/kdf/DerivedSecrets.java +++ b/library/src/org/whispersystems/textsecure/crypto/kdf/DerivedSecrets.java @@ -1,3 +1,20 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.whispersystems.textsecure.crypto.kdf; import javax.crypto.spec.SecretKeySpec; diff --git a/library/src/org/whispersystems/textsecure/crypto/kdf/HKDF.java b/library/src/org/whispersystems/textsecure/crypto/kdf/HKDF.java index 69cf3afc0f..e07e4baa4c 100644 --- a/library/src/org/whispersystems/textsecure/crypto/kdf/HKDF.java +++ b/library/src/org/whispersystems/textsecure/crypto/kdf/HKDF.java @@ -1,3 +1,20 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.whispersystems.textsecure.crypto.kdf; import java.io.ByteArrayOutputStream; @@ -18,7 +35,7 @@ public class HKDF extends KDF { private static final int MAC_KEYS_OFFSET = 32; @Override - public DerivedSecrets deriveSecrets(List sharedSecret, + public DerivedSecrets deriveSecrets(List sharedSecret, boolean isLowEnd, byte[] info) { byte[] inputKeyMaterial = concatenateSharedSecrets(sharedSecret); diff --git a/library/src/org/whispersystems/textsecure/crypto/kdf/KDF.java b/library/src/org/whispersystems/textsecure/crypto/kdf/KDF.java index b49ddc8d41..66e9b86e59 100644 --- a/library/src/org/whispersystems/textsecure/crypto/kdf/KDF.java +++ b/library/src/org/whispersystems/textsecure/crypto/kdf/KDF.java @@ -1,33 +1,45 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.whispersystems.textsecure.crypto.kdf; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.math.BigInteger; import java.util.LinkedList; import java.util.List; public abstract class KDF { - public abstract DerivedSecrets deriveSecrets(List sharedSecret, + public abstract DerivedSecrets deriveSecrets(List sharedSecret, boolean isLowEnd, byte[] info); - protected byte[] concatenateSharedSecrets(List sharedSecrets) { - int totalByteSize = 0; - List byteValues = new LinkedList(); + protected byte[] concatenateSharedSecrets(List sharedSecrets) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); - for (BigInteger sharedSecret : sharedSecrets) { - byte[] byteValue = sharedSecret.toByteArray(); - totalByteSize += byteValue.length; - byteValues.add(byteValue); + for (byte[] sharedSecret : sharedSecrets) { + baos.write(sharedSecret); + } + + return baos.toByteArray(); + } catch (IOException e) { + throw new AssertionError(e); } - - byte[] combined = new byte[totalByteSize]; - int offset = 0; - - for (byte[] byteValue : byteValues) { - System.arraycopy(byteValue, 0, combined, offset, byteValue.length); - offset += byteValue.length; - } - - return combined; } } diff --git a/library/src/org/whispersystems/textsecure/crypto/kdf/NKDF.java b/library/src/org/whispersystems/textsecure/crypto/kdf/NKDF.java index 5106af24f5..afde62a1a4 100644 --- a/library/src/org/whispersystems/textsecure/crypto/kdf/NKDF.java +++ b/library/src/org/whispersystems/textsecure/crypto/kdf/NKDF.java @@ -1,10 +1,26 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.whispersystems.textsecure.crypto.kdf; import android.util.Log; import org.whispersystems.textsecure.util.Conversions; -import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.List; @@ -14,7 +30,7 @@ import javax.crypto.spec.SecretKeySpec; public class NKDF extends KDF { @Override - public DerivedSecrets deriveSecrets(List sharedSecret, + public DerivedSecrets deriveSecrets(List sharedSecret, boolean isLowEnd, byte[] info) { SecretKeySpec cipherKey = deriveCipherSecret(isLowEnd, sharedSecret); @@ -23,7 +39,7 @@ public class NKDF extends KDF { return new DerivedSecrets(cipherKey, macKey); } - private SecretKeySpec deriveCipherSecret(boolean isLowEnd, List sharedSecret) { + private SecretKeySpec deriveCipherSecret(boolean isLowEnd, List sharedSecret) { byte[] sharedSecretBytes = concatenateSharedSecrets(sharedSecret); byte[] derivedBytes = deriveBytes(sharedSecretBytes, 16 * 2); byte[] cipherSecret = new byte[16]; diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/CiphertextMessage.java b/library/src/org/whispersystems/textsecure/crypto/protocol/CiphertextMessage.java index 63710d8eac..e2322e694e 100644 --- a/library/src/org/whispersystems/textsecure/crypto/protocol/CiphertextMessage.java +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/CiphertextMessage.java @@ -9,8 +9,9 @@ import org.whispersystems.textsecure.util.Conversions; public class CiphertextMessage { - public static final int SUPPORTED_VERSION = 2; - public static final int DHE3_INTRODUCED_VERSION = 2; + public static final int SUPPORTED_VERSION = 2; + public static final int DHE3_INTRODUCED_VERSION = 2; + public static final int CURVE25519_INTRODUCED_VERSION = 2; static final int VERSION_LENGTH = 1; private static final int SENDER_KEY_ID_LENGTH = 3; diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java b/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java index 6f65d47ad5..741f0979a0 100644 --- a/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java @@ -77,7 +77,9 @@ public class PreKeyBundleMessage { messageBytes[VERSION_OFFSET] = bundledMessageBytes[VERSION_OFFSET]; System.arraycopy(identityKeyBytes, 0, messageBytes, IDENTITY_KEY_OFFSET, identityKeyBytes.length); - System.arraycopy(bundledMessageBytes, VERSION_OFFSET+VERSION_LENGTH, messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, bundledMessageBytes.length-VERSION_LENGTH); + System.arraycopy(bundledMessageBytes, VERSION_OFFSET+VERSION_LENGTH, + messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, + bundledMessageBytes.length-VERSION_LENGTH); } public byte[] serialize() { diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java index 288e0649d3..cb42d19aee 100644 --- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java +++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java @@ -133,6 +133,7 @@ public class PushServiceSocket { PreKeyEntity entity = new PreKeyEntity(record.getId(), record.getKeyPair().getPublicKey(), identityKey); + entities.add(entity); } @@ -140,7 +141,9 @@ public class PushServiceSocket { lastResortKey.getKeyPair().getPublicKey(), identityKey); - makeRequest(String.format(PREKEY_PATH, ""), "PUT", PreKeyList.toJson(new PreKeyList(lastResortEntity, entities))); + + makeRequest(String.format(PREKEY_PATH, ""), "PUT", + PreKeyList.toJson(new PreKeyList(lastResortEntity, entities))); } public PreKeyEntity getPreKey(PushDestination destination) throws IOException { diff --git a/library/src/org/whispersystems/textsecure/storage/LocalKeyRecord.java b/library/src/org/whispersystems/textsecure/storage/LocalKeyRecord.java index a92f7d0b41..042f26df2d 100644 --- a/library/src/org/whispersystems/textsecure/storage/LocalKeyRecord.java +++ b/library/src/org/whispersystems/textsecure/storage/LocalKeyRecord.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Whisper Systems + * Copyright (C) 2013 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,9 +22,9 @@ import android.util.Log; import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.KeyPair; -import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.MasterCipher; import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.ecc.Curve; import org.whispersystems.textsecure.util.Medium; import java.io.FileInputStream; @@ -65,9 +66,12 @@ public class LocalKeyRecord extends Record { public void advanceKeyIfNecessary(int keyId) { Log.w("LocalKeyRecord", "Remote client acknowledges receiving key id: " + keyId); if (keyId == localNextKeyPair.getId()) { + int keyType = this.localNextKeyPair.getPublicKey().getType(); + this.localCurrentKeyPair = this.localNextKeyPair; this.localNextKeyPair = new KeyPair((this.localNextKeyPair.getId()+1) % Medium.MAX_VALUE, - KeyUtil.generateKeyPair(), masterSecret); + Curve.generateKeyPairForType(keyType), + masterSecret); } } diff --git a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java index 1bb540ba99..9c4f1252ce 100644 --- a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java +++ b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java @@ -1,3 +1,20 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.thoughtcrime.securesms; import android.app.Activity; @@ -10,6 +27,7 @@ import android.util.Log; import android.view.View; import android.widget.ProgressBar; +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.whispersystems.textsecure.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.util.VersionTracker; @@ -22,10 +40,12 @@ public class DatabaseUpgradeActivity extends Activity { public static final int NO_MORE_KEY_EXCHANGE_PREFIX_VERSION = 46; public static final int MMS_BODY_VERSION = 46; public static final int TOFU_IDENTITIES_VERSION = 50; + public static final int CURVE25519_VERSION = 58; private static final SortedSet UPGRADE_VERSIONS = new TreeSet() {{ add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION); add(TOFU_IDENTITIES_VERSION); + add(CURVE25519_VERSION); }}; private MasterSecret masterSecret; @@ -33,9 +53,9 @@ public class DatabaseUpgradeActivity extends Activity { @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); - this.masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret"); + this.masterSecret = getIntent().getParcelableExtra("master_secret"); - if (needsDatabaseUpgrade()) { + if (needsUpgradeTask()) { Log.w("DatabaseUpgradeActivity", "Upgrading..."); setContentView(R.layout.database_upgrade_activity); @@ -51,7 +71,7 @@ public class DatabaseUpgradeActivity extends Activity { } } - private boolean needsDatabaseUpgrade() { + private boolean needsUpgradeTask() { try { int currentVersionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode; int lastSeenVersion = VersionTracker.getLastSeenVersion(this); @@ -102,10 +122,18 @@ public class DatabaseUpgradeActivity extends Activity { @Override protected Void doInBackground(Integer... params) { + Context context = DatabaseUpgradeActivity.this.getApplicationContext(); + Log.w("DatabaseUpgradeActivity", "Running background upgrade.."); DatabaseFactory.getInstance(DatabaseUpgradeActivity.this) - .onApplicationLevelUpgrade(DatabaseUpgradeActivity.this.getApplicationContext(), - masterSecret, params[0], this); + .onApplicationLevelUpgrade(context, masterSecret, params[0], this); + + if (params[0] < CURVE25519_VERSION) { + if (!IdentityKeyUtil.hasCurve25519IdentityKeys(context)) { + IdentityKeyUtil.generateCurve25519IdentityKeys(context, masterSecret); + } + } + return null; } diff --git a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java index 5c8763d2cb..cd7ad31dd0 100644 --- a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java +++ b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Whisper Systems + * Copyright (C) 2013 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +24,9 @@ import android.widget.Toast; import org.whispersystems.textsecure.crypto.IdentityKey; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.storage.SessionRecord; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.MemoryCleaner; @@ -40,6 +44,8 @@ public class VerifyIdentityActivity extends KeyScanningActivity { private TextView localIdentityFingerprint; private TextView remoteIdentityFingerprint; + private int keyType; + @Override public void onCreate(Bundle state) { super.onCreate(state); @@ -57,12 +63,12 @@ public class VerifyIdentityActivity extends KeyScanningActivity { } private void initializeLocalIdentityKey() { - if (!IdentityKeyUtil.hasIdentityKey(this)) { + if (!IdentityKeyUtil.hasIdentityKey(this, keyType)) { localIdentityFingerprint.setText(R.string.VerifyIdentityActivity_you_do_not_have_an_identity_key); return; } - localIdentityFingerprint.setText(IdentityKeyUtil.getFingerprint(this)); + localIdentityFingerprint.setText(IdentityKeyUtil.getFingerprint(this, keyType)); } private void initializeRemoteIdentityKey() { @@ -86,15 +92,24 @@ public class VerifyIdentityActivity extends KeyScanningActivity { } private void initializeResources() { - localIdentityFingerprint = (TextView)findViewById(R.id.you_read); - remoteIdentityFingerprint = (TextView)findViewById(R.id.friend_reads); - recipient = (Recipient)this.getIntent().getParcelableExtra("recipient"); - masterSecret = (MasterSecret)this.getIntent().getParcelableExtra("master_secret"); + this.localIdentityFingerprint = (TextView)findViewById(R.id.you_read); + this.remoteIdentityFingerprint = (TextView)findViewById(R.id.friend_reads); + this.recipient = this.getIntent().getParcelableExtra("recipient"); + this.masterSecret = this.getIntent().getParcelableExtra("master_secret"); + + SessionRecord sessionRecord = new SessionRecord(this, masterSecret, recipient); + int sessionVersion = sessionRecord.getSessionVersion(); + + if (sessionVersion >= CiphertextMessage.CURVE25519_INTRODUCED_VERSION) { + this.keyType = Curve.DJB_TYPE; + } else { + this.keyType = Curve.NIST_TYPE; + } } @Override protected void initiateDisplay() { - if (!IdentityKeyUtil.hasIdentityKey(this)) { + if (!IdentityKeyUtil.hasIdentityKey(this, keyType)) { Toast.makeText(this, R.string.VerifyIdentityActivity_you_don_t_have_an_identity_key_exclamation, Toast.LENGTH_LONG).show(); @@ -135,7 +150,7 @@ public class VerifyIdentityActivity extends KeyScanningActivity { @Override protected IdentityKey getIdentityKeyToDisplay() { - return IdentityKeyUtil.getIdentityKey(this); + return IdentityKeyUtil.getIdentityKey(this, keyType); } @Override diff --git a/src/org/thoughtcrime/securesms/ViewLocalIdentityActivity.java b/src/org/thoughtcrime/securesms/ViewLocalIdentityActivity.java index 03c27cad1b..00d172c323 100644 --- a/src/org/thoughtcrime/securesms/ViewLocalIdentityActivity.java +++ b/src/org/thoughtcrime/securesms/ViewLocalIdentityActivity.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2011 Whisper Systems + * Copyright (C) 2013 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,6 +29,8 @@ import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; /** * Activity that displays the local identity key and offers the option to regenerate it. @@ -41,7 +44,7 @@ public class ViewLocalIdentityActivity extends ViewIdentityActivity { public void onCreate(Bundle bundle) { this.masterSecret = getIntent().getParcelableExtra("master_secret"); - getIntent().putExtra("identity_key", IdentityKeyUtil.getIdentityKey(this)); + getIntent().putExtra("identity_key", IdentityKeyUtil.getIdentityKey(this, Curve.DJB_TYPE)); getIntent().putExtra("title", getString(R.string.ApplicationPreferencesActivity_my) + " " + getString(R.string.ViewIdentityActivity_identity_fingerprint)); super.onCreate(bundle); @@ -113,7 +116,8 @@ public class ViewLocalIdentityActivity extends ViewIdentityActivity { Toast.LENGTH_LONG).show(); getIntent().putExtra("identity_key", - IdentityKeyUtil.getIdentityKey(ViewLocalIdentityActivity.this)); + IdentityKeyUtil.getIdentityKey(ViewLocalIdentityActivity.this, + Curve.DJB_TYPE)); initialize(); } diff --git a/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterCipher.java b/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterCipher.java index 9c5f3f0be1..4c6588a664 100644 --- a/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterCipher.java +++ b/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterCipher.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Whisper Systems + * Copyright (C) 2013 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,25 +17,25 @@ */ package org.thoughtcrime.securesms.crypto; +import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.InvalidMessageException; +import org.whispersystems.textsecure.crypto.MasterCipher; +import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.PublicKey; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECKeyPair; +import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; +import org.whispersystems.textsecure.util.Base64; +import org.whispersystems.textsecure.util.Conversions; +import org.whispersystems.textsecure.util.Util; + import java.io.IOException; -import java.math.BigInteger; import java.security.NoSuchAlgorithmException; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -import org.spongycastle.crypto.AsymmetricCipherKeyPair; -import org.spongycastle.crypto.agreement.ECDHBasicAgreement; -import org.spongycastle.crypto.params.ECPublicKeyParameters; -import org.whispersystems.textsecure.crypto.InvalidKeyException; -import org.whispersystems.textsecure.crypto.InvalidMessageException; -import org.whispersystems.textsecure.crypto.KeyUtil; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.PublicKey; -import org.whispersystems.textsecure.util.Base64; -import org.whispersystems.textsecure.util.Conversions; - /** * This class is used to asymmetricly encrypt local data. This is used in the case * where TextSecure receives an SMS, but the user's local encryption passphrase is @@ -58,26 +59,23 @@ import org.whispersystems.textsecure.util.Conversions; public class AsymmetricMasterCipher { private final AsymmetricMasterSecret asymmetricMasterSecret; - + public AsymmetricMasterCipher(AsymmetricMasterSecret asymmetricMasterSecret) { this.asymmetricMasterSecret = asymmetricMasterSecret; } public String decryptBody(String body) throws IOException, InvalidMessageException { try { - byte[] combined = Base64.decode(body); - PublicKey theirPublicKey = new PublicKey(combined, 0); - byte[] encryptedBodyBytes = new byte[combined.length - PublicKey.KEY_SIZE]; - System.arraycopy(combined, PublicKey.KEY_SIZE, encryptedBodyBytes, 0, encryptedBodyBytes.length); - - ECDHBasicAgreement agreement = new ECDHBasicAgreement(); - agreement.init(asymmetricMasterSecret.getPrivateKey()); - - BigInteger secret = KeyUtil.calculateAgreement(agreement, theirPublicKey.getKey()); - MasterCipher masterCipher = getMasterCipherForSecret(secret); - byte[] decryptedBodyBytes = masterCipher.decryptBytes(encryptedBodyBytes); - - return new String(decryptedBodyBytes); + byte[] combined = Base64.decode(body); + byte[][] parts = Util.split(combined, PublicKey.KEY_SIZE, combined.length - PublicKey.KEY_SIZE); + PublicKey theirPublicKey = new PublicKey(parts[0], 0); + + ECPrivateKey ourPrivateKey = asymmetricMasterSecret.getPrivateKey(theirPublicKey.getType()); + byte[] secret = Curve.calculateAgreement(theirPublicKey.getKey(), ourPrivateKey); + MasterCipher masterCipher = getMasterCipherForSecret(secret); + byte[] decryptedBody = masterCipher.decryptBytes(parts[1]); + + return new String(decryptedBody); } catch (InvalidKeyException ike) { throw new InvalidMessageException(ike); } catch (InvalidMessageException e) { @@ -86,26 +84,31 @@ public class AsymmetricMasterCipher { } public String encryptBody(String body) { - ECDHBasicAgreement agreement = new ECDHBasicAgreement(); - AsymmetricCipherKeyPair keyPair = KeyUtil.generateKeyPair(); - - agreement.init(keyPair.getPrivate()); - - BigInteger secret = KeyUtil.calculateAgreement(agreement, asymmetricMasterSecret.getPublicKey().getKey()); - MasterCipher masterCipher = getMasterCipherForSecret(secret); - byte[] encryptedBodyBytes = masterCipher.encryptBytes(body.getBytes()); - PublicKey publicKey = new PublicKey(31337, (ECPublicKeyParameters)keyPair.getPublic()); - byte[] publicKeyBytes = publicKey.serialize(); - byte[] combined = new byte[publicKeyBytes.length + encryptedBodyBytes.length]; - - System.arraycopy(publicKeyBytes, 0, combined, 0, publicKeyBytes.length); - System.arraycopy(encryptedBodyBytes, 0, combined, publicKeyBytes.length, encryptedBodyBytes.length); - - return Base64.encodeBytes(combined); + try { + ECPublicKey theirPublic; + + if (asymmetricMasterSecret.getDjbPublicKey() != null) { + theirPublic = asymmetricMasterSecret.getDjbPublicKey(); + } else { + theirPublic = asymmetricMasterSecret.getNistPublicKey(); + } + + ECKeyPair ourKeyPair = Curve.generateKeyPairForType(theirPublic.getType()); + byte[] secret = Curve.calculateAgreement(theirPublic, ourKeyPair.getPrivateKey()); + MasterCipher masterCipher = getMasterCipherForSecret(secret); + byte[] encryptedBodyBytes = masterCipher.encryptBytes(body.getBytes()); + + PublicKey ourPublicKey = new PublicKey(31337, ourKeyPair.getPublicKey()); + byte[] publicKeyBytes = ourPublicKey.serialize(); + byte[] combined = Util.combine(publicKeyBytes, encryptedBodyBytes); + + return Base64.encodeBytes(combined); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } } - private MasterCipher getMasterCipherForSecret(BigInteger secret) { - byte[] secretBytes = secret.toByteArray(); + private MasterCipher getMasterCipherForSecret(byte[] secretBytes) { SecretKeySpec cipherKey = deriveCipherKey(secretBytes); SecretKeySpec macKey = deriveMacKey(secretBytes); MasterSecret masterSecret = new MasterSecret(cipherKey, macKey); diff --git a/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterSecret.java b/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterSecret.java index ddb7de7892..aa20e5c4f2 100644 --- a/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterSecret.java +++ b/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterSecret.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Whisper Systems + * Copyright (C) 2013 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,8 +17,9 @@ */ package org.thoughtcrime.securesms.crypto; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; -import org.whispersystems.textsecure.crypto.PublicKey; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; /** * When a user first initializes TextSecure, a few secrets @@ -38,19 +40,35 @@ import org.whispersystems.textsecure.crypto.PublicKey; public class AsymmetricMasterSecret { - private final PublicKey publicKey; - private final ECPrivateKeyParameters privateKey; - - public AsymmetricMasterSecret(PublicKey publicKey, ECPrivateKeyParameters privateKey) { - this.publicKey = publicKey; - this.privateKey = privateKey; + private final ECPublicKey djbPublicKey; + private final ECPrivateKey djbPrivateKey; + + private final ECPublicKey nistPublicKey; + private final ECPrivateKey nistPrivateKey; + + public AsymmetricMasterSecret(ECPublicKey djbPublicKey, ECPrivateKey djbPrivateKey, + ECPublicKey nistPublicKey, ECPrivateKey nistPrivateKey) + { + this.djbPublicKey = djbPublicKey; + this.djbPrivateKey = djbPrivateKey; + this.nistPublicKey = nistPublicKey; + this.nistPrivateKey = nistPrivateKey; } - - public PublicKey getPublicKey() { - return publicKey; + + public ECPublicKey getDjbPublicKey() { + return djbPublicKey; } - - public ECPrivateKeyParameters getPrivateKey() { - return privateKey; + + public ECPublicKey getNistPublicKey() { + return nistPublicKey; } + + public ECPrivateKey getPrivateKey(int type) { + if (type == Curve.DJB_TYPE) { + return djbPrivateKey; + } else { + return nistPrivateKey; + } + } + } diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java index d9ded58c2a..c0b825e54f 100644 --- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java +++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Whisper Systems + * Copyright (C) 2013 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -47,6 +48,8 @@ import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MessageCipher; import org.whispersystems.textsecure.crypto.SessionCipher; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.util.Hex; @@ -195,7 +198,7 @@ public class DecryptingQueue { return; } - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE); MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey); byte[] plaintextBody = messageCipher.decrypt(recipient, message.getBody()); @@ -276,7 +279,7 @@ public class DecryptingQueue { synchronized (SessionCipher.CIPHER_LOCK) { Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes)); TextTransport transportDetails = new TextTransport(); - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE); MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey); byte[] ciphertext = transportDetails.getDecodedMessage(ciphertextPduBytes); @@ -360,7 +363,7 @@ public class DecryptingQueue { } SmsTransportDetails transportDetails = new SmsTransportDetails(); - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE); MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey); byte[] ciphertext = transportDetails.getDecodedMessage(body.getBytes()); byte[] paddedPlaintext = messageCipher.decrypt(recipient, ciphertext); diff --git a/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java b/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java index e852539ab6..3bd6d57138 100644 --- a/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Whisper Systems + * Copyright (C) 2013 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,17 +27,18 @@ import org.spongycastle.asn1.ASN1Primitive; import org.spongycastle.asn1.ASN1Sequence; import org.spongycastle.asn1.DERInteger; import org.spongycastle.asn1.DERSequence; -import org.spongycastle.crypto.AsymmetricCipherKeyPair; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; -import org.spongycastle.crypto.params.ECPublicKeyParameters; import org.spongycastle.crypto.signers.ECDSASigner; import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.IdentityKeyPair; import org.whispersystems.textsecure.crypto.InvalidKeyException; -import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.MasterCipher; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.PublicKey; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECKeyPair; +import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey; +import org.whispersystems.textsecure.crypto.ecc.NistECPrivateKey; +import org.whispersystems.textsecure.crypto.ecc.NistECPublicKey; import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Conversions; import org.whispersystems.textsecure.util.Util; @@ -54,19 +56,39 @@ import java.security.NoSuchAlgorithmException; public class IdentityKeyUtil { - private static final String IDENTITY_PUBLIC_KEY_PREF = "pref_identity_public"; - private static final String IDENTITY_PRIVATE_KEY_PREF = "pref_identity_private"; + private static final String IDENTITY_PUBLIC_KEY_NIST_PREF = "pref_identity_public"; + private static final String IDENTITY_PRIVATE_KEY_NIST_PREF = "pref_identity_private"; + + private static final String IDENTITY_PUBLIC_KEY_DJB_PREF = "pref_identity_public_curve25519"; + private static final String IDENTITY_PRIVATE_KEY_DJB_PREF = "pref_identity_private_curve25519"; - public static boolean hasIdentityKey(Context context) { + public static boolean hasIdentityKey(Context context, int type) { SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0); - return preferences.contains(IDENTITY_PUBLIC_KEY_PREF) && preferences.contains(IDENTITY_PRIVATE_KEY_PREF); + + if (type == Curve.DJB_TYPE) { + return + preferences.contains(IDENTITY_PUBLIC_KEY_DJB_PREF) && + preferences.contains(IDENTITY_PRIVATE_KEY_DJB_PREF); + } else if (type == Curve.NIST_TYPE) { + return + preferences.contains(IDENTITY_PUBLIC_KEY_NIST_PREF) && + preferences.contains(IDENTITY_PRIVATE_KEY_NIST_PREF); + } + + return false; } - public static IdentityKey getIdentityKey(Context context) { - if (!hasIdentityKey(context)) return null; + public static IdentityKey getIdentityKey(Context context, int type) { + if (!hasIdentityKey(context, type)) return null; try { - byte[] publicKeyBytes = Base64.decode(retrieve(context, IDENTITY_PUBLIC_KEY_PREF)); + String key; + + if (type == Curve.DJB_TYPE) key = IDENTITY_PUBLIC_KEY_DJB_PREF; + else if (type == Curve.NIST_TYPE) key = IDENTITY_PUBLIC_KEY_NIST_PREF; + else return null; + + byte[] publicKeyBytes = Base64.decode(retrieve(context, key)); return new IdentityKey(publicKeyBytes, 0); } catch (IOException ioe) { Log.w("IdentityKeyUtil", ioe); @@ -77,43 +99,78 @@ public class IdentityKeyUtil { } } - public static IdentityKeyPair getIdentityKeyPair(Context context, MasterSecret masterSecret) { - if (!hasIdentityKey(context)) + public static IdentityKeyPair getIdentityKeyPair(Context context, + MasterSecret masterSecret, + int type) + { + if (!hasIdentityKey(context, type)) return null; try { - MasterCipher masterCipher = new MasterCipher(masterSecret); - IdentityKey publicKey = getIdentityKey(context); - byte[] privateKeyBytes = Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_PREF)); - ECPrivateKeyParameters privateKey = masterCipher.decryptKey(privateKeyBytes); + String key; + + if (type == Curve.DJB_TYPE) key = IDENTITY_PRIVATE_KEY_DJB_PREF; + else if (type == Curve.NIST_TYPE) key = IDENTITY_PRIVATE_KEY_NIST_PREF; + else return null; + + MasterCipher masterCipher = new MasterCipher(masterSecret); + IdentityKey publicKey = getIdentityKey(context, type); + ECPrivateKey privateKey = masterCipher.decryptKey(type, Base64.decode(retrieve(context, key))); return new IdentityKeyPair(publicKey, privateKey); } catch (IOException e) { throw new AssertionError(e); + } catch (InvalidKeyException e) { + throw new AssertionError(e); } } - public static String getFingerprint(Context context) { - if (!hasIdentityKey(context)) return null; + public static String getFingerprint(Context context, int type) { + if (!hasIdentityKey(context, type)) return null; - IdentityKey identityKey = getIdentityKey(context); + IdentityKey identityKey = getIdentityKey(context, type); if (identityKey == null) return null; else return identityKey.getFingerprint(); } public static void generateIdentityKeys(Context context, MasterSecret masterSecret) { - MasterCipher masterCipher = new MasterCipher(masterSecret); - AsymmetricCipherKeyPair keyPair = KeyUtil.generateKeyPair(); - IdentityKey identityKey = new IdentityKey((ECPublicKeyParameters)keyPair.getPublic()); - byte[] serializedPublicKey = identityKey.serialize(); - byte[] serializedPrivateKey = masterCipher.encryptKey((ECPrivateKeyParameters)keyPair.getPrivate()); - - save(context, IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(serializedPublicKey)); - save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(serializedPrivateKey)); + ECKeyPair nistKeyPair = Curve.generateKeyPairForType(Curve.NIST_TYPE); + ECKeyPair djbKeyPair = Curve.generateKeyPairForType(Curve.DJB_TYPE); + + MasterCipher masterCipher = new MasterCipher(masterSecret); + IdentityKey nistIdentityKey = new IdentityKey(nistKeyPair.getPublicKey()); + IdentityKey djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey()); + + byte[] nistPrivateKey = masterCipher.encryptKey(nistKeyPair.getPrivateKey()); + byte[] djbPrivateKey = masterCipher.encryptKey(djbKeyPair.getPrivateKey()); + + save(context, IDENTITY_PUBLIC_KEY_NIST_PREF, Base64.encodeBytes(nistIdentityKey.serialize())); + save(context, IDENTITY_PUBLIC_KEY_DJB_PREF, Base64.encodeBytes(djbIdentityKey.serialize())); + + save(context, IDENTITY_PRIVATE_KEY_NIST_PREF, Base64.encodeBytes(nistPrivateKey)); + save(context, IDENTITY_PRIVATE_KEY_DJB_PREF, Base64.encodeBytes(djbPrivateKey)); + } + + public static boolean hasCurve25519IdentityKeys(Context context) { + return + retrieve(context, IDENTITY_PUBLIC_KEY_DJB_PREF) != null && + retrieve(context, IDENTITY_PRIVATE_KEY_DJB_PREF) != null; + } + + public static void generateCurve25519IdentityKeys(Context context, MasterSecret masterSecret) { + MasterCipher masterCipher = new MasterCipher(masterSecret); + ECKeyPair djbKeyPair = Curve.generateKeyPairForType(Curve.DJB_TYPE); + IdentityKey djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey()); + byte[] djbPrivateKey = masterCipher.encryptKey(djbKeyPair.getPrivateKey()); + + save(context, IDENTITY_PUBLIC_KEY_DJB_PREF, Base64.encodeBytes(djbIdentityKey.serialize())); + save(context, IDENTITY_PRIVATE_KEY_DJB_PREF, Base64.encodeBytes(djbPrivateKey)); } - public static IdentityKey verifySignedKeyExchange(byte[] keyExchangeBytes) throws InvalidKeyException { + public static IdentityKey verifySignedKeyExchange(byte[] keyExchangeBytes) + throws InvalidKeyException + { try { byte[] messageBytes = new byte[1 + PublicKey.KEY_SIZE]; System.arraycopy(keyExchangeBytes, 0, messageBytes, 0, messageBytes.length); @@ -128,8 +185,12 @@ public class IdentityKeyUtil { byte[] messageHash = getMessageHash(messageBytes, publicKeyBytes); IdentityKey identityKey = new IdentityKey(publicKeyBytes, 0); ECDSASigner verifier = new ECDSASigner(); - - verifier.init(false, identityKey.getPublicKeyParameters()); + + if (identityKey.getPublicKey().getType() != Curve.NIST_TYPE) { + throw new InvalidKeyException("Signing only support on P256 keys!"); + } + + verifier.init(false, ((NistECPublicKey)identityKey.getPublicKey()).getParameters()); ASN1Sequence sequence = (ASN1Sequence) ASN1Primitive.fromByteArray(signatureBytes); BigInteger[] signatureIntegers = new BigInteger[]{ @@ -148,17 +209,18 @@ public class IdentityKeyUtil { } - public static byte[] getSignedKeyExchange(Context context, MasterSecret masterSecret, byte[] keyExchangeBytes) { + public static byte[] getSignedKeyExchange(Context context, MasterSecret masterSecret, + byte[] keyExchangeBytes) + { try { - - MasterCipher masterCipher = new MasterCipher(masterSecret); - byte[] publicKeyBytes = getIdentityKey(context).serialize(); - byte[] messageHash = getMessageHash(keyExchangeBytes, publicKeyBytes); - byte[] privateKeyBytes = Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_PREF)); - ECPrivateKeyParameters privateKey = masterCipher.decryptKey(privateKeyBytes); - ECDSASigner signer = new ECDSASigner(); + MasterCipher masterCipher = new MasterCipher(masterSecret); + byte[] publicKeyBytes = getIdentityKey(context, Curve.NIST_TYPE).serialize(); + byte[] messageHash = getMessageHash(keyExchangeBytes, publicKeyBytes); + byte[] privateKeyBytes = Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_NIST_PREF)); + ECPrivateKey privateKey = masterCipher.decryptKey(Curve.NIST_TYPE, privateKeyBytes); + ECDSASigner signer = new ECDSASigner(); - signer.init(true, privateKey); + signer.init(true, ((NistECPrivateKey)privateKey).getParameters()); BigInteger[] messageSignatureInts = signer.generateSignature(messageHash); DERInteger[] derMessageSignatureInts = new DERInteger[]{ new DERInteger(messageSignatureInts[0]), new DERInteger(messageSignatureInts[1]) }; @@ -167,12 +229,12 @@ public class IdentityKeyUtil { Conversions.shortToByteArray(messageSignature, 0, messageSignatureBytes.length); System.arraycopy(messageSignatureBytes, 0, messageSignature, 2, messageSignatureBytes.length); - - byte[] combined = Util.combine(keyExchangeBytes, publicKeyBytes, messageSignature); - - return combined; + + return Util.combine(keyExchangeBytes, publicKeyBytes, messageSignature); } catch (IOException ioe) { throw new AssertionError(ioe); + } catch (InvalidKeyException e) { + throw new AssertionError(e); } } diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java index e3a3d13537..05e0027301 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Whisper Systems + * Copyright (C) 2013 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,6 +29,7 @@ import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.storage.LocalKeyRecord; public class KeyExchangeInitiator { @@ -52,8 +54,8 @@ public class KeyExchangeInitiator { } private static void initiateKeyExchange(Context context, MasterSecret masterSecret, Recipient recipient) { - LocalKeyRecord record = KeyUtil.initializeRecordFor(recipient, context, masterSecret); - KeyExchangeMessage message = new KeyExchangeMessage(context, masterSecret, 1, record, 0); + LocalKeyRecord record = KeyUtil.initializeRecordFor(context, masterSecret, recipient, CiphertextMessage.CURVE25519_INTRODUCED_VERSION); + KeyExchangeMessage message = new KeyExchangeMessage(context, masterSecret, CiphertextMessage.CURVE25519_INTRODUCED_VERSION, record, 0); OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, message.serialize()); Log.w("SendKeyActivity", "Sending public key: " + record.getCurrentKeyPair().getPublicKey().getFingerprint()); diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java index 360ca80b6a..78de44d75a 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Whisper Systems + * Copyright (C) 2013 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -71,11 +72,7 @@ public class KeyExchangeProcessor { } public boolean isTrusted(KeyExchangeMessage message) { - if (!message.hasIdentityKey()) { - return false; - } - - return isTrusted(message.getIdentityKey()); + return message.hasIdentityKey() && isTrusted(message.getIdentityKey()); } public boolean isTrusted(PreKeyBundleMessage message) { @@ -155,7 +152,7 @@ public class KeyExchangeProcessor { remoteKeyRecord.setLastRemoteKey(remoteKey); remoteKeyRecord.save(); - localKeyRecord = KeyUtil.initializeRecordFor(recipient, context, masterSecret); + localKeyRecord = KeyUtil.initializeRecordFor(context, masterSecret, recipient, CiphertextMessage.SUPPORTED_VERSION); localKeyRecord.setNextKeyPair(localKeyRecord.getCurrentKeyPair()); localKeyRecord.save(); @@ -176,7 +173,7 @@ public class KeyExchangeProcessor { message.getPublicKey().setId(initiateKeyId); if (needsResponseFromUs()) { - localKeyRecord = KeyUtil.initializeRecordFor(recipient, context, masterSecret); + localKeyRecord = KeyUtil.initializeRecordFor(context, masterSecret, recipient, message.getMessageVersion()); KeyExchangeMessage ourMessage = new KeyExchangeMessage(context, masterSecret, Math.min(CiphertextMessage.SUPPORTED_VERSION, message.getMaxVersion()), localKeyRecord, initiateKeyId); OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, ourMessage.serialize()); Log.w("KeyExchangeProcessor", "Responding with key exchange message fingerprint: " + ourMessage.getPublicKey().getFingerprint()); diff --git a/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java b/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java index df102813fc..146925a8c9 100644 --- a/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Whisper Systems + * Copyright (C) 2013 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,14 +22,14 @@ import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.util.Log; -import org.spongycastle.crypto.AsymmetricCipherKeyPair; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; import org.whispersystems.textsecure.crypto.InvalidKeyException; -import org.whispersystems.textsecure.crypto.KeyPair; -import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.MasterCipher; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.PublicKey; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECKeyPair; +import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Util; @@ -57,7 +58,11 @@ public class MasterSecretUtil { public static final String UNENCRYPTED_PASSPHRASE = "unencrypted"; public static final String PREFERENCES_NAME = "SecureSMS-Preferences"; - public static final String ASYMMETRIC_LOCAL_PUBLIC = "asymmetric_master_secret_public"; + + private static final String ASYMMETRIC_LOCAL_PUBLIC_NIST = "asymmetric_master_secret_public"; + private static final String ASYMMETRIC_LOCAL_PRIVATE_NIST = "asymmetric_master_secret_private"; + private static final String ASYMMETRIC_LOCAL_PUBLIC_DJB = "asymmetric_master_secret_curve25519_public"; + private static final String ASYMMETRIC_LOCAL_PRIVATE_DJB = "asymmetric_master_secret_curve25519_private"; public static MasterSecret changeMasterSecretPassphrase(Context context, MasterSecret masterSecret, @@ -86,7 +91,9 @@ public class MasterSecretUtil { return masterSecret; } - public static MasterSecret getMasterSecret(Context context, String passphrase) throws InvalidPassphraseException { + public static MasterSecret getMasterSecret(Context context, String passphrase) + throws InvalidPassphraseException + { try { byte[] encryptedAndMacdMasterSecret = retrieve(context, "master_secret"); byte[] encryptedMasterSecret = verifyMac(context, encryptedAndMacdMasterSecret, passphrase); @@ -95,7 +102,7 @@ public class MasterSecretUtil { byte[] macSecret = getMacSecret(combinedSecrets); return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"), - new SecretKeySpec(macSecret, "HmacSHA1")); + new SecretKeySpec(macSecret, "HmacSHA1")); } catch (GeneralSecurityException e) { Log.w("keyutil", e); return null; //XXX @@ -105,17 +112,43 @@ public class MasterSecretUtil { } } - public static AsymmetricMasterSecret getAsymmetricMasterSecret(Context context, MasterSecret masterSecret) { + public static AsymmetricMasterSecret getAsymmetricMasterSecret(Context context, + MasterSecret masterSecret) + { try { - PublicKey publicKey = new PublicKey(retrieve(context, ASYMMETRIC_LOCAL_PUBLIC)); - ECPrivateKeyParameters privateKey = null; + byte[] nistPublicBytes = retrieve(context, ASYMMETRIC_LOCAL_PUBLIC_NIST); + byte[] djbPublicBytes = retrieve(context, ASYMMETRIC_LOCAL_PUBLIC_DJB); + + byte[] nistPrivateBytes = retrieve(context, ASYMMETRIC_LOCAL_PRIVATE_NIST); + byte[] djbPrivateBytes = retrieve(context, ASYMMETRIC_LOCAL_PRIVATE_DJB); + + ECPublicKey nistPublicKey = null; + ECPublicKey djbPublicKey = null; + + ECPrivateKey nistPrivateKey = null; + ECPrivateKey djbPrivateKey = null; + + if (nistPublicBytes != null) { + nistPublicKey = new PublicKey(nistPublicBytes, 0).getKey(); + } + + if (djbPublicBytes != null) { + djbPublicKey = Curve.decodePoint(djbPublicBytes, 0); + } if (masterSecret != null) { MasterCipher masterCipher = new MasterCipher(masterSecret); - privateKey = masterCipher.decryptKey(retrieve(context, "asymmetric_master_secret_private")); + + if (nistPrivateBytes != null) { + nistPrivateKey = masterCipher.decryptKey(Curve.NIST_TYPE, nistPrivateBytes); + } + + if (djbPrivateBytes != null) { + djbPrivateKey = masterCipher.decryptKey(Curve.DJB_TYPE, djbPrivateBytes); + } } - return new AsymmetricMasterSecret(publicKey, privateKey); + return new AsymmetricMasterSecret(djbPublicKey, djbPrivateKey, nistPublicKey, nistPrivateKey); } catch (InvalidKeyException ike) { throw new AssertionError(ike); } catch (IOException e) { @@ -123,17 +156,16 @@ public class MasterSecretUtil { } } - public static AsymmetricMasterSecret generateAsymmetricMasterSecret(Context context, MasterSecret masterSecret) { - MasterCipher masterCipher = new MasterCipher(masterSecret); - AsymmetricCipherKeyPair ackp = KeyUtil.generateKeyPair(); - KeyPair keyPair = new KeyPair(31337, ackp, masterSecret); - PublicKey publicKey = keyPair.getPublicKey(); - ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters)ackp.getPrivate(); + public static AsymmetricMasterSecret generateAsymmetricMasterSecret(Context context, + MasterSecret masterSecret) + { + MasterCipher masterCipher = new MasterCipher(masterSecret); + ECKeyPair keyPair = Curve.generateKeyPairForType(Curve.DJB_TYPE); - save(context, ASYMMETRIC_LOCAL_PUBLIC, publicKey.serialize()); - save(context, "asymmetric_master_secret_private", masterCipher.encryptKey(privateKey)); + save(context, ASYMMETRIC_LOCAL_PUBLIC_DJB, keyPair.getPublicKey().serialize()); + save(context, ASYMMETRIC_LOCAL_PRIVATE_DJB, masterCipher.encryptKey(keyPair.getPrivateKey())); - return new AsymmetricMasterSecret(publicKey, privateKey); + return new AsymmetricMasterSecret(keyPair.getPublicKey(), keyPair.getPrivateKey(), null, null); } public static MasterSecret generateMasterSecret(Context context, String passphrase) { @@ -154,7 +186,10 @@ public class MasterSecretUtil { public static boolean hasAsymmericMasterSecret(Context context) { SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0); - return settings.contains(ASYMMETRIC_LOCAL_PUBLIC); + + return + settings.contains(ASYMMETRIC_LOCAL_PUBLIC_NIST) || + settings.contains(ASYMMETRIC_LOCAL_PUBLIC_DJB); } public static boolean isPassphraseInitialized(Context context) { diff --git a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java index f5e81c0f12..2e02c07a81 100644 --- a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java +++ b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Whisper Systems + * Copyright (C) 2013 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,6 +26,8 @@ import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidVersionException; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.PublicKey; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.storage.LocalKeyRecord; import org.whispersystems.textsecure.util.Base64; @@ -59,8 +62,6 @@ import java.io.IOException; public class KeyExchangeMessage { - private static final int SUPPORTED_VERSION = CiphertextMessage.SUPPORTED_VERSION; - private final int messageVersion; private final int supportedVersion; private final PublicKey publicKey; @@ -70,7 +71,7 @@ public class KeyExchangeMessage { public KeyExchangeMessage(Context context, MasterSecret masterSecret, int messageVersion, LocalKeyRecord record, int highIdBits) { this.publicKey = new PublicKey(record.getCurrentKeyPair().getPublicKey()); this.messageVersion = messageVersion; - this.supportedVersion = SUPPORTED_VERSION; + this.supportedVersion = CiphertextMessage.SUPPORTED_VERSION; publicKey.setId(publicKey.getId() | (highIdBits << 12)); @@ -80,11 +81,11 @@ public class KeyExchangeMessage { byte[] serializedBytes; if (includeIdentityNoSignature(messageVersion, context)) { - byte[] identityKey = IdentityKeyUtil.getIdentityKey(context).serialize(); + byte[] identityKey = IdentityKeyUtil.getIdentityKey(context, Curve.DJB_TYPE).serialize(); serializedBytes = Util.combine(versionBytes, publicKeyBytes, identityKey); } else if (includeIdentitySignature(messageVersion, context)) { - byte[] prolog = Util.combine(versionBytes, publicKeyBytes); + byte[] prolog = Util.combine(versionBytes, publicKeyBytes); serializedBytes = IdentityKeyUtil.getSignedKeyExchange(context, masterSecret, prolog); } else { @@ -102,14 +103,13 @@ public class KeyExchangeMessage { this.supportedVersion = Conversions.lowBitsToInt(keyBytes[0]); this.serialized = messageBody; - if (messageVersion > SUPPORTED_VERSION) - throw new InvalidVersionException("Key exchange with version: " + messageVersion + - " but we only support: " + SUPPORTED_VERSION); + if (messageVersion > CiphertextMessage.SUPPORTED_VERSION) + throw new InvalidVersionException("Key exchange with version: " + messageVersion); if (messageVersion >= 1) keyBytes = Base64.decodeWithoutPadding(messageBody); - this.publicKey = new PublicKey(keyBytes, 1); + this.publicKey = new PublicKey(keyBytes, 1); if (keyBytes.length <= PublicKey.KEY_SIZE + 1) { this.identityKey = null; @@ -134,11 +134,11 @@ public class KeyExchangeMessage { } private static boolean includeIdentitySignature(int messageVersion, Context context) { - return IdentityKeyUtil.hasIdentityKey(context) && (messageVersion == 1); + return IdentityKeyUtil.hasIdentityKey(context, Curve.NIST_TYPE) && (messageVersion == 1); } private static boolean includeIdentityNoSignature(int messageVersion, Context context) { - return IdentityKeyUtil.hasIdentityKey(context) && (messageVersion >= 2); + return IdentityKeyUtil.hasIdentityKey(context, Curve.DJB_TYPE) && (messageVersion >= 2); } public PublicKey getPublicKey() { @@ -152,6 +152,10 @@ public class KeyExchangeMessage { public int getMaxVersion() { return supportedVersion; } + + public int getMessageVersion() { + return messageVersion; + } public boolean hasIdentityKey() { return identityKey != null; diff --git a/src/org/thoughtcrime/securesms/database/IdentityDatabase.java b/src/org/thoughtcrime/securesms/database/IdentityDatabase.java index dc1f209d56..2602559922 100644 --- a/src/org/thoughtcrime/securesms/database/IdentityDatabase.java +++ b/src/org/thoughtcrime/securesms/database/IdentityDatabase.java @@ -24,13 +24,14 @@ import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; import android.util.Log; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.recipients.Recipients; import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.MasterCipher; import org.whispersystems.textsecure.crypto.MasterSecret; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientFactory; -import org.thoughtcrime.securesms.recipients.Recipients; +import org.whispersystems.textsecure.crypto.ecc.Curve; import org.whispersystems.textsecure.util.Base64; import java.io.IOException; @@ -89,6 +90,13 @@ public class IdentityDatabase extends Database { } IdentityKey ourIdentity = new IdentityKey(Base64.decode(serializedIdentity), 0); + + if (theirIdentity.getPublicKey().getType() == Curve.DJB_TYPE && + ourIdentity.getPublicKey().getType() == Curve.NIST_TYPE) + { + return true; + } + return ourIdentity.equals(theirIdentity); } else { return true; diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java index 07007ed9da..31fb6642d7 100644 --- a/src/org/thoughtcrime/securesms/service/RegistrationService.java +++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java @@ -19,6 +19,8 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.PreKeyUtil; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; import org.whispersystems.textsecure.directory.Directory; import org.whispersystems.textsecure.push.ContactTokenDetails; import org.whispersystems.textsecure.push.PushServiceSocket; @@ -273,7 +275,7 @@ public class RegistrationService extends Service { throws GcmRegistrationTimeoutException, IOException { setState(new RegistrationState(RegistrationState.STATE_GENERATING_KEYS, number)); - IdentityKey identityKey = IdentityKeyUtil.getIdentityKey(this); + IdentityKey identityKey = IdentityKeyUtil.getIdentityKey(this, Curve.DJB_TYPE); List records = waitForPreKeys(masterSecret); PreKeyRecord lastResort = PreKeyUtil.generateLastResortKey(this, masterSecret); socket.registerPreKeys(identityKey, lastResort, records); diff --git a/src/org/thoughtcrime/securesms/transport/MmsTransport.java b/src/org/thoughtcrime/securesms/transport/MmsTransport.java index 033c9a6960..f90a76e582 100644 --- a/src/org/thoughtcrime/securesms/transport/MmsTransport.java +++ b/src/org/thoughtcrime/securesms/transport/MmsTransport.java @@ -1,3 +1,20 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.thoughtcrime.securesms.transport; import android.content.Context; @@ -16,6 +33,8 @@ import org.thoughtcrime.securesms.mms.MmsSendHelper; import org.thoughtcrime.securesms.mms.TextTransport; import org.thoughtcrime.securesms.protocol.WirePrefix; import org.thoughtcrime.securesms.recipients.Recipient; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.util.Hex; @@ -138,7 +157,7 @@ public class MmsTransport { private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes) { TextTransport transportDetails = new TextTransport(); Recipient recipient = new Recipient(null, recipientString, null, null); - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE); MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey); CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, pduBytes); diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java index 7f9351c39d..8b4e897b03 100644 --- a/src/org/thoughtcrime/securesms/transport/PushTransport.java +++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java @@ -1,3 +1,20 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.thoughtcrime.securesms.transport; import android.content.Context; @@ -21,6 +38,7 @@ import org.whispersystems.textsecure.crypto.IdentityKeyPair; import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MessageCipher; +import org.whispersystems.textsecure.crypto.ecc.Curve; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; import org.whispersystems.textsecure.push.OutgoingPushMessage; @@ -162,7 +180,7 @@ public class PushTransport extends BaseTransport { private byte[] getEncryptedPrekeyBundleMessageForExistingSession(Recipient recipient, byte[] plaintext) { - IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE); IdentityKey identityKey = identityKeyPair.getPublicKey(); MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair); CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext); @@ -177,7 +195,7 @@ public class PushTransport extends BaseTransport { byte[] plaintext) throws IOException { - IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE); IdentityKey identityKey = identityKeyPair.getPublicKey(); PreKeyEntity preKey = socket.getPreKey(pushDestination); KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient); @@ -194,7 +212,7 @@ public class PushTransport extends BaseTransport { private byte[] getEncryptedMessageForExistingSession(Recipient recipient, byte[] plaintext) throws IOException { - IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE); MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair); CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext); diff --git a/src/org/thoughtcrime/securesms/transport/SmsTransport.java b/src/org/thoughtcrime/securesms/transport/SmsTransport.java index fc154c3039..768e36d176 100644 --- a/src/org/thoughtcrime/securesms/transport/SmsTransport.java +++ b/src/org/thoughtcrime/securesms/transport/SmsTransport.java @@ -1,3 +1,20 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.thoughtcrime.securesms.transport; import android.app.PendingIntent; @@ -19,6 +36,8 @@ import org.whispersystems.textsecure.crypto.IdentityKeyPair; import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MessageCipher; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; @@ -116,7 +135,7 @@ public class SmsTransport extends BaseTransport { private ArrayList constructSentIntents(long messageId, long type, ArrayList messages) { ArrayList sentIntents = new ArrayList(messages.size()); - for (String message : messages) { + for (String ignored : messages) { sentIntents.add(PendingIntent.getBroadcast(context, 0, constructSentIntent(context, messageId, type), 0)); @@ -132,7 +151,7 @@ public class SmsTransport extends BaseTransport { ArrayList deliveredIntents = new ArrayList(messages.size()); - for (String message : messages) { + for (String ignored : messages) { deliveredIntents.add(PendingIntent.getBroadcast(context, 0, constructDeliveredIntent(context, messageId, type), 0)); @@ -146,7 +165,9 @@ public class SmsTransport extends BaseTransport { { Recipient recipient = message.getRecipients().getPrimaryRecipient(); String body = message.getMessageBody(); - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, + Curve.DJB_TYPE); + SmsTransportDetails transportDetails = new SmsTransportDetails(); if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) {