Generate "prekeys" at push registration time.

This generates a large number of key exchange messages and
registers them with the server during signup.
This commit is contained in:
Moxie Marlinspike
2013-08-15 08:25:30 -07:00
parent cfb7b8fcba
commit 2042ca6cb7
25 changed files with 1015 additions and 109 deletions

View File

@@ -19,22 +19,18 @@ package org.thoughtcrime.securesms.database.keys;
public class InvalidKeyIdException extends Exception {
public InvalidKeyIdException() {
// TODO Auto-generated constructor stub
}
public InvalidKeyIdException(String detailMessage) {
super(detailMessage);
// TODO Auto-generated constructor stub
}
public InvalidKeyIdException(Throwable throwable) {
super(throwable);
// TODO Auto-generated constructor stub
}
public InvalidKeyIdException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
// TODO Auto-generated constructor stub
}
}

View File

@@ -25,8 +25,6 @@ import org.thoughtcrime.securesms.crypto.KeyUtil;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.CanonicalAddressDatabase;
import org.thoughtcrime.securesms.database.keys.InvalidKeyIdException;
import org.thoughtcrime.securesms.database.keys.Record;
import org.thoughtcrime.securesms.recipients.Recipient;
import java.io.FileInputStream;
@@ -46,7 +44,7 @@ public class LocalKeyRecord extends Record {
private final MasterSecret masterSecret;
public LocalKeyRecord(Context context, MasterSecret masterSecret, Recipient recipient) {
super(context, getFileNameForRecipient(context, recipient));
super(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
this.masterSecret = masterSecret;
this.masterCipher = new MasterCipher(masterSecret);
loadData();
@@ -54,11 +52,11 @@ public class LocalKeyRecord extends Record {
public static boolean hasRecord(Context context, Recipient recipient) {
Log.w("LocalKeyRecord", "Checking: " + getFileNameForRecipient(context, recipient));
return Record.hasRecord(context, getFileNameForRecipient(context, recipient));
return Record.hasRecord(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
}
public static void delete(Context context, Recipient recipient) {
Record.delete(context, getFileNameForRecipient(context, recipient));
Record.delete(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
}
private static String getFileNameForRecipient(Context context, Recipient recipient) {
@@ -121,12 +119,11 @@ public class LocalKeyRecord extends Record {
synchronized (FILE_LOCK) {
try {
FileInputStream in = this.openInputStream();
localCurrentKeyPair = readKeyPair(in);
localNextKeyPair = readKeyPair(in);
localCurrentKeyPair = readKeyPair(in, masterCipher);
localNextKeyPair = readKeyPair(in, masterCipher);
in.close();
} catch (FileNotFoundException e) {
Log.w("LocalKeyRecord", "No local keypair set found.");
return;
} catch (IOException ioe) {
Log.w("keyrecord", ioe);
// XXX
@@ -141,8 +138,11 @@ public class LocalKeyRecord extends Record {
writeBlob(keyPairBytes, out);
}
private KeyPair readKeyPair(FileInputStream in) throws IOException, InvalidKeyException {
private KeyPair readKeyPair(FileInputStream in, MasterCipher masterCipher)
throws IOException, InvalidKeyException
{
byte[] keyPairBytes = readBlob(in);
return new KeyPair(keyPairBytes, masterCipher);
}
}

View File

@@ -0,0 +1,125 @@
package org.thoughtcrime.securesms.database.keys;
import android.content.Context;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.InvalidKeyException;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.PreKeyPair;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
public class PreKeyRecord extends Record {
private static final Object FILE_LOCK = new Object();
private static final int CURRENT_VERSION_MARKER = 1;
private final MasterCipher masterCipher;
private final MasterSecret masterSecret;
private PreKeyPair keyPair;
private long id;
public PreKeyRecord(Context context, MasterSecret masterSecret, long id)
throws InvalidKeyIdException
{
super(context, PREKEY_DIRECTORY, id+"");
this.id = id;
this.masterSecret = masterSecret;
this.masterCipher = new MasterCipher(masterSecret);
loadData();
}
public PreKeyRecord(Context context, MasterSecret masterSecret,
long id, PreKeyPair keyPair)
{
super(context, PREKEY_DIRECTORY, id+"");
this.id = id;
this.keyPair = keyPair;
this.masterSecret = masterSecret;
this.masterCipher = new MasterCipher(masterSecret);
}
public long getId() {
return id;
}
public PreKeyPair getKeyPair() {
return keyPair;
}
public static boolean hasRecord(Context context, long id) {
Log.w("PreKeyRecord", "Checking: " + id);
return Record.hasRecord(context, PREKEY_DIRECTORY, id+"");
}
public static void delete(Context context, long id) {
Record.delete(context, PREKEY_DIRECTORY, id+"");
}
public void save() {
synchronized (FILE_LOCK) {
try {
RandomAccessFile file = openRandomAccessFile();
FileChannel out = file.getChannel();
out.position(0);
writeInteger(CURRENT_VERSION_MARKER, out);
writeKeyPair(keyPair, out);
out.force(true);
out.truncate(out.position());
out.close();
file.close();
} catch (IOException ioe) {
Log.w("PreKeyRecord", ioe);
}
}
}
private void loadData() throws InvalidKeyIdException {
synchronized (FILE_LOCK) {
try {
FileInputStream in = this.openInputStream();
int recordVersion = readInteger(in);
if (recordVersion != CURRENT_VERSION_MARKER) {
Log.w("PreKeyRecord", "Invalid version: " + recordVersion);
return;
}
keyPair = readKeyPair(in, masterCipher);
in.close();
} catch (FileNotFoundException e) {
Log.w("PreKeyRecord", e);
throw new InvalidKeyIdException(e);
} catch (IOException ioe) {
Log.w("PreKeyRecord", ioe);
throw new InvalidKeyIdException(ioe);
} catch (InvalidKeyException ike) {
Log.w("LocalKeyRecord", ike);
throw new InvalidKeyIdException(ike);
}
}
}
private void writeKeyPair(PreKeyPair keyPair, FileChannel out) throws IOException {
byte[] serialized = keyPair.serialize();
writeBlob(serialized, out);
}
private PreKeyPair readKeyPair(FileInputStream in, MasterCipher masterCipher)
throws IOException, InvalidKeyException
{
byte[] keyPairBytes = readBlob(in);
return new PreKeyPair(masterSecret, keyPairBytes);
}
}

View File

@@ -18,6 +18,9 @@ package org.thoughtcrime.securesms.database.keys;
import android.content.Context;
import org.thoughtcrime.securesms.crypto.InvalidKeyException;
import org.thoughtcrime.securesms.crypto.KeyPair;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.whispersystems.textsecure.util.Conversions;
import java.io.File;
@@ -30,24 +33,29 @@ import java.nio.channels.FileChannel;
public abstract class Record {
protected static final String SESSIONS_DIRECTORY = "sessions";
public static final String PREKEY_DIRECTORY = "prekeys";
protected final String address;
protected final String directory;
protected final Context context;
public Record(Context context, String address) {
this.context = context;
this.address = address;
public Record(Context context, String directory, String address) {
this.context = context;
this.directory = directory;
this.address = address;
}
public void delete() {
delete(this.context, this.address);
delete(this.context, this.directory, this.address);
}
protected static void delete(Context context, String address) {
getAddressFile(context, address).delete();
protected static void delete(Context context, String directory, String address) {
getAddressFile(context, directory, address).delete();
}
protected static boolean hasRecord(Context context, String address) {
return getAddressFile(context, address).exists();
protected static boolean hasRecord(Context context, String directory, String address) {
return getAddressFile(context, directory, address).exists();
}
protected RandomAccessFile openRandomAccessFile() throws FileNotFoundException {
@@ -59,11 +67,11 @@ public abstract class Record {
}
private File getAddressFile() {
return getAddressFile(context, address);
return getAddressFile(context, directory, address);
}
private static File getAddressFile(Context context, String address) {
return new File(context.getFilesDir().getAbsolutePath() + File.separatorChar + "sessions", address);
private static File getAddressFile(Context context, String directory, String address) {
return new File(context.getFilesDir().getAbsolutePath() + File.separatorChar + directory, address);
}
protected byte[] readBlob(FileInputStream in) throws IOException {

View File

@@ -47,17 +47,17 @@ public class RemoteKeyRecord extends Record {
private PublicKey remoteKeyLast;
public RemoteKeyRecord(Context context, Recipient recipient) {
super(context,getFileNameForRecipient(context, recipient));
super(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
loadData();
}
public static void delete(Context context, Recipient recipient) {
Record.delete(context, getFileNameForRecipient(context, recipient));
Record.delete(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
}
public static boolean hasRecord(Context context, Recipient recipient) {
Log.w("LocalKeyRecord", "Checking: " + getFileNameForRecipient(context, recipient));
return Record.hasRecord(context, getFileNameForRecipient(context, recipient));
return Record.hasRecord(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
}
private static String getFileNameForRecipient(Context context, Recipient recipient) {
@@ -126,7 +126,6 @@ public class RemoteKeyRecord extends Record {
in.close();
} catch (FileNotFoundException e) {
Log.w("RemoteKeyRecord", "No remote keys found.");
return;
} catch (IOException ioe) {
Log.w("keyrecord", ioe);
// XXX

View File

@@ -58,19 +58,19 @@ public class SessionRecord extends Record {
}
public SessionRecord(Context context, MasterSecret masterSecret, long recipientId) {
super(context, recipientId+"");
super(context, SESSIONS_DIRECTORY, recipientId+"");
this.masterSecret = masterSecret;
this.sessionVersion = 31337;
loadData();
}
public static void delete(Context context, Recipient recipient) {
Record.delete(context, getRecipientId(context, recipient)+"");
Record.delete(context, SESSIONS_DIRECTORY, getRecipientId(context, recipient)+"");
}
public static boolean hasSession(Context context, Recipient recipient) {
Log.w("LocalKeyRecord", "Checking: " + getRecipientId(context, recipient));
return Record.hasRecord(context, getRecipientId(context, recipient)+"");
return Record.hasRecord(context, SESSIONS_DIRECTORY, getRecipientId(context, recipient)+"");
}
private static long getRecipientId(Context context, Recipient recipient) {