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
Moxie Marlinspike 2013-11-10 04:15:29 -08:00
rodzic a03fff8b24
commit c38a8aa699
57 zmienionych plików z 2197 dodań i 498 usunięć

2
.gitignore vendored
Wyświetl plik

@ -20,3 +20,5 @@ signing.properties
gradle
gradlew
gradlew.bat
library/lib/
library/obj/

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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)

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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.

Wyświetl plik

@ -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() {

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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());
}
}

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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 {
}
}
}
}

Wyświetl plik

@ -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() {

Wyświetl plik

@ -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();
}
}

Wyświetl plik

@ -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();

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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());
}
}
}

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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());
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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);

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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];

Wyświetl plik

@ -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;

Wyświetl plik

@ -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() {

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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);

Wyświetl plik

@ -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;
}
}
}

Wyświetl plik

@ -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);

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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());

Wyświetl plik

@ -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());

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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;

Wyświetl plik

@ -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;

Wyświetl plik

@ -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);

Wyświetl plik

@ -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);

Wyświetl plik

@ -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);

Wyświetl plik

@ -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)) {