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