mirror of
https://github.com/oxen-io/session-android.git
synced 2025-04-19 16:31:30 +00:00
Migrate to Curve25519.
1) Generate a Curve25519 identity key. 2) Use Curve25519 ephemerals and identities for v2 3DHE agreements. 3) Initiate v2 key exchange messages. 4) Accept v1 key exchange messages. 5) TOFU Curve25519 identities.
This commit is contained in:
parent
a03fff8b24
commit
c38a8aa699
2
.gitignore
vendored
2
.gitignore
vendored
@ -20,3 +20,5 @@ signing.properties
|
|||||||
gradle
|
gradle
|
||||||
gradlew
|
gradlew
|
||||||
gradlew.bat
|
gradlew.bat
|
||||||
|
library/lib/
|
||||||
|
library/obj/
|
||||||
|
@ -22,7 +22,8 @@ dependencies {
|
|||||||
compile 'com.google.protobuf:protobuf-java:2.4.1'
|
compile 'com.google.protobuf:protobuf-java:2.4.1'
|
||||||
compile 'com.madgag:sc-light-jdk15on:1.47.0.2'
|
compile 'com.madgag:sc-light-jdk15on:1.47.0.2'
|
||||||
compile 'com.googlecode.libphonenumber:libphonenumber:5.3'
|
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 {
|
android {
|
||||||
|
17
library/jni/Android.mk
Normal file
17
library/jni/Android.mk
Normal file
@ -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)
|
70
library/jni/curve25519-donna-jni.c
Normal file
70
library/jni/curve25519-donna-jni.c
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
|
#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;
|
||||||
|
}
|
734
library/jni/curve25519-donna.c
Normal file
734
library/jni/curve25519-donna.c
Normal file
@ -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 <agl@imperialviolet.org>
|
||||||
|
*
|
||||||
|
* Derived from public domain C code by Daniel J. Bernstein <djb@cr.yp.to>
|
||||||
|
*
|
||||||
|
* 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 <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
6
library/jni/curve25519-donna.h
Normal file
6
library/jni/curve25519-donna.h
Normal file
@ -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
|
BIN
library/libs/armeabi.jar
Normal file
BIN
library/libs/armeabi.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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.Parcel;
|
||||||
import android.os.Parcelable;
|
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.Hex;
|
||||||
|
import org.whispersystems.textsecure.util.Util;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class for representing an identity key.
|
* A class for representing an identity key.
|
||||||
@ -44,12 +47,12 @@ public class IdentityKey implements Parcelable, SerializableKey {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static final int SIZE = 1 + KeyUtil.POINT_SIZE;
|
public static final int SIZE = 1 + ECPublicKey.KEY_SIZE;
|
||||||
private static final int VERSION = 1;
|
private static final int CURRENT_VESION = 1;
|
||||||
|
|
||||||
private ECPublicKeyParameters publicKey;
|
private ECPublicKey publicKey;
|
||||||
|
|
||||||
public IdentityKey(ECPublicKeyParameters publicKey) {
|
public IdentityKey(ECPublicKey publicKey) {
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,42 +68,41 @@ public class IdentityKey implements Parcelable, SerializableKey {
|
|||||||
initializeFromSerialized(bytes, offset);
|
initializeFromSerialized(bytes, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ECPublicKeyParameters getPublicKeyParameters() {
|
public ECPublicKey getPublicKey() {
|
||||||
return this.publicKey;
|
return publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeFromSerialized(byte[] bytes, int offset) throws InvalidKeyException {
|
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);
|
throw new InvalidKeyException("Unsupported key version: " + version);
|
||||||
|
|
||||||
this.publicKey = KeyUtil.decodePoint(bytes, offset+1);
|
this.publicKey = Curve.decodePoint(bytes, offset + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] serialize() {
|
public byte[] serialize() {
|
||||||
byte[] encodedKey = KeyUtil.encodePoint(publicKey.getQ());
|
byte[] versionBytes = {(byte)CURRENT_VESION};
|
||||||
byte[] combined = new byte[1 + encodedKey.length];
|
byte[] encodedKey = publicKey.serialize();
|
||||||
|
|
||||||
combined[0] = (byte)VERSION;
|
return Util.combine(versionBytes, encodedKey);
|
||||||
System.arraycopy(encodedKey, 0, combined, 1, encodedKey.length);
|
|
||||||
|
|
||||||
return combined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFingerprint() {
|
public String getFingerprint() {
|
||||||
return Hex.toString(serialize());
|
return Hex.toString(publicKey.serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
|
if (other == null) return false;
|
||||||
if (!(other instanceof IdentityKey)) return false;
|
if (!(other instanceof IdentityKey)) return false;
|
||||||
return publicKey.getQ().equals(((IdentityKey)other).publicKey.getQ());
|
|
||||||
|
return publicKey.equals(((IdentityKey) other).getPublicKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return publicKey.getQ().hashCode();
|
return publicKey.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int describeContents() {
|
public int describeContents() {
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecure.crypto;
|
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.
|
* Holder for public and private identity key pair.
|
||||||
@ -26,9 +26,9 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
|||||||
public class IdentityKeyPair {
|
public class IdentityKeyPair {
|
||||||
|
|
||||||
private final IdentityKey publicKey;
|
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.publicKey = publicKey;
|
||||||
this.privateKey = privateKey;
|
this.privateKey = privateKey;
|
||||||
}
|
}
|
||||||
@ -37,7 +37,7 @@ public class IdentityKeyPair {
|
|||||||
return publicKey;
|
return publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ECPrivateKeyParameters getPrivateKey() {
|
public ECPrivateKey getPrivateKey() {
|
||||||
return privateKey;
|
return privateKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -16,13 +17,13 @@
|
|||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecure.crypto;
|
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 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.
|
* Represents a session's active KeyPair.
|
||||||
*
|
*
|
||||||
@ -31,15 +32,15 @@ import android.util.Log;
|
|||||||
|
|
||||||
public class KeyPair {
|
public class KeyPair {
|
||||||
|
|
||||||
private ECPrivateKeyParameters privateKey;
|
|
||||||
private PublicKey publicKey;
|
private PublicKey publicKey;
|
||||||
|
private ECPrivateKey privateKey;
|
||||||
|
|
||||||
private final MasterCipher masterCipher;
|
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.masterCipher = new MasterCipher(masterSecret);
|
||||||
this.publicKey = new PublicKey(keyPairId, (ECPublicKeyParameters)keyPair.getPublic());
|
this.publicKey = new PublicKey(keyPairId, keyPair.getPublicKey());
|
||||||
this.privateKey = (ECPrivateKeyParameters)keyPair.getPrivate();
|
this.privateKey = keyPair.getPrivateKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyPair(byte[] bytes, MasterCipher masterCipher) throws InvalidKeyException {
|
public KeyPair(byte[] bytes, MasterCipher masterCipher) throws InvalidKeyException {
|
||||||
@ -55,8 +56,8 @@ public class KeyPair {
|
|||||||
return publicKey;
|
return publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AsymmetricCipherKeyPair getKeyPair() {
|
public ECPrivateKey getPrivateKey() {
|
||||||
return new AsymmetricCipherKeyPair(publicKey.getKey(), privateKey);
|
return privateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] toBytes() {
|
public byte[] toBytes() {
|
||||||
@ -67,18 +68,14 @@ public class KeyPair {
|
|||||||
this.publicKey = new PublicKey(bytes);
|
this.publicKey = new PublicKey(bytes);
|
||||||
byte[] privateKeyBytes = new byte[bytes.length - PublicKey.KEY_SIZE];
|
byte[] privateKeyBytes = new byte[bytes.length - PublicKey.KEY_SIZE];
|
||||||
System.arraycopy(bytes, PublicKey.KEY_SIZE, privateKeyBytes, 0, privateKeyBytes.length);
|
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() {
|
public byte[] serialize() {
|
||||||
byte[] publicKeyBytes = publicKey.serialize();
|
byte[] publicKeyBytes = publicKey.serialize();
|
||||||
Log.w("KeyPair", "Serialized public key bytes: " + Hex.toString(publicKeyBytes));
|
Log.w("KeyPair", "Serialized public key bytes: " + Hex.toString(publicKeyBytes));
|
||||||
byte[] privateKeyBytes = masterCipher.encryptKey(privateKey);
|
byte[] privateKeyBytes = masterCipher.encryptKey(privateKey);
|
||||||
byte[] combined = new byte[publicKeyBytes.length + privateKeyBytes.length];
|
return Util.combine(publicKeyBytes, privateKeyBytes);
|
||||||
System.arraycopy(publicKeyBytes, 0, combined, 0, publicKeyBytes.length);
|
|
||||||
System.arraycopy(privateKeyBytes, 0, combined, publicKeyBytes.length, privateKeyBytes.length);
|
|
||||||
|
|
||||||
return combined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -16,26 +16,17 @@
|
|||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecure.crypto;
|
package org.whispersystems.textsecure.crypto;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import android.content.Context;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import android.util.Log;
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||||
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.storage.CanonicalRecipientAddress;
|
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
|
||||||
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
||||||
import org.whispersystems.textsecure.storage.RemoteKeyRecord;
|
import org.whispersystems.textsecure.storage.RemoteKeyRecord;
|
||||||
import org.whispersystems.textsecure.storage.SessionRecord;
|
import org.whispersystems.textsecure.storage.SessionRecord;
|
||||||
|
|
||||||
import android.content.Context;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import android.util.Log;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class for generating key pairs and calculating ECDH agreements.
|
* Helper class for generating key pairs and calculating ECDH agreements.
|
||||||
@ -45,52 +36,6 @@ import android.util.Log;
|
|||||||
|
|
||||||
public class KeyUtil {
|
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) {
|
public static void abortSessionFor(Context context, CanonicalRecipientAddress recipient) {
|
||||||
//XXX Obviously we should probably do something more thorough here eventually.
|
//XXX Obviously we should probably do something more thorough here eventually.
|
||||||
Log.w("KeyUtil", "Aborting session, deleting keys...");
|
Log.w("KeyUtil", "Aborting session, deleting keys...");
|
||||||
@ -120,17 +65,18 @@ public class KeyUtil {
|
|||||||
new SessionRecord(context, masterSecret, recipient).getIdentityKey() != null;
|
new SessionRecord(context, masterSecret, recipient).getIdentityKey() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LocalKeyRecord initializeRecordFor(CanonicalRecipientAddress recipient,
|
public static LocalKeyRecord initializeRecordFor(Context context,
|
||||||
Context context,
|
MasterSecret masterSecret,
|
||||||
MasterSecret masterSecret)
|
CanonicalRecipientAddress recipient,
|
||||||
|
int sessionVersion)
|
||||||
{
|
{
|
||||||
Log.w("KeyUtil", "Initializing local key pairs...");
|
Log.w("KeyUtil", "Initializing local key pairs...");
|
||||||
try {
|
try {
|
||||||
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
|
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
|
||||||
int initialId = secureRandom.nextInt(4094) + 1;
|
int initialId = secureRandom.nextInt(4094) + 1;
|
||||||
|
|
||||||
KeyPair currentPair = new KeyPair(initialId, KeyUtil.generateKeyPair(), masterSecret);
|
KeyPair currentPair = new KeyPair(initialId, Curve.generateKeyPairForSession(sessionVersion), masterSecret);
|
||||||
KeyPair nextPair = new KeyPair(initialId + 1, KeyUtil.generateKeyPair(), masterSecret);
|
KeyPair nextPair = new KeyPair(initialId + 1, Curve.generateKeyPairForSession(sessionVersion), masterSecret);
|
||||||
LocalKeyRecord record = new LocalKeyRecord(context, masterSecret, recipient);
|
LocalKeyRecord record = new LocalKeyRecord(context, masterSecret, recipient);
|
||||||
|
|
||||||
record.setCurrentKeyPair(currentPair);
|
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -16,8 +17,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecure.crypto;
|
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.io.IOException;
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
@ -32,12 +39,6 @@ import javax.crypto.NoSuchPaddingException;
|
|||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
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.
|
* Class that handles encryption for local storage.
|
||||||
*
|
*
|
||||||
@ -70,10 +71,8 @@ public class MasterCipher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] encryptKey(ECPrivateKeyParameters params) {
|
public byte[] encryptKey(ECPrivateKey privateKey) {
|
||||||
BigInteger d = params.getD();
|
return encryptBytes(privateKey.serialize());
|
||||||
byte[] dBytes = d.toByteArray();
|
|
||||||
return encryptBytes(dBytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String encryptBody(String body) {
|
public String encryptBody(String body) {
|
||||||
@ -84,13 +83,13 @@ public class MasterCipher {
|
|||||||
return new String(decodeAndDecryptBytes(body));
|
return new String(decodeAndDecryptBytes(body));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ECPrivateKeyParameters decryptKey(byte[] key) {
|
public ECPrivateKey decryptKey(int type, byte[] key)
|
||||||
|
throws org.whispersystems.textsecure.crypto.InvalidKeyException
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
BigInteger d = new BigInteger(decryptBytes(key));
|
return Curve.decodePrivatePoint(type, decryptBytes(key));
|
||||||
return new ECPrivateKeyParameters(d, KeyUtil.domainParameters);
|
|
||||||
} catch (InvalidMessageException ime) {
|
} catch (InvalidMessageException ime) {
|
||||||
Log.w("bodycipher", ime);
|
throw new org.whispersystems.textsecure.crypto.InvalidKeyException(ime);
|
||||||
return null; // XXX
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -58,8 +59,6 @@ public class MessageCipher {
|
|||||||
CiphertextMessage message = new CiphertextMessage(ciphertext);
|
CiphertextMessage message = new CiphertextMessage(ciphertext);
|
||||||
|
|
||||||
int messageVersion = message.getCurrentVersion();
|
int messageVersion = message.getCurrentVersion();
|
||||||
int supportedVersion = message.getSupportedVersion();
|
|
||||||
int negotiatedVersion = Math.min(supportedVersion, CiphertextMessage.SUPPORTED_VERSION);
|
|
||||||
int senderKeyId = message.getSenderKeyId();
|
int senderKeyId = message.getSenderKeyId();
|
||||||
int receiverKeyId = message.getReceiverKeyId();
|
int receiverKeyId = message.getReceiverKeyId();
|
||||||
PublicKey nextRemoteKey = new PublicKey(message.getNextKeyBytes());
|
PublicKey nextRemoteKey = new PublicKey(message.getNextKeyBytes());
|
||||||
@ -73,8 +72,7 @@ public class MessageCipher {
|
|||||||
receiverKeyId,
|
receiverKeyId,
|
||||||
nextRemoteKey,
|
nextRemoteKey,
|
||||||
counter,
|
counter,
|
||||||
messageVersion,
|
messageVersion);
|
||||||
negotiatedVersion);
|
|
||||||
|
|
||||||
message.verifyMac(sessionContext);
|
message.verifyMac(sessionContext);
|
||||||
|
|
||||||
@ -84,5 +82,4 @@ public class MessageCipher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.whispersystems.textsecure.crypto;
|
package org.whispersystems.textsecure.crypto;
|
||||||
|
|
||||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
|
||||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
|
||||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
|
||||||
import org.whispersystems.textsecure.util.Util;
|
import org.whispersystems.textsecure.util.Util;
|
||||||
|
|
||||||
public class PreKeyPair {
|
public class PreKeyPair {
|
||||||
|
|
||||||
private final MasterCipher masterCipher;
|
private final MasterCipher masterCipher;
|
||||||
private final ECPrivateKeyParameters privateKey;
|
|
||||||
private final PreKeyPublic publicKey;
|
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.masterCipher = new MasterCipher(masterSecret);
|
||||||
this.publicKey = new PreKeyPublic((ECPublicKeyParameters)keyPair.getPublic());
|
this.publicKey = new PreKeyPublic(keyPair.getPublicKey());
|
||||||
this.privateKey = (ECPrivateKeyParameters)keyPair.getPrivate();
|
this.privateKey = keyPair.getPrivateKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PreKeyPair(MasterSecret masterSecret, byte[] serialized) throws InvalidKeyException {
|
public PreKeyPair(MasterSecret masterSecret, byte[] serialized) throws InvalidKeyException {
|
||||||
if (serialized.length < KeyUtil.POINT_SIZE + 1)
|
byte[] privateKeyBytes = new byte[serialized.length - PreKeyPublic.KEY_SIZE];
|
||||||
throw new InvalidKeyException("Serialized length: " + serialized.length);
|
System.arraycopy(serialized, PreKeyPublic.KEY_SIZE, privateKeyBytes, 0, privateKeyBytes.length);
|
||||||
|
|
||||||
byte[] privateKeyBytes = new byte[serialized.length - KeyUtil.POINT_SIZE];
|
|
||||||
System.arraycopy(serialized, KeyUtil.POINT_SIZE, privateKeyBytes, 0, privateKeyBytes.length);
|
|
||||||
|
|
||||||
this.masterCipher = new MasterCipher(masterSecret);
|
this.masterCipher = new MasterCipher(masterSecret);
|
||||||
this.publicKey = new PreKeyPublic(serialized, 0);
|
this.publicKey = new PreKeyPublic(serialized, 0);
|
||||||
this.privateKey = masterCipher.decryptKey(privateKeyBytes);
|
this.privateKey = masterCipher.decryptKey(this.publicKey.getType(), privateKeyBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PreKeyPublic getPublicKey() {
|
public PreKeyPublic getPublicKey() {
|
||||||
return publicKey;
|
return publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AsymmetricCipherKeyPair getKeyPair() {
|
public ECKeyPair getKeyPair() {
|
||||||
return new AsymmetricCipherKeyPair(publicKey.getPublicKey(), privateKey);
|
return new ECKeyPair(publicKey.getPublicKey(), privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] serialize() {
|
public byte[] serialize() {
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.whispersystems.textsecure.crypto;
|
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 {
|
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;
|
this.publicKey = publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PreKeyPublic(byte[] serialized, int offset) throws InvalidKeyException {
|
public PreKeyPublic(byte[] serialized, int offset) throws InvalidKeyException {
|
||||||
this.publicKey = KeyUtil.decodePoint(serialized, offset);
|
this.publicKey = Curve.decodePoint(serialized, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] serialize() {
|
public byte[] serialize() {
|
||||||
return KeyUtil.encodePoint(publicKey.getQ());
|
return publicKey.serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ECPublicKeyParameters getPublicKey() {
|
public ECPublicKey getPublicKey() {
|
||||||
return publicKey;
|
return publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
return this.publicKey.getType();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.whispersystems.textsecure.crypto;
|
package org.whispersystems.textsecure.crypto;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.thoughtcrimegson.Gson;
|
import com.google.thoughtcrimegson.Gson;
|
||||||
|
|
||||||
|
import org.whispersystems.textsecure.crypto.ecc.Curve25519;
|
||||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||||
import org.whispersystems.textsecure.storage.PreKeyRecord;
|
import org.whispersystems.textsecure.storage.PreKeyRecord;
|
||||||
import org.whispersystems.textsecure.util.Medium;
|
import org.whispersystems.textsecure.util.Medium;
|
||||||
@ -21,7 +40,7 @@ import java.util.List;
|
|||||||
|
|
||||||
public class PreKeyUtil {
|
public class PreKeyUtil {
|
||||||
|
|
||||||
public static final int BATCH_SIZE = 70;
|
public static final int BATCH_SIZE = 20;
|
||||||
|
|
||||||
public static List<PreKeyRecord> generatePreKeys(Context context, MasterSecret masterSecret) {
|
public static List<PreKeyRecord> generatePreKeys(Context context, MasterSecret masterSecret) {
|
||||||
List<PreKeyRecord> records = new LinkedList<PreKeyRecord>();
|
List<PreKeyRecord> records = new LinkedList<PreKeyRecord>();
|
||||||
@ -29,7 +48,7 @@ public class PreKeyUtil {
|
|||||||
|
|
||||||
for (int i=0;i<BATCH_SIZE;i++) {
|
for (int i=0;i<BATCH_SIZE;i++) {
|
||||||
int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
|
int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
|
||||||
PreKeyPair keyPair = new PreKeyPair(masterSecret, KeyUtil.generateKeyPair());
|
PreKeyPair keyPair = new PreKeyPair(masterSecret, Curve25519.generateKeyPair());
|
||||||
PreKeyRecord record = new PreKeyRecord(context, masterSecret, preKeyId, keyPair);
|
PreKeyRecord record = new PreKeyRecord(context, masterSecret, preKeyId, keyPair);
|
||||||
|
|
||||||
record.save();
|
record.save();
|
||||||
@ -50,7 +69,7 @@ public class PreKeyUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PreKeyPair keyPair = new PreKeyPair(masterSecret, KeyUtil.generateKeyPair());
|
PreKeyPair keyPair = new PreKeyPair(masterSecret, Curve25519.generateKeyPair());
|
||||||
PreKeyRecord record = new PreKeyRecord(context, masterSecret, Medium.MAX_VALUE, keyPair);
|
PreKeyRecord record = new PreKeyRecord(context, masterSecret, Medium.MAX_VALUE, keyPair);
|
||||||
|
|
||||||
record.save();
|
record.save();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -18,17 +19,20 @@ package org.whispersystems.textsecure.crypto;
|
|||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||||
import org.whispersystems.textsecure.util.Hex;
|
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||||
import org.whispersystems.textsecure.util.Conversions;
|
import org.whispersystems.textsecure.util.Conversions;
|
||||||
|
import org.whispersystems.textsecure.util.Hex;
|
||||||
|
import org.whispersystems.textsecure.util.Util;
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
public class PublicKey {
|
public class PublicKey {
|
||||||
public static final int KEY_SIZE = 3 + KeyUtil.POINT_SIZE;
|
|
||||||
|
|
||||||
private final ECPublicKeyParameters publicKey;
|
public static final int KEY_SIZE = 3 + ECPublicKey.KEY_SIZE;
|
||||||
|
|
||||||
|
private final ECPublicKey publicKey;
|
||||||
private int id;
|
private int id;
|
||||||
|
|
||||||
public PublicKey(PublicKey publicKey) {
|
public PublicKey(PublicKey publicKey) {
|
||||||
@ -38,7 +42,7 @@ public class PublicKey {
|
|||||||
this.publicKey = publicKey.publicKey;
|
this.publicKey = publicKey.publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PublicKey(int id, ECPublicKeyParameters publicKey) {
|
public PublicKey(int id, ECPublicKey publicKey) {
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
@ -50,17 +54,22 @@ public class PublicKey {
|
|||||||
|
|
||||||
public PublicKey(byte[] bytes, int offset) throws InvalidKeyException {
|
public PublicKey(byte[] bytes, int offset) throws InvalidKeyException {
|
||||||
Log.w("PublicKey", "PublicKey Length: " + (bytes.length - offset));
|
Log.w("PublicKey", "PublicKey Length: " + (bytes.length - offset));
|
||||||
|
|
||||||
if ((bytes.length - offset) < KEY_SIZE)
|
if ((bytes.length - offset) < KEY_SIZE)
|
||||||
throw new InvalidKeyException("Provided bytes are too short.");
|
throw new InvalidKeyException("Provided bytes are too short.");
|
||||||
|
|
||||||
this.id = Conversions.byteArrayToMedium(bytes, offset);
|
this.id = Conversions.byteArrayToMedium(bytes, offset);
|
||||||
this.publicKey = KeyUtil.decodePoint(bytes, offset + 3);
|
this.publicKey = Curve.decodePoint(bytes, offset + 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PublicKey(byte[] bytes) throws InvalidKeyException {
|
public PublicKey(byte[] bytes) throws InvalidKeyException {
|
||||||
this(bytes, 0);
|
this(bytes, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
return publicKey.getType();
|
||||||
|
}
|
||||||
|
|
||||||
public void setId(int id) {
|
public void setId(int id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
@ -69,7 +78,7 @@ public class PublicKey {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ECPublicKeyParameters getKey() {
|
public ECPublicKey getKey() {
|
||||||
return publicKey;
|
return publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,14 +97,11 @@ public class PublicKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public byte[] serialize() {
|
public byte[] serialize() {
|
||||||
byte[] complete = new byte[KEY_SIZE];
|
byte[] keyIdBytes = Conversions.mediumToByteArray(id);
|
||||||
byte[] serializedPoint = KeyUtil.encodePoint(publicKey.getQ());
|
byte[] serializedPoint = publicKey.serialize();
|
||||||
|
|
||||||
Log.w("PublicKey", "Serializing public key point: " + Hex.toString(serializedPoint));
|
Log.w("PublicKey", "Serializing public key point: " + Hex.toString(serializedPoint));
|
||||||
|
|
||||||
Conversions.mediumToByteArray(complete, 0, id);
|
return Util.combine(keyIdBytes, serializedPoint);
|
||||||
System.arraycopy(serializedPoint, 0, complete, 3, serializedPoint.length);
|
|
||||||
|
|
||||||
return complete;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -19,7 +19,7 @@ package org.whispersystems.textsecure.crypto;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||||
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
|
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
|
||||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||||
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
|
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
|
||||||
@ -30,17 +30,16 @@ import org.whispersystems.textsecure.storage.SessionKey;
|
|||||||
import org.whispersystems.textsecure.storage.SessionRecord;
|
import org.whispersystems.textsecure.storage.SessionRecord;
|
||||||
import org.whispersystems.textsecure.util.Conversions;
|
import org.whispersystems.textsecure.util.Conversions;
|
||||||
|
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is where the session encryption magic happens. Implements a compressed version of the OTR protocol.
|
* This is where the session encryption magic happens. Implements a compressed version of the OTR protocol.
|
||||||
@ -64,16 +63,18 @@ public class SessionCipher {
|
|||||||
KeyRecords records = getKeyRecords(context, masterSecret, recipient);
|
KeyRecords records = getKeyRecords(context, masterSecret, recipient);
|
||||||
int localKeyId = records.getLocalKeyRecord().getCurrentKeyPair().getId();
|
int localKeyId = records.getLocalKeyRecord().getCurrentKeyPair().getId();
|
||||||
int remoteKeyId = records.getRemoteKeyRecord().getCurrentRemoteKey().getId();
|
int remoteKeyId = records.getRemoteKeyRecord().getCurrentRemoteKey().getId();
|
||||||
int negotiatedVersion = records.getSessionRecord().getSessionVersion();
|
int sessionVersion = records.getSessionRecord().getSessionVersion();
|
||||||
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.ENCRYPT_MODE, negotiatedVersion, localIdentityKey, records, localKeyId, remoteKeyId);
|
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.ENCRYPT_MODE, sessionVersion, localIdentityKey, records, localKeyId, remoteKeyId);
|
||||||
PublicKey nextKey = records.getLocalKeyRecord().getNextKeyPair().getPublicKey();
|
PublicKey nextKey = records.getLocalKeyRecord().getNextKeyPair().getPublicKey();
|
||||||
int counter = records.getSessionRecord().getCounter();
|
int counter = records.getSessionRecord().getCounter();
|
||||||
|
|
||||||
|
|
||||||
return new SessionCipherContext(records, sessionKey, localKeyId, remoteKeyId,
|
return new SessionCipherContext(records, sessionKey, localKeyId, remoteKeyId,
|
||||||
nextKey, counter, negotiatedVersion, negotiatedVersion);
|
nextKey, counter, sessionVersion);
|
||||||
} catch (InvalidKeyIdException e) {
|
} catch (InvalidKeyIdException e) {
|
||||||
throw new IllegalArgumentException(e);
|
throw new IllegalArgumentException(e);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +83,7 @@ public class SessionCipher {
|
|||||||
CanonicalRecipientAddress recipient,
|
CanonicalRecipientAddress recipient,
|
||||||
int senderKeyId, int recipientKeyId,
|
int senderKeyId, int recipientKeyId,
|
||||||
PublicKey nextKey, int counter,
|
PublicKey nextKey, int counter,
|
||||||
int messageVersion, int negotiatedVersion)
|
int messageVersion)
|
||||||
throws InvalidMessageException
|
throws InvalidMessageException
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@ -94,12 +95,16 @@ public class SessionCipher {
|
|||||||
records.getSessionRecord().getNegotiatedSessionVersion());
|
records.getSessionRecord().getNegotiatedSessionVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE, messageVersion, localIdentityKey, records, recipientKeyId, senderKeyId);
|
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE, messageVersion,
|
||||||
|
localIdentityKey, records, recipientKeyId, senderKeyId);
|
||||||
|
|
||||||
return new SessionCipherContext(records, sessionKey, senderKeyId,
|
return new SessionCipherContext(records, sessionKey, senderKeyId,
|
||||||
recipientKeyId, nextKey, counter,
|
recipientKeyId, nextKey, counter,
|
||||||
messageVersion, negotiatedVersion);
|
messageVersion);
|
||||||
} catch (InvalidKeyIdException e) {
|
} catch (InvalidKeyIdException e) {
|
||||||
throw new InvalidMessageException(e);
|
throw new InvalidMessageException(e);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new InvalidMessageException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +130,9 @@ public class SessionCipher {
|
|||||||
{
|
{
|
||||||
Log.w("SessionCipher", "Decrypting message...");
|
Log.w("SessionCipher", "Decrypting message...");
|
||||||
try {
|
try {
|
||||||
byte[] plaintextWithPadding = getPlaintext(decodedCiphertext, context.getSessionKey().getCipherKey(), context.getCounter());
|
byte[] plaintextWithPadding = getPlaintext(decodedCiphertext,
|
||||||
|
context.getSessionKey().getCipherKey(),
|
||||||
|
context.getCounter());
|
||||||
|
|
||||||
context.getRemoteKeyRecord().updateCurrentRemoteKey(context.getNextKey());
|
context.getRemoteKeyRecord().updateCurrentRemoteKey(context.getNextKey());
|
||||||
context.getRemoteKeyRecord().save();
|
context.getRemoteKeyRecord().save();
|
||||||
@ -134,7 +141,6 @@ public class SessionCipher {
|
|||||||
context.getLocalKeyRecord().save();
|
context.getLocalKeyRecord().save();
|
||||||
|
|
||||||
context.getSessionRecord().setSessionKey(context.getSessionKey());
|
context.getSessionRecord().setSessionKey(context.getSessionKey());
|
||||||
context.getSessionRecord().setSessionVersion(context.getNegotiatedVersion());
|
|
||||||
context.getSessionRecord().setPrekeyBundleRequired(false);
|
context.getSessionRecord().setPrekeyBundleRequired(false);
|
||||||
context.getSessionRecord().save();
|
context.getSessionRecord().save();
|
||||||
|
|
||||||
@ -175,7 +181,7 @@ public class SessionCipher {
|
|||||||
throw new IllegalArgumentException("AES Not Supported!");
|
throw new IllegalArgumentException("AES Not Supported!");
|
||||||
} catch (NoSuchPaddingException e) {
|
} catch (NoSuchPaddingException e) {
|
||||||
throw new IllegalArgumentException("NoPadding Not Supported!");
|
throw new IllegalArgumentException("NoPadding Not Supported!");
|
||||||
} catch (InvalidKeyException e) {
|
} catch (java.security.InvalidKeyException e) {
|
||||||
Log.w("SessionCipher", e);
|
Log.w("SessionCipher", e);
|
||||||
throw new IllegalArgumentException("Invaid Key?");
|
throw new IllegalArgumentException("Invaid Key?");
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
@ -189,7 +195,7 @@ public class SessionCipher {
|
|||||||
IdentityKeyPair localIdentityKey,
|
IdentityKeyPair localIdentityKey,
|
||||||
KeyRecords records,
|
KeyRecords records,
|
||||||
int localKeyId, int remoteKeyId)
|
int localKeyId, int remoteKeyId)
|
||||||
throws InvalidKeyIdException
|
throws InvalidKeyIdException, InvalidKeyException
|
||||||
{
|
{
|
||||||
Log.w("SessionCipher", "Getting session key for local: " + localKeyId + " remote: " + remoteKeyId);
|
Log.w("SessionCipher", "Getting session key for local: " + localKeyId + " remote: " + remoteKeyId);
|
||||||
SessionKey sessionKey = records.getSessionRecord().getSessionKey(mode, localKeyId, remoteKeyId);
|
SessionKey sessionKey = records.getSessionRecord().getSessionKey(mode, localKeyId, remoteKeyId);
|
||||||
@ -208,10 +214,10 @@ public class SessionCipher {
|
|||||||
IdentityKeyPair localIdentityKey,
|
IdentityKeyPair localIdentityKey,
|
||||||
KeyRecords records,
|
KeyRecords records,
|
||||||
int localKeyId, int remoteKeyId)
|
int localKeyId, int remoteKeyId)
|
||||||
throws InvalidKeyIdException
|
throws InvalidKeyIdException, InvalidKeyException
|
||||||
{
|
{
|
||||||
KeyPair localKeyPair = records.getLocalKeyRecord().getKeyPairForId(localKeyId);
|
KeyPair localKeyPair = records.getLocalKeyRecord().getKeyPairForId(localKeyId);
|
||||||
ECPublicKeyParameters remoteKey = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
|
ECPublicKey remoteKey = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
|
||||||
IdentityKey remoteIdentityKey = records.getSessionRecord().getIdentityKey();
|
IdentityKey remoteIdentityKey = records.getSessionRecord().getIdentityKey();
|
||||||
boolean isLowEnd = isLowEnd(records, localKeyId, remoteKeyId);
|
boolean isLowEnd = isLowEnd(records, localKeyId, remoteKeyId);
|
||||||
|
|
||||||
@ -233,13 +239,10 @@ public class SessionCipher {
|
|||||||
private boolean isLowEnd(KeyRecords records, int localKeyId, int remoteKeyId)
|
private boolean isLowEnd(KeyRecords records, int localKeyId, int remoteKeyId)
|
||||||
throws InvalidKeyIdException
|
throws InvalidKeyIdException
|
||||||
{
|
{
|
||||||
ECPublicKeyParameters localPublic = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().getKey();
|
ECPublicKey localPublic = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().getKey();
|
||||||
ECPublicKeyParameters remotePublic = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
|
ECPublicKey remotePublic = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
|
||||||
|
|
||||||
BigInteger local = localPublic.getQ().getX().toBigInteger();
|
return localPublic.compareTo(remotePublic) < 0;
|
||||||
BigInteger remote = remotePublic.getQ().getX().toBigInteger();
|
|
||||||
|
|
||||||
return local.compareTo(remote) < 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isInitiallyExchangedKeys(KeyRecords records, int localKeyId, int remoteKeyId)
|
private boolean isInitiallyExchangedKeys(KeyRecords records, int localKeyId, int remoteKeyId)
|
||||||
@ -297,7 +300,6 @@ public class SessionCipher {
|
|||||||
private final PublicKey nextKey;
|
private final PublicKey nextKey;
|
||||||
private final int counter;
|
private final int counter;
|
||||||
private final int messageVersion;
|
private final int messageVersion;
|
||||||
private final int negotiatedVersion;
|
|
||||||
|
|
||||||
public SessionCipherContext(KeyRecords records,
|
public SessionCipherContext(KeyRecords records,
|
||||||
SessionKey sessionKey,
|
SessionKey sessionKey,
|
||||||
@ -305,8 +307,7 @@ public class SessionCipher {
|
|||||||
int receiverKeyId,
|
int receiverKeyId,
|
||||||
PublicKey nextKey,
|
PublicKey nextKey,
|
||||||
int counter,
|
int counter,
|
||||||
int messageVersion,
|
int messageVersion)
|
||||||
int negotiatedVersion)
|
|
||||||
{
|
{
|
||||||
this.localKeyRecord = records.getLocalKeyRecord();
|
this.localKeyRecord = records.getLocalKeyRecord();
|
||||||
this.remoteKeyRecord = records.getRemoteKeyRecord();
|
this.remoteKeyRecord = records.getRemoteKeyRecord();
|
||||||
@ -317,7 +318,6 @@ public class SessionCipher {
|
|||||||
this.nextKey = nextKey;
|
this.nextKey = nextKey;
|
||||||
this.counter = counter;
|
this.counter = counter;
|
||||||
this.messageVersion = messageVersion;
|
this.messageVersion = messageVersion;
|
||||||
this.negotiatedVersion = negotiatedVersion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalKeyRecord getLocalKeyRecord() {
|
public LocalKeyRecord getLocalKeyRecord() {
|
||||||
@ -352,10 +352,6 @@ public class SessionCipher {
|
|||||||
return recipientKeyId;
|
return recipientKeyId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getNegotiatedVersion() {
|
|
||||||
return negotiatedVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMessageVersion() {
|
public int getMessageVersion() {
|
||||||
return messageVersion;
|
return messageVersion;
|
||||||
}
|
}
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.whispersystems.textsecure.crypto;
|
package org.whispersystems.textsecure.crypto;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.spongycastle.crypto.CipherParameters;
|
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||||
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
|
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
|
||||||
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
|
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
|
||||||
import org.whispersystems.textsecure.crypto.kdf.HKDF;
|
import org.whispersystems.textsecure.crypto.kdf.HKDF;
|
||||||
import org.whispersystems.textsecure.crypto.kdf.KDF;
|
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.crypto.protocol.CiphertextMessage;
|
||||||
import org.whispersystems.textsecure.util.Conversions;
|
import org.whispersystems.textsecure.util.Conversions;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -21,34 +36,34 @@ public class SharedSecretCalculator {
|
|||||||
public static DerivedSecrets calculateSharedSecret(boolean isLowEnd, KeyPair localKeyPair,
|
public static DerivedSecrets calculateSharedSecret(boolean isLowEnd, KeyPair localKeyPair,
|
||||||
int localKeyId,
|
int localKeyId,
|
||||||
IdentityKeyPair localIdentityKeyPair,
|
IdentityKeyPair localIdentityKeyPair,
|
||||||
ECPublicKeyParameters remoteKey,
|
ECPublicKey remoteKey,
|
||||||
int remoteKeyId,
|
int remoteKeyId,
|
||||||
IdentityKey remoteIdentityKey)
|
IdentityKey remoteIdentityKey)
|
||||||
|
throws InvalidKeyException
|
||||||
{
|
{
|
||||||
Log.w("SharedSecretCalculator", "Calculating shared secret with cradle agreement...");
|
Log.w("SharedSecretCalculator", "Calculating shared secret with 3DHE agreement...");
|
||||||
KDF kdf = new HKDF();
|
KDF kdf = new HKDF();
|
||||||
List<BigInteger> results = new LinkedList<BigInteger>();
|
List<byte[]> results = new LinkedList<byte[]>();
|
||||||
|
|
||||||
if (isSmaller(localKeyPair.getPublicKey().getKey(), remoteKey)) {
|
if (isSmaller(localKeyPair.getPublicKey().getKey(), remoteKey)) {
|
||||||
results.add(calculateAgreement(localIdentityKeyPair.getPrivateKey(), remoteKey));
|
results.add(Curve.calculateAgreement(remoteKey, localIdentityKeyPair.getPrivateKey()));
|
||||||
|
results.add(Curve.calculateAgreement(remoteIdentityKey.getPublicKey(),
|
||||||
results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(),
|
localKeyPair.getPrivateKey()));
|
||||||
remoteIdentityKey.getPublicKeyParameters()));
|
|
||||||
} else {
|
} else {
|
||||||
results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(),
|
results.add(Curve.calculateAgreement(remoteIdentityKey.getPublicKey(),
|
||||||
remoteIdentityKey.getPublicKeyParameters()));
|
localKeyPair.getPrivateKey()));
|
||||||
|
results.add(Curve.calculateAgreement(remoteKey, localIdentityKeyPair.getPrivateKey()));
|
||||||
results.add(calculateAgreement(localIdentityKeyPair.getPrivateKey(), remoteKey));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
public static DerivedSecrets calculateSharedSecret(int messageVersion, boolean isLowEnd,
|
||||||
KeyPair localKeyPair, int localKeyId,
|
KeyPair localKeyPair, int localKeyId,
|
||||||
ECPublicKeyParameters remoteKey, int remoteKeyId)
|
ECPublicKey remoteKey, int remoteKeyId)
|
||||||
|
throws InvalidKeyException
|
||||||
{
|
{
|
||||||
Log.w("SharedSecretCalculator", "Calculating shared secret with standard agreement...");
|
Log.w("SharedSecretCalculator", "Calculating shared secret with standard agreement...");
|
||||||
KDF kdf;
|
KDF kdf;
|
||||||
@ -58,8 +73,8 @@ public class SharedSecretCalculator {
|
|||||||
|
|
||||||
Log.w("SharedSecretCalculator", "Using kdf: " + kdf);
|
Log.w("SharedSecretCalculator", "Using kdf: " + kdf);
|
||||||
|
|
||||||
List<BigInteger> results = new LinkedList<BigInteger>();
|
List<byte[]> results = new LinkedList<byte[]>();
|
||||||
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));
|
||||||
}
|
}
|
||||||
@ -78,23 +93,10 @@ public class SharedSecretCalculator {
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BigInteger calculateAgreement(CipherParameters privateKey,
|
private static boolean isSmaller(ECPublicKey localPublic,
|
||||||
ECPublicKeyParameters publicKey)
|
ECPublicKey remotePublic)
|
||||||
{
|
{
|
||||||
ECDHBasicAgreement agreement = new ECDHBasicAgreement();
|
return localPublic.compareTo(remotePublic) < 0;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.whispersystems.textsecure.crypto.ecc;
|
||||||
|
|
||||||
|
public interface ECPrivateKey {
|
||||||
|
public byte[] serialize();
|
||||||
|
public int getType();
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.whispersystems.textsecure.crypto.ecc;
|
||||||
|
|
||||||
|
public interface ECPublicKey extends Comparable<ECPublicKey> {
|
||||||
|
|
||||||
|
public static final int KEY_SIZE = 33;
|
||||||
|
|
||||||
|
public byte[] serialize();
|
||||||
|
|
||||||
|
public int getType();
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.whispersystems.textsecure.crypto.kdf;
|
package org.whispersystems.textsecure.crypto.kdf;
|
||||||
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.whispersystems.textsecure.crypto.kdf;
|
package org.whispersystems.textsecure.crypto.kdf;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@ -18,7 +35,7 @@ public class HKDF extends KDF {
|
|||||||
private static final int MAC_KEYS_OFFSET = 32;
|
private static final int MAC_KEYS_OFFSET = 32;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DerivedSecrets deriveSecrets(List<BigInteger> sharedSecret,
|
public DerivedSecrets deriveSecrets(List<byte[]> sharedSecret,
|
||||||
boolean isLowEnd, byte[] info)
|
boolean isLowEnd, byte[] info)
|
||||||
{
|
{
|
||||||
byte[] inputKeyMaterial = concatenateSharedSecrets(sharedSecret);
|
byte[] inputKeyMaterial = concatenateSharedSecrets(sharedSecret);
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.whispersystems.textsecure.crypto.kdf;
|
package org.whispersystems.textsecure.crypto.kdf;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class KDF {
|
public abstract class KDF {
|
||||||
|
|
||||||
public abstract DerivedSecrets deriveSecrets(List<BigInteger> sharedSecret,
|
public abstract DerivedSecrets deriveSecrets(List<byte[]> sharedSecret,
|
||||||
boolean isLowEnd, byte[] info);
|
boolean isLowEnd, byte[] info);
|
||||||
|
|
||||||
protected byte[] concatenateSharedSecrets(List<BigInteger> sharedSecrets) {
|
protected byte[] concatenateSharedSecrets(List<byte[]> sharedSecrets) {
|
||||||
int totalByteSize = 0;
|
try {
|
||||||
List<byte[]> byteValues = new LinkedList<byte[]>();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
|
||||||
for (BigInteger sharedSecret : sharedSecrets) {
|
for (byte[] sharedSecret : sharedSecrets) {
|
||||||
byte[] byteValue = sharedSecret.toByteArray();
|
baos.write(sharedSecret);
|
||||||
totalByteSize += byteValue.length;
|
|
||||||
byteValues.add(byteValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] combined = new byte[totalByteSize];
|
return baos.toByteArray();
|
||||||
int offset = 0;
|
} catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
for (byte[] byteValue : byteValues) {
|
|
||||||
System.arraycopy(byteValue, 0, combined, offset, byteValue.length);
|
|
||||||
offset += byteValue.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return combined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.whispersystems.textsecure.crypto.kdf;
|
package org.whispersystems.textsecure.crypto.kdf;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.whispersystems.textsecure.util.Conversions;
|
import org.whispersystems.textsecure.util.Conversions;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -14,7 +30,7 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
public class NKDF extends KDF {
|
public class NKDF extends KDF {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DerivedSecrets deriveSecrets(List<BigInteger> sharedSecret,
|
public DerivedSecrets deriveSecrets(List<byte[]> sharedSecret,
|
||||||
boolean isLowEnd, byte[] info)
|
boolean isLowEnd, byte[] info)
|
||||||
{
|
{
|
||||||
SecretKeySpec cipherKey = deriveCipherSecret(isLowEnd, sharedSecret);
|
SecretKeySpec cipherKey = deriveCipherSecret(isLowEnd, sharedSecret);
|
||||||
@ -23,7 +39,7 @@ public class NKDF extends KDF {
|
|||||||
return new DerivedSecrets(cipherKey, macKey);
|
return new DerivedSecrets(cipherKey, macKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SecretKeySpec deriveCipherSecret(boolean isLowEnd, List<BigInteger> sharedSecret) {
|
private SecretKeySpec deriveCipherSecret(boolean isLowEnd, List<byte[]> sharedSecret) {
|
||||||
byte[] sharedSecretBytes = concatenateSharedSecrets(sharedSecret);
|
byte[] sharedSecretBytes = concatenateSharedSecrets(sharedSecret);
|
||||||
byte[] derivedBytes = deriveBytes(sharedSecretBytes, 16 * 2);
|
byte[] derivedBytes = deriveBytes(sharedSecretBytes, 16 * 2);
|
||||||
byte[] cipherSecret = new byte[16];
|
byte[] cipherSecret = new byte[16];
|
||||||
|
@ -11,6 +11,7 @@ public class CiphertextMessage {
|
|||||||
|
|
||||||
public static final int SUPPORTED_VERSION = 2;
|
public static final int SUPPORTED_VERSION = 2;
|
||||||
public static final int DHE3_INTRODUCED_VERSION = 2;
|
public static final int DHE3_INTRODUCED_VERSION = 2;
|
||||||
|
public static final int CURVE25519_INTRODUCED_VERSION = 2;
|
||||||
|
|
||||||
static final int VERSION_LENGTH = 1;
|
static final int VERSION_LENGTH = 1;
|
||||||
private static final int SENDER_KEY_ID_LENGTH = 3;
|
private static final int SENDER_KEY_ID_LENGTH = 3;
|
||||||
|
@ -77,7 +77,9 @@ public class PreKeyBundleMessage {
|
|||||||
|
|
||||||
messageBytes[VERSION_OFFSET] = bundledMessageBytes[VERSION_OFFSET];
|
messageBytes[VERSION_OFFSET] = bundledMessageBytes[VERSION_OFFSET];
|
||||||
System.arraycopy(identityKeyBytes, 0, messageBytes, IDENTITY_KEY_OFFSET, identityKeyBytes.length);
|
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() {
|
public byte[] serialize() {
|
||||||
|
@ -133,6 +133,7 @@ public class PushServiceSocket {
|
|||||||
PreKeyEntity entity = new PreKeyEntity(record.getId(),
|
PreKeyEntity entity = new PreKeyEntity(record.getId(),
|
||||||
record.getKeyPair().getPublicKey(),
|
record.getKeyPair().getPublicKey(),
|
||||||
identityKey);
|
identityKey);
|
||||||
|
|
||||||
entities.add(entity);
|
entities.add(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +141,9 @@ public class PushServiceSocket {
|
|||||||
lastResortKey.getKeyPair().getPublicKey(),
|
lastResortKey.getKeyPair().getPublicKey(),
|
||||||
identityKey);
|
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 {
|
public PreKeyEntity getPreKey(PushDestination destination) throws IOException {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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.InvalidKeyException;
|
||||||
import org.whispersystems.textsecure.crypto.KeyPair;
|
import org.whispersystems.textsecure.crypto.KeyPair;
|
||||||
import org.whispersystems.textsecure.crypto.KeyUtil;
|
|
||||||
import org.whispersystems.textsecure.crypto.MasterCipher;
|
import org.whispersystems.textsecure.crypto.MasterCipher;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
|
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||||
import org.whispersystems.textsecure.util.Medium;
|
import org.whispersystems.textsecure.util.Medium;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
@ -65,9 +66,12 @@ public class LocalKeyRecord extends Record {
|
|||||||
public void advanceKeyIfNecessary(int keyId) {
|
public void advanceKeyIfNecessary(int keyId) {
|
||||||
Log.w("LocalKeyRecord", "Remote client acknowledges receiving key id: " + keyId);
|
Log.w("LocalKeyRecord", "Remote client acknowledges receiving key id: " + keyId);
|
||||||
if (keyId == localNextKeyPair.getId()) {
|
if (keyId == localNextKeyPair.getId()) {
|
||||||
|
int keyType = this.localNextKeyPair.getPublicKey().getType();
|
||||||
|
|
||||||
this.localCurrentKeyPair = this.localNextKeyPair;
|
this.localCurrentKeyPair = this.localNextKeyPair;
|
||||||
this.localNextKeyPair = new KeyPair((this.localNextKeyPair.getId()+1) % Medium.MAX_VALUE,
|
this.localNextKeyPair = new KeyPair((this.localNextKeyPair.getId()+1) % Medium.MAX_VALUE,
|
||||||
KeyUtil.generateKeyPair(), masterSecret);
|
Curve.generateKeyPairForType(keyType),
|
||||||
|
masterSecret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@ -10,6 +27,7 @@ import android.util.Log;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.util.VersionTracker;
|
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 NO_MORE_KEY_EXCHANGE_PREFIX_VERSION = 46;
|
||||||
public static final int MMS_BODY_VERSION = 46;
|
public static final int MMS_BODY_VERSION = 46;
|
||||||
public static final int TOFU_IDENTITIES_VERSION = 50;
|
public static final int TOFU_IDENTITIES_VERSION = 50;
|
||||||
|
public static final int CURVE25519_VERSION = 58;
|
||||||
|
|
||||||
private static final SortedSet<Integer> UPGRADE_VERSIONS = new TreeSet<Integer>() {{
|
private static final SortedSet<Integer> UPGRADE_VERSIONS = new TreeSet<Integer>() {{
|
||||||
add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
|
add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
|
||||||
add(TOFU_IDENTITIES_VERSION);
|
add(TOFU_IDENTITIES_VERSION);
|
||||||
|
add(CURVE25519_VERSION);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
private MasterSecret masterSecret;
|
private MasterSecret masterSecret;
|
||||||
@ -33,9 +53,9 @@ public class DatabaseUpgradeActivity extends Activity {
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle) {
|
public void onCreate(Bundle bundle) {
|
||||||
super.onCreate(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...");
|
Log.w("DatabaseUpgradeActivity", "Upgrading...");
|
||||||
setContentView(R.layout.database_upgrade_activity);
|
setContentView(R.layout.database_upgrade_activity);
|
||||||
|
|
||||||
@ -51,7 +71,7 @@ public class DatabaseUpgradeActivity extends Activity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean needsDatabaseUpgrade() {
|
private boolean needsUpgradeTask() {
|
||||||
try {
|
try {
|
||||||
int currentVersionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;
|
int currentVersionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;
|
||||||
int lastSeenVersion = VersionTracker.getLastSeenVersion(this);
|
int lastSeenVersion = VersionTracker.getLastSeenVersion(this);
|
||||||
@ -102,10 +122,18 @@ public class DatabaseUpgradeActivity extends Activity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Integer... params) {
|
protected Void doInBackground(Integer... params) {
|
||||||
|
Context context = DatabaseUpgradeActivity.this.getApplicationContext();
|
||||||
|
|
||||||
Log.w("DatabaseUpgradeActivity", "Running background upgrade..");
|
Log.w("DatabaseUpgradeActivity", "Running background upgrade..");
|
||||||
DatabaseFactory.getInstance(DatabaseUpgradeActivity.this)
|
DatabaseFactory.getInstance(DatabaseUpgradeActivity.this)
|
||||||
.onApplicationLevelUpgrade(DatabaseUpgradeActivity.this.getApplicationContext(),
|
.onApplicationLevelUpgrade(context, masterSecret, params[0], this);
|
||||||
masterSecret, params[0], this);
|
|
||||||
|
if (params[0] < CURVE25519_VERSION) {
|
||||||
|
if (!IdentityKeyUtil.hasCurve25519IdentityKeys(context)) {
|
||||||
|
IdentityKeyUtil.generateCurve25519IdentityKeys(context, masterSecret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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.whispersystems.textsecure.crypto.IdentityKey;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
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.whispersystems.textsecure.storage.SessionRecord;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.MemoryCleaner;
|
import org.thoughtcrime.securesms.util.MemoryCleaner;
|
||||||
@ -40,6 +44,8 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
|
|||||||
private TextView localIdentityFingerprint;
|
private TextView localIdentityFingerprint;
|
||||||
private TextView remoteIdentityFingerprint;
|
private TextView remoteIdentityFingerprint;
|
||||||
|
|
||||||
|
private int keyType;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle state) {
|
public void onCreate(Bundle state) {
|
||||||
super.onCreate(state);
|
super.onCreate(state);
|
||||||
@ -57,12 +63,12 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeLocalIdentityKey() {
|
private void initializeLocalIdentityKey() {
|
||||||
if (!IdentityKeyUtil.hasIdentityKey(this)) {
|
if (!IdentityKeyUtil.hasIdentityKey(this, keyType)) {
|
||||||
localIdentityFingerprint.setText(R.string.VerifyIdentityActivity_you_do_not_have_an_identity_key);
|
localIdentityFingerprint.setText(R.string.VerifyIdentityActivity_you_do_not_have_an_identity_key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
localIdentityFingerprint.setText(IdentityKeyUtil.getFingerprint(this));
|
localIdentityFingerprint.setText(IdentityKeyUtil.getFingerprint(this, keyType));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeRemoteIdentityKey() {
|
private void initializeRemoteIdentityKey() {
|
||||||
@ -86,15 +92,24 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeResources() {
|
private void initializeResources() {
|
||||||
localIdentityFingerprint = (TextView)findViewById(R.id.you_read);
|
this.localIdentityFingerprint = (TextView)findViewById(R.id.you_read);
|
||||||
remoteIdentityFingerprint = (TextView)findViewById(R.id.friend_reads);
|
this.remoteIdentityFingerprint = (TextView)findViewById(R.id.friend_reads);
|
||||||
recipient = (Recipient)this.getIntent().getParcelableExtra("recipient");
|
this.recipient = this.getIntent().getParcelableExtra("recipient");
|
||||||
masterSecret = (MasterSecret)this.getIntent().getParcelableExtra("master_secret");
|
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
|
@Override
|
||||||
protected void initiateDisplay() {
|
protected void initiateDisplay() {
|
||||||
if (!IdentityKeyUtil.hasIdentityKey(this)) {
|
if (!IdentityKeyUtil.hasIdentityKey(this, keyType)) {
|
||||||
Toast.makeText(this,
|
Toast.makeText(this,
|
||||||
R.string.VerifyIdentityActivity_you_don_t_have_an_identity_key_exclamation,
|
R.string.VerifyIdentityActivity_you_don_t_have_an_identity_key_exclamation,
|
||||||
Toast.LENGTH_LONG).show();
|
Toast.LENGTH_LONG).show();
|
||||||
@ -135,7 +150,7 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IdentityKey getIdentityKeyToDisplay() {
|
protected IdentityKey getIdentityKeyToDisplay() {
|
||||||
return IdentityKeyUtil.getIdentityKey(this);
|
return IdentityKeyUtil.getIdentityKey(this, keyType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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 com.actionbarsherlock.view.MenuItem;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
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.
|
* 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) {
|
public void onCreate(Bundle bundle) {
|
||||||
this.masterSecret = getIntent().getParcelableExtra("master_secret");
|
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) + " " +
|
getIntent().putExtra("title", getString(R.string.ApplicationPreferencesActivity_my) + " " +
|
||||||
getString(R.string.ViewIdentityActivity_identity_fingerprint));
|
getString(R.string.ViewIdentityActivity_identity_fingerprint));
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
@ -113,7 +116,8 @@ public class ViewLocalIdentityActivity extends ViewIdentityActivity {
|
|||||||
Toast.LENGTH_LONG).show();
|
Toast.LENGTH_LONG).show();
|
||||||
|
|
||||||
getIntent().putExtra("identity_key",
|
getIntent().putExtra("identity_key",
|
||||||
IdentityKeyUtil.getIdentityKey(ViewLocalIdentityActivity.this));
|
IdentityKeyUtil.getIdentityKey(ViewLocalIdentityActivity.this,
|
||||||
|
Curve.DJB_TYPE));
|
||||||
initialize();
|
initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -16,25 +17,25 @@
|
|||||||
*/
|
*/
|
||||||
package org.thoughtcrime.securesms.crypto;
|
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.io.IOException;
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
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
|
* 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
|
* where TextSecure receives an SMS, but the user's local encryption passphrase is
|
||||||
@ -66,18 +67,15 @@ public class AsymmetricMasterCipher {
|
|||||||
public String decryptBody(String body) throws IOException, InvalidMessageException {
|
public String decryptBody(String body) throws IOException, InvalidMessageException {
|
||||||
try {
|
try {
|
||||||
byte[] combined = Base64.decode(body);
|
byte[] combined = Base64.decode(body);
|
||||||
PublicKey theirPublicKey = new PublicKey(combined, 0);
|
byte[][] parts = Util.split(combined, PublicKey.KEY_SIZE, combined.length - PublicKey.KEY_SIZE);
|
||||||
byte[] encryptedBodyBytes = new byte[combined.length - PublicKey.KEY_SIZE];
|
PublicKey theirPublicKey = new PublicKey(parts[0], 0);
|
||||||
System.arraycopy(combined, PublicKey.KEY_SIZE, encryptedBodyBytes, 0, encryptedBodyBytes.length);
|
|
||||||
|
|
||||||
ECDHBasicAgreement agreement = new ECDHBasicAgreement();
|
ECPrivateKey ourPrivateKey = asymmetricMasterSecret.getPrivateKey(theirPublicKey.getType());
|
||||||
agreement.init(asymmetricMasterSecret.getPrivateKey());
|
byte[] secret = Curve.calculateAgreement(theirPublicKey.getKey(), ourPrivateKey);
|
||||||
|
|
||||||
BigInteger secret = KeyUtil.calculateAgreement(agreement, theirPublicKey.getKey());
|
|
||||||
MasterCipher masterCipher = getMasterCipherForSecret(secret);
|
MasterCipher masterCipher = getMasterCipherForSecret(secret);
|
||||||
byte[] decryptedBodyBytes = masterCipher.decryptBytes(encryptedBodyBytes);
|
byte[] decryptedBody = masterCipher.decryptBytes(parts[1]);
|
||||||
|
|
||||||
return new String(decryptedBodyBytes);
|
return new String(decryptedBody);
|
||||||
} catch (InvalidKeyException ike) {
|
} catch (InvalidKeyException ike) {
|
||||||
throw new InvalidMessageException(ike);
|
throw new InvalidMessageException(ike);
|
||||||
} catch (InvalidMessageException e) {
|
} catch (InvalidMessageException e) {
|
||||||
@ -86,26 +84,31 @@ public class AsymmetricMasterCipher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String encryptBody(String body) {
|
public String encryptBody(String body) {
|
||||||
ECDHBasicAgreement agreement = new ECDHBasicAgreement();
|
try {
|
||||||
AsymmetricCipherKeyPair keyPair = KeyUtil.generateKeyPair();
|
ECPublicKey theirPublic;
|
||||||
|
|
||||||
agreement.init(keyPair.getPrivate());
|
if (asymmetricMasterSecret.getDjbPublicKey() != null) {
|
||||||
|
theirPublic = asymmetricMasterSecret.getDjbPublicKey();
|
||||||
BigInteger secret = KeyUtil.calculateAgreement(agreement, asymmetricMasterSecret.getPublicKey().getKey());
|
} else {
|
||||||
MasterCipher masterCipher = getMasterCipherForSecret(secret);
|
theirPublic = asymmetricMasterSecret.getNistPublicKey();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private MasterCipher getMasterCipherForSecret(BigInteger secret) {
|
ECKeyPair ourKeyPair = Curve.generateKeyPairForType(theirPublic.getType());
|
||||||
byte[] secretBytes = secret.toByteArray();
|
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(byte[] secretBytes) {
|
||||||
SecretKeySpec cipherKey = deriveCipherKey(secretBytes);
|
SecretKeySpec cipherKey = deriveCipherKey(secretBytes);
|
||||||
SecretKeySpec macKey = deriveMacKey(secretBytes);
|
SecretKeySpec macKey = deriveMacKey(secretBytes);
|
||||||
MasterSecret masterSecret = new MasterSecret(cipherKey, macKey);
|
MasterSecret masterSecret = new MasterSecret(cipherKey, macKey);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -16,8 +17,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.thoughtcrime.securesms.crypto;
|
package org.thoughtcrime.securesms.crypto;
|
||||||
|
|
||||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||||
import org.whispersystems.textsecure.crypto.PublicKey;
|
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
|
||||||
|
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When a user first initializes TextSecure, a few secrets
|
* When a user first initializes TextSecure, a few secrets
|
||||||
@ -38,19 +40,35 @@ import org.whispersystems.textsecure.crypto.PublicKey;
|
|||||||
|
|
||||||
public class AsymmetricMasterSecret {
|
public class AsymmetricMasterSecret {
|
||||||
|
|
||||||
private final PublicKey publicKey;
|
private final ECPublicKey djbPublicKey;
|
||||||
private final ECPrivateKeyParameters privateKey;
|
private final ECPrivateKey djbPrivateKey;
|
||||||
|
|
||||||
public AsymmetricMasterSecret(PublicKey publicKey, ECPrivateKeyParameters privateKey) {
|
private final ECPublicKey nistPublicKey;
|
||||||
this.publicKey = publicKey;
|
private final ECPrivateKey nistPrivateKey;
|
||||||
this.privateKey = privateKey;
|
|
||||||
|
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() {
|
public ECPublicKey getDjbPublicKey() {
|
||||||
return publicKey;
|
return djbPublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ECPrivateKeyParameters getPrivateKey() {
|
public ECPublicKey getNistPublicKey() {
|
||||||
return privateKey;
|
return nistPublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ECPrivateKey getPrivateKey(int type) {
|
||||||
|
if (type == Curve.DJB_TYPE) {
|
||||||
|
return djbPrivateKey;
|
||||||
|
} else {
|
||||||
|
return nistPrivateKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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.MasterSecret;
|
||||||
import org.whispersystems.textsecure.crypto.MessageCipher;
|
import org.whispersystems.textsecure.crypto.MessageCipher;
|
||||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
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.push.IncomingPushMessage;
|
||||||
import org.whispersystems.textsecure.util.Hex;
|
import org.whispersystems.textsecure.util.Hex;
|
||||||
|
|
||||||
@ -195,7 +198,7 @@ public class DecryptingQueue {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE);
|
||||||
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
|
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
|
||||||
byte[] plaintextBody = messageCipher.decrypt(recipient, message.getBody());
|
byte[] plaintextBody = messageCipher.decrypt(recipient, message.getBody());
|
||||||
|
|
||||||
@ -276,7 +279,7 @@ public class DecryptingQueue {
|
|||||||
synchronized (SessionCipher.CIPHER_LOCK) {
|
synchronized (SessionCipher.CIPHER_LOCK) {
|
||||||
Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes));
|
Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes));
|
||||||
TextTransport transportDetails = new TextTransport();
|
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);
|
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
|
||||||
byte[] ciphertext = transportDetails.getDecodedMessage(ciphertextPduBytes);
|
byte[] ciphertext = transportDetails.getDecodedMessage(ciphertextPduBytes);
|
||||||
|
|
||||||
@ -360,7 +363,7 @@ public class DecryptingQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SmsTransportDetails transportDetails = new SmsTransportDetails();
|
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);
|
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
|
||||||
byte[] ciphertext = transportDetails.getDecodedMessage(body.getBytes());
|
byte[] ciphertext = transportDetails.getDecodedMessage(body.getBytes());
|
||||||
byte[] paddedPlaintext = messageCipher.decrypt(recipient, ciphertext);
|
byte[] paddedPlaintext = messageCipher.decrypt(recipient, ciphertext);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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.ASN1Sequence;
|
||||||
import org.spongycastle.asn1.DERInteger;
|
import org.spongycastle.asn1.DERInteger;
|
||||||
import org.spongycastle.asn1.DERSequence;
|
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.spongycastle.crypto.signers.ECDSASigner;
|
||||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||||
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
||||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||||
import org.whispersystems.textsecure.crypto.KeyUtil;
|
|
||||||
import org.whispersystems.textsecure.crypto.MasterCipher;
|
import org.whispersystems.textsecure.crypto.MasterCipher;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
import org.whispersystems.textsecure.crypto.PublicKey;
|
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.Base64;
|
||||||
import org.whispersystems.textsecure.util.Conversions;
|
import org.whispersystems.textsecure.util.Conversions;
|
||||||
import org.whispersystems.textsecure.util.Util;
|
import org.whispersystems.textsecure.util.Util;
|
||||||
@ -54,19 +56,39 @@ import java.security.NoSuchAlgorithmException;
|
|||||||
|
|
||||||
public class IdentityKeyUtil {
|
public class IdentityKeyUtil {
|
||||||
|
|
||||||
private static final String IDENTITY_PUBLIC_KEY_PREF = "pref_identity_public";
|
private static final String IDENTITY_PUBLIC_KEY_NIST_PREF = "pref_identity_public";
|
||||||
private static final String IDENTITY_PRIVATE_KEY_PREF = "pref_identity_private";
|
private static final String IDENTITY_PRIVATE_KEY_NIST_PREF = "pref_identity_private";
|
||||||
|
|
||||||
public static boolean hasIdentityKey(Context context) {
|
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, int type) {
|
||||||
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IdentityKey getIdentityKey(Context context) {
|
return false;
|
||||||
if (!hasIdentityKey(context)) return null;
|
}
|
||||||
|
|
||||||
|
public static IdentityKey getIdentityKey(Context context, int type) {
|
||||||
|
if (!hasIdentityKey(context, type)) return null;
|
||||||
|
|
||||||
try {
|
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);
|
return new IdentityKey(publicKeyBytes, 0);
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
Log.w("IdentityKeyUtil", ioe);
|
Log.w("IdentityKeyUtil", ioe);
|
||||||
@ -77,43 +99,78 @@ public class IdentityKeyUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IdentityKeyPair getIdentityKeyPair(Context context, MasterSecret masterSecret) {
|
public static IdentityKeyPair getIdentityKeyPair(Context context,
|
||||||
if (!hasIdentityKey(context))
|
MasterSecret masterSecret,
|
||||||
|
int type)
|
||||||
|
{
|
||||||
|
if (!hasIdentityKey(context, type))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
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);
|
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||||
IdentityKey publicKey = getIdentityKey(context);
|
IdentityKey publicKey = getIdentityKey(context, type);
|
||||||
byte[] privateKeyBytes = Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_PREF));
|
ECPrivateKey privateKey = masterCipher.decryptKey(type, Base64.decode(retrieve(context, key)));
|
||||||
ECPrivateKeyParameters privateKey = masterCipher.decryptKey(privateKeyBytes);
|
|
||||||
|
|
||||||
return new IdentityKeyPair(publicKey, privateKey);
|
return new IdentityKeyPair(publicKey, privateKey);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getFingerprint(Context context) {
|
public static String getFingerprint(Context context, int type) {
|
||||||
if (!hasIdentityKey(context)) return null;
|
if (!hasIdentityKey(context, type)) return null;
|
||||||
|
|
||||||
IdentityKey identityKey = getIdentityKey(context);
|
IdentityKey identityKey = getIdentityKey(context, type);
|
||||||
|
|
||||||
if (identityKey == null) return null;
|
if (identityKey == null) return null;
|
||||||
else return identityKey.getFingerprint();
|
else return identityKey.getFingerprint();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void generateIdentityKeys(Context context, MasterSecret masterSecret) {
|
public static void generateIdentityKeys(Context context, MasterSecret masterSecret) {
|
||||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
ECKeyPair nistKeyPair = Curve.generateKeyPairForType(Curve.NIST_TYPE);
|
||||||
AsymmetricCipherKeyPair keyPair = KeyUtil.generateKeyPair();
|
ECKeyPair djbKeyPair = Curve.generateKeyPairForType(Curve.DJB_TYPE);
|
||||||
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));
|
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||||
save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(serializedPrivateKey));
|
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 IdentityKey verifySignedKeyExchange(byte[] keyExchangeBytes) throws InvalidKeyException {
|
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
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
byte[] messageBytes = new byte[1 + PublicKey.KEY_SIZE];
|
byte[] messageBytes = new byte[1 + PublicKey.KEY_SIZE];
|
||||||
System.arraycopy(keyExchangeBytes, 0, messageBytes, 0, messageBytes.length);
|
System.arraycopy(keyExchangeBytes, 0, messageBytes, 0, messageBytes.length);
|
||||||
@ -129,7 +186,11 @@ public class IdentityKeyUtil {
|
|||||||
IdentityKey identityKey = new IdentityKey(publicKeyBytes, 0);
|
IdentityKey identityKey = new IdentityKey(publicKeyBytes, 0);
|
||||||
ECDSASigner verifier = new ECDSASigner();
|
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);
|
ASN1Sequence sequence = (ASN1Sequence) ASN1Primitive.fromByteArray(signatureBytes);
|
||||||
BigInteger[] signatureIntegers = new BigInteger[]{
|
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 {
|
try {
|
||||||
|
|
||||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||||
byte[] publicKeyBytes = getIdentityKey(context).serialize();
|
byte[] publicKeyBytes = getIdentityKey(context, Curve.NIST_TYPE).serialize();
|
||||||
byte[] messageHash = getMessageHash(keyExchangeBytes, publicKeyBytes);
|
byte[] messageHash = getMessageHash(keyExchangeBytes, publicKeyBytes);
|
||||||
byte[] privateKeyBytes = Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_PREF));
|
byte[] privateKeyBytes = Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_NIST_PREF));
|
||||||
ECPrivateKeyParameters privateKey = masterCipher.decryptKey(privateKeyBytes);
|
ECPrivateKey privateKey = masterCipher.decryptKey(Curve.NIST_TYPE, privateKeyBytes);
|
||||||
ECDSASigner signer = new ECDSASigner();
|
ECDSASigner signer = new ECDSASigner();
|
||||||
|
|
||||||
signer.init(true, privateKey);
|
signer.init(true, ((NistECPrivateKey)privateKey).getParameters());
|
||||||
|
|
||||||
BigInteger[] messageSignatureInts = signer.generateSignature(messageHash);
|
BigInteger[] messageSignatureInts = signer.generateSignature(messageHash);
|
||||||
DERInteger[] derMessageSignatureInts = new DERInteger[]{ new DERInteger(messageSignatureInts[0]), new DERInteger(messageSignatureInts[1]) };
|
DERInteger[] derMessageSignatureInts = new DERInteger[]{ new DERInteger(messageSignatureInts[0]), new DERInteger(messageSignatureInts[1]) };
|
||||||
@ -168,11 +230,11 @@ public class IdentityKeyUtil {
|
|||||||
Conversions.shortToByteArray(messageSignature, 0, messageSignatureBytes.length);
|
Conversions.shortToByteArray(messageSignature, 0, messageSignatureBytes.length);
|
||||||
System.arraycopy(messageSignatureBytes, 0, messageSignature, 2, messageSignatureBytes.length);
|
System.arraycopy(messageSignatureBytes, 0, messageSignature, 2, messageSignatureBytes.length);
|
||||||
|
|
||||||
byte[] combined = Util.combine(keyExchangeBytes, publicKeyBytes, messageSignature);
|
return Util.combine(keyExchangeBytes, publicKeyBytes, messageSignature);
|
||||||
|
|
||||||
return combined;
|
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
throw new AssertionError(ioe);
|
throw new AssertionError(ioe);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
|
||||||
import org.whispersystems.textsecure.crypto.KeyUtil;
|
import org.whispersystems.textsecure.crypto.KeyUtil;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
|
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||||
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
||||||
|
|
||||||
public class KeyExchangeInitiator {
|
public class KeyExchangeInitiator {
|
||||||
@ -52,8 +54,8 @@ public class KeyExchangeInitiator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void initiateKeyExchange(Context context, MasterSecret masterSecret, Recipient recipient) {
|
private static void initiateKeyExchange(Context context, MasterSecret masterSecret, Recipient recipient) {
|
||||||
LocalKeyRecord record = KeyUtil.initializeRecordFor(recipient, context, masterSecret);
|
LocalKeyRecord record = KeyUtil.initializeRecordFor(context, masterSecret, recipient, CiphertextMessage.CURVE25519_INTRODUCED_VERSION);
|
||||||
KeyExchangeMessage message = new KeyExchangeMessage(context, masterSecret, 1, record, 0);
|
KeyExchangeMessage message = new KeyExchangeMessage(context, masterSecret, CiphertextMessage.CURVE25519_INTRODUCED_VERSION, record, 0);
|
||||||
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, message.serialize());
|
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, message.serialize());
|
||||||
|
|
||||||
Log.w("SendKeyActivity", "Sending public key: " + record.getCurrentKeyPair().getPublicKey().getFingerprint());
|
Log.w("SendKeyActivity", "Sending public key: " + record.getCurrentKeyPair().getPublicKey().getFingerprint());
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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) {
|
public boolean isTrusted(KeyExchangeMessage message) {
|
||||||
if (!message.hasIdentityKey()) {
|
return message.hasIdentityKey() && isTrusted(message.getIdentityKey());
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isTrusted(message.getIdentityKey());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTrusted(PreKeyBundleMessage message) {
|
public boolean isTrusted(PreKeyBundleMessage message) {
|
||||||
@ -155,7 +152,7 @@ public class KeyExchangeProcessor {
|
|||||||
remoteKeyRecord.setLastRemoteKey(remoteKey);
|
remoteKeyRecord.setLastRemoteKey(remoteKey);
|
||||||
remoteKeyRecord.save();
|
remoteKeyRecord.save();
|
||||||
|
|
||||||
localKeyRecord = KeyUtil.initializeRecordFor(recipient, context, masterSecret);
|
localKeyRecord = KeyUtil.initializeRecordFor(context, masterSecret, recipient, CiphertextMessage.SUPPORTED_VERSION);
|
||||||
localKeyRecord.setNextKeyPair(localKeyRecord.getCurrentKeyPair());
|
localKeyRecord.setNextKeyPair(localKeyRecord.getCurrentKeyPair());
|
||||||
localKeyRecord.save();
|
localKeyRecord.save();
|
||||||
|
|
||||||
@ -176,7 +173,7 @@ public class KeyExchangeProcessor {
|
|||||||
message.getPublicKey().setId(initiateKeyId);
|
message.getPublicKey().setId(initiateKeyId);
|
||||||
|
|
||||||
if (needsResponseFromUs()) {
|
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);
|
KeyExchangeMessage ourMessage = new KeyExchangeMessage(context, masterSecret, Math.min(CiphertextMessage.SUPPORTED_VERSION, message.getMaxVersion()), localKeyRecord, initiateKeyId);
|
||||||
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, ourMessage.serialize());
|
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, ourMessage.serialize());
|
||||||
Log.w("KeyExchangeProcessor", "Responding with key exchange message fingerprint: " + ourMessage.getPublicKey().getFingerprint());
|
Log.w("KeyExchangeProcessor", "Responding with key exchange message fingerprint: " + ourMessage.getPublicKey().getFingerprint());
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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.content.SharedPreferences.Editor;
|
||||||
import android.util.Log;
|
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.InvalidKeyException;
|
||||||
import org.whispersystems.textsecure.crypto.KeyPair;
|
|
||||||
import org.whispersystems.textsecure.crypto.KeyUtil;
|
|
||||||
import org.whispersystems.textsecure.crypto.MasterCipher;
|
import org.whispersystems.textsecure.crypto.MasterCipher;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
import org.whispersystems.textsecure.crypto.PublicKey;
|
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.Base64;
|
||||||
import org.whispersystems.textsecure.util.Util;
|
import org.whispersystems.textsecure.util.Util;
|
||||||
|
|
||||||
@ -57,7 +58,11 @@ public class MasterSecretUtil {
|
|||||||
|
|
||||||
public static final String UNENCRYPTED_PASSPHRASE = "unencrypted";
|
public static final String UNENCRYPTED_PASSPHRASE = "unencrypted";
|
||||||
public static final String PREFERENCES_NAME = "SecureSMS-Preferences";
|
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,
|
public static MasterSecret changeMasterSecretPassphrase(Context context,
|
||||||
MasterSecret masterSecret,
|
MasterSecret masterSecret,
|
||||||
@ -86,7 +91,9 @@ public class MasterSecretUtil {
|
|||||||
return masterSecret;
|
return masterSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MasterSecret getMasterSecret(Context context, String passphrase) throws InvalidPassphraseException {
|
public static MasterSecret getMasterSecret(Context context, String passphrase)
|
||||||
|
throws InvalidPassphraseException
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
byte[] encryptedAndMacdMasterSecret = retrieve(context, "master_secret");
|
byte[] encryptedAndMacdMasterSecret = retrieve(context, "master_secret");
|
||||||
byte[] encryptedMasterSecret = verifyMac(context, encryptedAndMacdMasterSecret, passphrase);
|
byte[] encryptedMasterSecret = verifyMac(context, encryptedAndMacdMasterSecret, passphrase);
|
||||||
@ -105,17 +112,43 @@ public class MasterSecretUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AsymmetricMasterSecret getAsymmetricMasterSecret(Context context, MasterSecret masterSecret) {
|
public static AsymmetricMasterSecret getAsymmetricMasterSecret(Context context,
|
||||||
|
MasterSecret masterSecret)
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
PublicKey publicKey = new PublicKey(retrieve(context, ASYMMETRIC_LOCAL_PUBLIC));
|
byte[] nistPublicBytes = retrieve(context, ASYMMETRIC_LOCAL_PUBLIC_NIST);
|
||||||
ECPrivateKeyParameters privateKey = null;
|
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) {
|
if (masterSecret != null) {
|
||||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||||
privateKey = masterCipher.decryptKey(retrieve(context, "asymmetric_master_secret_private"));
|
|
||||||
|
if (nistPrivateBytes != null) {
|
||||||
|
nistPrivateKey = masterCipher.decryptKey(Curve.NIST_TYPE, nistPrivateBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AsymmetricMasterSecret(publicKey, privateKey);
|
if (djbPrivateBytes != null) {
|
||||||
|
djbPrivateKey = masterCipher.decryptKey(Curve.DJB_TYPE, djbPrivateBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AsymmetricMasterSecret(djbPublicKey, djbPrivateKey, nistPublicKey, nistPrivateKey);
|
||||||
} catch (InvalidKeyException ike) {
|
} catch (InvalidKeyException ike) {
|
||||||
throw new AssertionError(ike);
|
throw new AssertionError(ike);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -123,17 +156,16 @@ public class MasterSecretUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AsymmetricMasterSecret generateAsymmetricMasterSecret(Context context, MasterSecret masterSecret) {
|
public static AsymmetricMasterSecret generateAsymmetricMasterSecret(Context context,
|
||||||
|
MasterSecret masterSecret)
|
||||||
|
{
|
||||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||||
AsymmetricCipherKeyPair ackp = KeyUtil.generateKeyPair();
|
ECKeyPair keyPair = Curve.generateKeyPairForType(Curve.DJB_TYPE);
|
||||||
KeyPair keyPair = new KeyPair(31337, ackp, masterSecret);
|
|
||||||
PublicKey publicKey = keyPair.getPublicKey();
|
|
||||||
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters)ackp.getPrivate();
|
|
||||||
|
|
||||||
save(context, ASYMMETRIC_LOCAL_PUBLIC, publicKey.serialize());
|
save(context, ASYMMETRIC_LOCAL_PUBLIC_DJB, keyPair.getPublicKey().serialize());
|
||||||
save(context, "asymmetric_master_secret_private", masterCipher.encryptKey(privateKey));
|
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) {
|
public static MasterSecret generateMasterSecret(Context context, String passphrase) {
|
||||||
@ -154,7 +186,10 @@ public class MasterSecretUtil {
|
|||||||
|
|
||||||
public static boolean hasAsymmericMasterSecret(Context context) {
|
public static boolean hasAsymmericMasterSecret(Context context) {
|
||||||
SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0);
|
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) {
|
public static boolean isPassphraseInitialized(Context context) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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.InvalidVersionException;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
import org.whispersystems.textsecure.crypto.PublicKey;
|
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.crypto.protocol.CiphertextMessage;
|
||||||
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
||||||
import org.whispersystems.textsecure.util.Base64;
|
import org.whispersystems.textsecure.util.Base64;
|
||||||
@ -59,8 +62,6 @@ import java.io.IOException;
|
|||||||
|
|
||||||
public class KeyExchangeMessage {
|
public class KeyExchangeMessage {
|
||||||
|
|
||||||
private static final int SUPPORTED_VERSION = CiphertextMessage.SUPPORTED_VERSION;
|
|
||||||
|
|
||||||
private final int messageVersion;
|
private final int messageVersion;
|
||||||
private final int supportedVersion;
|
private final int supportedVersion;
|
||||||
private final PublicKey publicKey;
|
private final PublicKey publicKey;
|
||||||
@ -70,7 +71,7 @@ public class KeyExchangeMessage {
|
|||||||
public KeyExchangeMessage(Context context, MasterSecret masterSecret, int messageVersion, LocalKeyRecord record, int highIdBits) {
|
public KeyExchangeMessage(Context context, MasterSecret masterSecret, int messageVersion, LocalKeyRecord record, int highIdBits) {
|
||||||
this.publicKey = new PublicKey(record.getCurrentKeyPair().getPublicKey());
|
this.publicKey = new PublicKey(record.getCurrentKeyPair().getPublicKey());
|
||||||
this.messageVersion = messageVersion;
|
this.messageVersion = messageVersion;
|
||||||
this.supportedVersion = SUPPORTED_VERSION;
|
this.supportedVersion = CiphertextMessage.SUPPORTED_VERSION;
|
||||||
|
|
||||||
publicKey.setId(publicKey.getId() | (highIdBits << 12));
|
publicKey.setId(publicKey.getId() | (highIdBits << 12));
|
||||||
|
|
||||||
@ -80,7 +81,7 @@ public class KeyExchangeMessage {
|
|||||||
byte[] serializedBytes;
|
byte[] serializedBytes;
|
||||||
|
|
||||||
if (includeIdentityNoSignature(messageVersion, context)) {
|
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);
|
serializedBytes = Util.combine(versionBytes, publicKeyBytes, identityKey);
|
||||||
} else if (includeIdentitySignature(messageVersion, context)) {
|
} else if (includeIdentitySignature(messageVersion, context)) {
|
||||||
@ -102,9 +103,8 @@ public class KeyExchangeMessage {
|
|||||||
this.supportedVersion = Conversions.lowBitsToInt(keyBytes[0]);
|
this.supportedVersion = Conversions.lowBitsToInt(keyBytes[0]);
|
||||||
this.serialized = messageBody;
|
this.serialized = messageBody;
|
||||||
|
|
||||||
if (messageVersion > SUPPORTED_VERSION)
|
if (messageVersion > CiphertextMessage.SUPPORTED_VERSION)
|
||||||
throw new InvalidVersionException("Key exchange with version: " + messageVersion +
|
throw new InvalidVersionException("Key exchange with version: " + messageVersion);
|
||||||
" but we only support: " + SUPPORTED_VERSION);
|
|
||||||
|
|
||||||
if (messageVersion >= 1)
|
if (messageVersion >= 1)
|
||||||
keyBytes = Base64.decodeWithoutPadding(messageBody);
|
keyBytes = Base64.decodeWithoutPadding(messageBody);
|
||||||
@ -134,11 +134,11 @@ public class KeyExchangeMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean includeIdentitySignature(int messageVersion, Context context) {
|
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) {
|
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() {
|
public PublicKey getPublicKey() {
|
||||||
@ -153,6 +153,10 @@ public class KeyExchangeMessage {
|
|||||||
return supportedVersion;
|
return supportedVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMessageVersion() {
|
||||||
|
return messageVersion;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasIdentityKey() {
|
public boolean hasIdentityKey() {
|
||||||
return identityKey != null;
|
return identityKey != null;
|
||||||
}
|
}
|
||||||
|
@ -24,13 +24,14 @@ import android.database.sqlite.SQLiteOpenHelper;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
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.IdentityKey;
|
||||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||||
import org.whispersystems.textsecure.crypto.MasterCipher;
|
import org.whispersystems.textsecure.crypto.MasterCipher;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
|
||||||
import org.whispersystems.textsecure.util.Base64;
|
import org.whispersystems.textsecure.util.Base64;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -89,6 +90,13 @@ public class IdentityDatabase extends Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IdentityKey ourIdentity = new IdentityKey(Base64.decode(serializedIdentity), 0);
|
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);
|
return ourIdentity.equals(theirIdentity);
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
|
@ -19,6 +19,8 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|||||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
import org.whispersystems.textsecure.crypto.PreKeyUtil;
|
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.directory.Directory;
|
||||||
import org.whispersystems.textsecure.push.ContactTokenDetails;
|
import org.whispersystems.textsecure.push.ContactTokenDetails;
|
||||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||||
@ -273,7 +275,7 @@ public class RegistrationService extends Service {
|
|||||||
throws GcmRegistrationTimeoutException, IOException
|
throws GcmRegistrationTimeoutException, IOException
|
||||||
{
|
{
|
||||||
setState(new RegistrationState(RegistrationState.STATE_GENERATING_KEYS, number));
|
setState(new RegistrationState(RegistrationState.STATE_GENERATING_KEYS, number));
|
||||||
IdentityKey identityKey = IdentityKeyUtil.getIdentityKey(this);
|
IdentityKey identityKey = IdentityKeyUtil.getIdentityKey(this, Curve.DJB_TYPE);
|
||||||
List<PreKeyRecord> records = waitForPreKeys(masterSecret);
|
List<PreKeyRecord> records = waitForPreKeys(masterSecret);
|
||||||
PreKeyRecord lastResort = PreKeyUtil.generateLastResortKey(this, masterSecret);
|
PreKeyRecord lastResort = PreKeyUtil.generateLastResortKey(this, masterSecret);
|
||||||
socket.registerPreKeys(identityKey, lastResort, records);
|
socket.registerPreKeys(identityKey, lastResort, records);
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.thoughtcrime.securesms.transport;
|
package org.thoughtcrime.securesms.transport;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -16,6 +33,8 @@ import org.thoughtcrime.securesms.mms.MmsSendHelper;
|
|||||||
import org.thoughtcrime.securesms.mms.TextTransport;
|
import org.thoughtcrime.securesms.mms.TextTransport;
|
||||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
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.crypto.protocol.CiphertextMessage;
|
||||||
import org.whispersystems.textsecure.util.Hex;
|
import org.whispersystems.textsecure.util.Hex;
|
||||||
|
|
||||||
@ -138,7 +157,7 @@ public class MmsTransport {
|
|||||||
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes) {
|
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes) {
|
||||||
TextTransport transportDetails = new TextTransport();
|
TextTransport transportDetails = new TextTransport();
|
||||||
Recipient recipient = new Recipient(null, recipientString, null, null);
|
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);
|
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
|
||||||
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, pduBytes);
|
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, pduBytes);
|
||||||
|
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.thoughtcrime.securesms.transport;
|
package org.thoughtcrime.securesms.transport;
|
||||||
|
|
||||||
import android.content.Context;
|
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.KeyUtil;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
import org.whispersystems.textsecure.crypto.MessageCipher;
|
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.CiphertextMessage;
|
||||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
|
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
|
||||||
import org.whispersystems.textsecure.push.OutgoingPushMessage;
|
import org.whispersystems.textsecure.push.OutgoingPushMessage;
|
||||||
@ -162,7 +180,7 @@ public class PushTransport extends BaseTransport {
|
|||||||
private byte[] getEncryptedPrekeyBundleMessageForExistingSession(Recipient recipient,
|
private byte[] getEncryptedPrekeyBundleMessageForExistingSession(Recipient recipient,
|
||||||
byte[] plaintext)
|
byte[] plaintext)
|
||||||
{
|
{
|
||||||
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE);
|
||||||
IdentityKey identityKey = identityKeyPair.getPublicKey();
|
IdentityKey identityKey = identityKeyPair.getPublicKey();
|
||||||
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair);
|
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair);
|
||||||
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext);
|
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext);
|
||||||
@ -177,7 +195,7 @@ public class PushTransport extends BaseTransport {
|
|||||||
byte[] plaintext)
|
byte[] plaintext)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE);
|
||||||
IdentityKey identityKey = identityKeyPair.getPublicKey();
|
IdentityKey identityKey = identityKeyPair.getPublicKey();
|
||||||
PreKeyEntity preKey = socket.getPreKey(pushDestination);
|
PreKeyEntity preKey = socket.getPreKey(pushDestination);
|
||||||
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
|
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
|
||||||
@ -194,7 +212,7 @@ public class PushTransport extends BaseTransport {
|
|||||||
private byte[] getEncryptedMessageForExistingSession(Recipient recipient, byte[] plaintext)
|
private byte[] getEncryptedMessageForExistingSession(Recipient recipient, byte[] plaintext)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE);
|
||||||
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair);
|
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair);
|
||||||
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext);
|
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext);
|
||||||
|
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.thoughtcrime.securesms.transport;
|
package org.thoughtcrime.securesms.transport;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
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.KeyUtil;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
import org.whispersystems.textsecure.crypto.MessageCipher;
|
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.CiphertextMessage;
|
||||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
|
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
|
||||||
|
|
||||||
@ -116,7 +135,7 @@ public class SmsTransport extends BaseTransport {
|
|||||||
private ArrayList<PendingIntent> constructSentIntents(long messageId, long type, ArrayList<String> messages) {
|
private ArrayList<PendingIntent> constructSentIntents(long messageId, long type, ArrayList<String> messages) {
|
||||||
ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(messages.size());
|
ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(messages.size());
|
||||||
|
|
||||||
for (String message : messages) {
|
for (String ignored : messages) {
|
||||||
sentIntents.add(PendingIntent.getBroadcast(context, 0,
|
sentIntents.add(PendingIntent.getBroadcast(context, 0,
|
||||||
constructSentIntent(context, messageId, type),
|
constructSentIntent(context, messageId, type),
|
||||||
0));
|
0));
|
||||||
@ -132,7 +151,7 @@ public class SmsTransport extends BaseTransport {
|
|||||||
|
|
||||||
ArrayList<PendingIntent> deliveredIntents = new ArrayList<PendingIntent>(messages.size());
|
ArrayList<PendingIntent> deliveredIntents = new ArrayList<PendingIntent>(messages.size());
|
||||||
|
|
||||||
for (String message : messages) {
|
for (String ignored : messages) {
|
||||||
deliveredIntents.add(PendingIntent.getBroadcast(context, 0,
|
deliveredIntents.add(PendingIntent.getBroadcast(context, 0,
|
||||||
constructDeliveredIntent(context, messageId, type),
|
constructDeliveredIntent(context, messageId, type),
|
||||||
0));
|
0));
|
||||||
@ -146,7 +165,9 @@ public class SmsTransport extends BaseTransport {
|
|||||||
{
|
{
|
||||||
Recipient recipient = message.getRecipients().getPrimaryRecipient();
|
Recipient recipient = message.getRecipients().getPrimaryRecipient();
|
||||||
String body = message.getMessageBody();
|
String body = message.getMessageBody();
|
||||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret,
|
||||||
|
Curve.DJB_TYPE);
|
||||||
|
|
||||||
SmsTransportDetails transportDetails = new SmsTransportDetails();
|
SmsTransportDetails transportDetails = new SmsTransportDetails();
|
||||||
|
|
||||||
if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) {
|
if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user