mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-30 13:35:18 +00:00
Migrate prekeys into database
This commit is contained in:
parent
6239508b39
commit
9f3c04dfb5
@ -32,8 +32,6 @@ import android.widget.ProgressBar;
|
|||||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
|
|
||||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||||
@ -222,8 +220,8 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (params[0] < MIGRATE_SESSION_PLAINTEXT) {
|
if (params[0] < MIGRATE_SESSION_PLAINTEXT) {
|
||||||
new TextSecureSessionStore(context, masterSecret).migrateSessions();
|
// new TextSecureSessionStore(context, masterSecret).migrateSessions();
|
||||||
new TextSecurePreKeyStore(context, masterSecret).migrateRecords();
|
// new TextSecurePreKeyStore(context, masterSecret).migrateRecords();
|
||||||
|
|
||||||
IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
|
IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
|
||||||
scheduleMessagesInPushDatabase(context);;
|
scheduleMessagesInPushDatabase(context);;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (C) 2013 Open Whisper Systems
|
* Copyright (C) 2013-2018 Open Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -18,16 +18,11 @@
|
|||||||
package org.thoughtcrime.securesms.crypto;
|
package org.thoughtcrime.securesms.crypto;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
|
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
|
||||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||||
import org.whispersystems.libsignal.InvalidKeyException;
|
import org.whispersystems.libsignal.InvalidKeyException;
|
||||||
import org.whispersystems.libsignal.InvalidKeyIdException;
|
|
||||||
import org.whispersystems.libsignal.ecc.Curve;
|
import org.whispersystems.libsignal.ecc.Curve;
|
||||||
import org.whispersystems.libsignal.ecc.ECKeyPair;
|
import org.whispersystems.libsignal.ecc.ECKeyPair;
|
||||||
import org.whispersystems.libsignal.state.PreKeyRecord;
|
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||||
@ -35,26 +30,21 @@ import org.whispersystems.libsignal.state.PreKeyStore;
|
|||||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||||
import org.whispersystems.libsignal.state.SignedPreKeyStore;
|
import org.whispersystems.libsignal.state.SignedPreKeyStore;
|
||||||
import org.whispersystems.libsignal.util.Medium;
|
import org.whispersystems.libsignal.util.Medium;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class PreKeyUtil {
|
public class PreKeyUtil {
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = PreKeyUtil.class.getName();
|
private static final String TAG = PreKeyUtil.class.getName();
|
||||||
|
|
||||||
private static final int BATCH_SIZE = 100;
|
private static final int BATCH_SIZE = 100;
|
||||||
|
|
||||||
public static List<PreKeyRecord> generatePreKeys(Context context) {
|
public synchronized static List<PreKeyRecord> generatePreKeys(Context context) {
|
||||||
PreKeyStore preKeyStore = new TextSecurePreKeyStore(context);
|
PreKeyStore preKeyStore = new TextSecurePreKeyStore(context);
|
||||||
List<PreKeyRecord> records = new LinkedList<>();
|
List<PreKeyRecord> records = new LinkedList<>();
|
||||||
int preKeyIdOffset = getNextPreKeyId(context);
|
int preKeyIdOffset = TextSecurePreferences.getNextPreKeyId(context);
|
||||||
|
|
||||||
for (int i=0;i<BATCH_SIZE;i++) {
|
for (int i=0;i<BATCH_SIZE;i++) {
|
||||||
int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
|
int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
|
||||||
@ -65,24 +55,24 @@ public class PreKeyUtil {
|
|||||||
records.add(record);
|
records.add(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
setNextPreKeyId(context, (preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE);
|
TextSecurePreferences.setNextPreKeyId(context, (preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE);
|
||||||
|
|
||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SignedPreKeyRecord generateSignedPreKey(Context context, IdentityKeyPair identityKeyPair, boolean active)
|
public synchronized static SignedPreKeyRecord generateSignedPreKey(Context context, IdentityKeyPair identityKeyPair, boolean active) {
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context);
|
SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context);
|
||||||
int signedPreKeyId = getNextSignedPreKeyId(context);
|
int signedPreKeyId = TextSecurePreferences.getNextSignedPreKeyId(context);
|
||||||
ECKeyPair keyPair = Curve.generateKeyPair();
|
ECKeyPair keyPair = Curve.generateKeyPair();
|
||||||
byte[] signature = Curve.calculateSignature(identityKeyPair.getPrivateKey(), keyPair.getPublicKey().serialize());
|
byte[] signature = Curve.calculateSignature(identityKeyPair.getPrivateKey(), keyPair.getPublicKey().serialize());
|
||||||
SignedPreKeyRecord record = new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature);
|
SignedPreKeyRecord record = new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature);
|
||||||
|
|
||||||
signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
|
signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
|
||||||
setNextSignedPreKeyId(context, (signedPreKeyId + 1) % Medium.MAX_VALUE);
|
TextSecurePreferences.setNextSignedPreKeyId(context, (signedPreKeyId + 1) % Medium.MAX_VALUE);
|
||||||
|
|
||||||
if (active) {
|
if (active) {
|
||||||
setActiveSignedPreKeyId(context, signedPreKeyId);
|
TextSecurePreferences.setActiveSignedPreKeyId(context, signedPreKeyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return record;
|
return record;
|
||||||
@ -91,150 +81,12 @@ public class PreKeyUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static synchronized void setNextPreKeyId(Context context, int id) {
|
|
||||||
try {
|
|
||||||
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
|
|
||||||
FileOutputStream fout = new FileOutputStream(nextFile);
|
|
||||||
fout.write(JsonUtils.toJson(new PreKeyIndex(id)).getBytes());
|
|
||||||
fout.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w("PreKeyUtil", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static synchronized void setNextSignedPreKeyId(Context context, int id) {
|
|
||||||
try {
|
|
||||||
SignedPreKeyIndex index = getSignedPreKeyIndex(context).or(new SignedPreKeyIndex());
|
|
||||||
index.nextSignedPreKeyId = id;
|
|
||||||
|
|
||||||
setSignedPreKeyIndex(context, index);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static synchronized void setActiveSignedPreKeyId(Context context, int id) {
|
public static synchronized void setActiveSignedPreKeyId(Context context, int id) {
|
||||||
try {
|
TextSecurePreferences.setActiveSignedPreKeyId(context, id);
|
||||||
SignedPreKeyIndex index = getSignedPreKeyIndex(context).or(new SignedPreKeyIndex());
|
|
||||||
index.activeSignedPreKeyId = id;
|
|
||||||
|
|
||||||
setSignedPreKeyIndex(context, index);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized int getActiveSignedPreKeyId(Context context) {
|
public static synchronized int getActiveSignedPreKeyId(Context context) {
|
||||||
Optional<SignedPreKeyIndex> index = getSignedPreKeyIndex(context);
|
return TextSecurePreferences.getActiveSignedPreKeyId(context);
|
||||||
|
|
||||||
if (index.isPresent()) return index.get().activeSignedPreKeyId;
|
|
||||||
else return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static synchronized int getNextPreKeyId(Context context) {
|
|
||||||
try {
|
|
||||||
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
|
|
||||||
|
|
||||||
if (!nextFile.exists()) {
|
|
||||||
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
|
|
||||||
} else {
|
|
||||||
InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile));
|
|
||||||
PreKeyIndex index = JsonUtils.fromJson(reader, PreKeyIndex.class);
|
|
||||||
reader.close();
|
|
||||||
return index.nextPreKeyId;
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w("PreKeyUtil", e);
|
|
||||||
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static synchronized int getNextSignedPreKeyId(Context context) {
|
|
||||||
try {
|
|
||||||
File nextFile = new File(getSignedPreKeysDirectory(context), SignedPreKeyIndex.FILE_NAME);
|
|
||||||
|
|
||||||
if (!nextFile.exists()) {
|
|
||||||
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
|
|
||||||
} else {
|
|
||||||
InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile));
|
|
||||||
SignedPreKeyIndex index = JsonUtils.fromJson(reader, SignedPreKeyIndex.class);
|
|
||||||
reader.close();
|
|
||||||
return index.nextSignedPreKeyId;
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w("PreKeyUtil", e);
|
|
||||||
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static synchronized Optional<SignedPreKeyIndex> getSignedPreKeyIndex(Context context) {
|
|
||||||
File indexFile = new File(getSignedPreKeysDirectory(context), SignedPreKeyIndex.FILE_NAME);
|
|
||||||
|
|
||||||
if (!indexFile.exists()) {
|
|
||||||
return Optional.absent();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
InputStreamReader reader = new InputStreamReader(new FileInputStream(indexFile));
|
|
||||||
SignedPreKeyIndex index = JsonUtils.fromJson(reader, SignedPreKeyIndex.class);
|
|
||||||
reader.close();
|
|
||||||
|
|
||||||
return Optional.of(index);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
return Optional.absent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static synchronized void setSignedPreKeyIndex(Context context, SignedPreKeyIndex index) throws IOException {
|
|
||||||
File indexFile = new File(getSignedPreKeysDirectory(context), SignedPreKeyIndex.FILE_NAME);
|
|
||||||
FileOutputStream fout = new FileOutputStream(indexFile);
|
|
||||||
fout.write(JsonUtils.toJson(index).getBytes());
|
|
||||||
fout.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static File getPreKeysDirectory(Context context) {
|
|
||||||
return getKeysDirectory(context, TextSecurePreKeyStore.PREKEY_DIRECTORY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static File getSignedPreKeysDirectory(Context context) {
|
|
||||||
return getKeysDirectory(context, TextSecurePreKeyStore.SIGNED_PREKEY_DIRECTORY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static File getKeysDirectory(Context context, String name) {
|
|
||||||
File directory = new File(context.getFilesDir(), name);
|
|
||||||
|
|
||||||
if (!directory.exists())
|
|
||||||
directory.mkdirs();
|
|
||||||
|
|
||||||
return directory;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class PreKeyIndex {
|
|
||||||
public static final String FILE_NAME = "index.dat";
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private int nextPreKeyId;
|
|
||||||
|
|
||||||
public PreKeyIndex() {}
|
|
||||||
|
|
||||||
public PreKeyIndex(int nextPreKeyId) {
|
|
||||||
this.nextPreKeyId = nextPreKeyId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SignedPreKeyIndex {
|
|
||||||
public static final String FILE_NAME = "index.dat";
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private int nextSignedPreKeyId;
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private int activeSignedPreKeyId = -1;
|
|
||||||
|
|
||||||
public SignedPreKeyIndex() {}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,258 +2,89 @@ package org.thoughtcrime.securesms.crypto.storage;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|
||||||
import org.whispersystems.libsignal.InvalidKeyIdException;
|
import org.whispersystems.libsignal.InvalidKeyIdException;
|
||||||
import org.whispersystems.libsignal.InvalidMessageException;
|
|
||||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
|
||||||
import org.whispersystems.libsignal.state.SignedPreKeyStore;
|
|
||||||
import org.whispersystems.libsignal.state.PreKeyRecord;
|
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||||
import org.whispersystems.libsignal.state.PreKeyStore;
|
import org.whispersystems.libsignal.state.PreKeyStore;
|
||||||
import org.thoughtcrime.securesms.util.Conversions;
|
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||||
|
import org.whispersystems.libsignal.state.SignedPreKeyStore;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.RandomAccessFile;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.FileChannel;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore {
|
public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore {
|
||||||
|
|
||||||
public static final String PREKEY_DIRECTORY = "prekeys";
|
@SuppressWarnings("unused")
|
||||||
public static final String SIGNED_PREKEY_DIRECTORY = "signed_prekeys";
|
private static final String TAG = TextSecurePreKeyStore.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final Object FILE_LOCK = new Object();
|
||||||
|
|
||||||
private static final int PLAINTEXT_VERSION = 2;
|
@NonNull
|
||||||
private static final int CURRENT_VERSION_MARKER = 2;
|
private final Context context;
|
||||||
private static final Object FILE_LOCK = new Object();
|
|
||||||
private static final String TAG = TextSecurePreKeyStore.class.getSimpleName();
|
|
||||||
|
|
||||||
@NonNull private final Context context;
|
|
||||||
@Nullable private final MasterSecret masterSecret;
|
|
||||||
|
|
||||||
public TextSecurePreKeyStore(@NonNull Context context) {
|
public TextSecurePreKeyStore(@NonNull Context context) {
|
||||||
this(context, null);
|
this.context = context;
|
||||||
}
|
|
||||||
|
|
||||||
public TextSecurePreKeyStore(@NonNull Context context, @Nullable MasterSecret masterSecret) {
|
|
||||||
this.context = context;
|
|
||||||
this.masterSecret = masterSecret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
|
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
|
||||||
synchronized (FILE_LOCK) {
|
synchronized (FILE_LOCK) {
|
||||||
try {
|
PreKeyRecord preKeyRecord = DatabaseFactory.getPreKeyDatabase(context).getPreKey(preKeyId);
|
||||||
return new PreKeyRecord(loadSerializedRecord(getPreKeyFile(preKeyId)));
|
|
||||||
} catch (IOException | InvalidMessageException e) {
|
if (preKeyRecord == null) throw new InvalidKeyIdException("No such key: " + preKeyId);
|
||||||
Log.w(TAG, e);
|
else return preKeyRecord;
|
||||||
throw new InvalidKeyIdException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
|
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
|
||||||
synchronized (FILE_LOCK) {
|
synchronized (FILE_LOCK) {
|
||||||
try {
|
SignedPreKeyRecord signedPreKeyRecord = DatabaseFactory.getSignedPreKeyDatabase(context).getSignedPreKey(signedPreKeyId);
|
||||||
return new SignedPreKeyRecord(loadSerializedRecord(getSignedPreKeyFile(signedPreKeyId)));
|
|
||||||
} catch (IOException | InvalidMessageException e) {
|
if (signedPreKeyRecord == null) throw new InvalidKeyIdException("No such signed prekey: " + signedPreKeyId);
|
||||||
Log.w(TAG, e);
|
else return signedPreKeyRecord;
|
||||||
throw new InvalidKeyIdException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<SignedPreKeyRecord> loadSignedPreKeys() {
|
public List<SignedPreKeyRecord> loadSignedPreKeys() {
|
||||||
synchronized (FILE_LOCK) {
|
synchronized (FILE_LOCK) {
|
||||||
File directory = getSignedPreKeyDirectory();
|
return DatabaseFactory.getSignedPreKeyDatabase(context).getAllSignedPreKeys();
|
||||||
List<SignedPreKeyRecord> results = new LinkedList<>();
|
|
||||||
|
|
||||||
for (File signedPreKeyFile : directory.listFiles()) {
|
|
||||||
try {
|
|
||||||
if (!"index.dat".equals(signedPreKeyFile.getName())) {
|
|
||||||
results.add(new SignedPreKeyRecord(loadSerializedRecord(signedPreKeyFile)));
|
|
||||||
}
|
|
||||||
} catch (IOException | InvalidMessageException e) {
|
|
||||||
Log.w(TAG, signedPreKeyFile.getAbsolutePath(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void storePreKey(int preKeyId, PreKeyRecord record) {
|
public void storePreKey(int preKeyId, PreKeyRecord record) {
|
||||||
synchronized (FILE_LOCK) {
|
synchronized (FILE_LOCK) {
|
||||||
try {
|
DatabaseFactory.getPreKeyDatabase(context).insertPreKey(preKeyId, record);
|
||||||
storeSerializedRecord(getPreKeyFile(preKeyId), record.serialize());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
|
public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
|
||||||
synchronized (FILE_LOCK) {
|
synchronized (FILE_LOCK) {
|
||||||
try {
|
DatabaseFactory.getSignedPreKeyDatabase(context).insertSignedPreKey(signedPreKeyId, record);
|
||||||
storeSerializedRecord(getSignedPreKeyFile(signedPreKeyId), record.serialize());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsPreKey(int preKeyId) {
|
public boolean containsPreKey(int preKeyId) {
|
||||||
File record = getPreKeyFile(preKeyId);
|
return DatabaseFactory.getPreKeyDatabase(context).getPreKey(preKeyId) != null;
|
||||||
return record.exists();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsSignedPreKey(int signedPreKeyId) {
|
public boolean containsSignedPreKey(int signedPreKeyId) {
|
||||||
File record = getSignedPreKeyFile(signedPreKeyId);
|
return DatabaseFactory.getSignedPreKeyDatabase(context).getSignedPreKey(signedPreKeyId) != null;
|
||||||
return record.exists();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removePreKey(int preKeyId) {
|
public void removePreKey(int preKeyId) {
|
||||||
File record = getPreKeyFile(preKeyId);
|
DatabaseFactory.getPreKeyDatabase(context).removePreKey(preKeyId);
|
||||||
record.delete();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeSignedPreKey(int signedPreKeyId) {
|
public void removeSignedPreKey(int signedPreKeyId) {
|
||||||
File record = getSignedPreKeyFile(signedPreKeyId);
|
DatabaseFactory.getSignedPreKeyDatabase(context).removeSignedPreKey(signedPreKeyId);
|
||||||
record.delete();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void migrateRecords() {
|
|
||||||
synchronized (FILE_LOCK) {
|
|
||||||
File preKeyRecords = getPreKeyDirectory();
|
|
||||||
|
|
||||||
for (File preKeyRecord : preKeyRecords.listFiles()) {
|
|
||||||
try {
|
|
||||||
int preKeyId = Integer.parseInt(preKeyRecord.getName());
|
|
||||||
PreKeyRecord record = loadPreKey(preKeyId);
|
|
||||||
|
|
||||||
storePreKey(preKeyId, record);
|
|
||||||
} catch (InvalidKeyIdException | NumberFormatException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
File signedPreKeyRecords = getSignedPreKeyDirectory();
|
|
||||||
|
|
||||||
for (File signedPreKeyRecord : signedPreKeyRecords.listFiles()) {
|
|
||||||
try {
|
|
||||||
int signedPreKeyId = Integer.parseInt(signedPreKeyRecord.getName());
|
|
||||||
SignedPreKeyRecord record = loadSignedPreKey(signedPreKeyId);
|
|
||||||
|
|
||||||
storeSignedPreKey(signedPreKeyId, record);
|
|
||||||
} catch (InvalidKeyIdException | NumberFormatException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] loadSerializedRecord(File recordFile)
|
|
||||||
throws IOException, InvalidMessageException
|
|
||||||
{
|
|
||||||
FileInputStream fin = new FileInputStream(recordFile);
|
|
||||||
int recordVersion = readInteger(fin);
|
|
||||||
|
|
||||||
if (recordVersion > CURRENT_VERSION_MARKER) {
|
|
||||||
throw new AssertionError("Invalid version: " + recordVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] serializedRecord = readBlob(fin);
|
|
||||||
|
|
||||||
if (recordVersion < PLAINTEXT_VERSION && masterSecret != null) {
|
|
||||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
|
||||||
serializedRecord = masterCipher.decryptBytes(serializedRecord);
|
|
||||||
} else if (recordVersion < PLAINTEXT_VERSION) {
|
|
||||||
throw new AssertionError("Migration didn't happen! " + recordFile.getAbsolutePath() + ", " + recordVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
fin.close();
|
|
||||||
return serializedRecord;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void storeSerializedRecord(File file, byte[] serialized) throws IOException {
|
|
||||||
RandomAccessFile recordFile = new RandomAccessFile(file, "rw");
|
|
||||||
FileChannel out = recordFile.getChannel();
|
|
||||||
|
|
||||||
out.position(0);
|
|
||||||
writeInteger(CURRENT_VERSION_MARKER, out);
|
|
||||||
writeBlob(serialized, out);
|
|
||||||
out.truncate(out.position());
|
|
||||||
recordFile.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getPreKeyFile(int preKeyId) {
|
|
||||||
return new File(getPreKeyDirectory(), String.valueOf(preKeyId));
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getSignedPreKeyFile(int signedPreKeyId) {
|
|
||||||
return new File(getSignedPreKeyDirectory(), String.valueOf(signedPreKeyId));
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getPreKeyDirectory() {
|
|
||||||
return getRecordsDirectory(PREKEY_DIRECTORY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getSignedPreKeyDirectory() {
|
|
||||||
return getRecordsDirectory(SIGNED_PREKEY_DIRECTORY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getRecordsDirectory(String directoryName) {
|
|
||||||
File directory = new File(context.getFilesDir(), directoryName);
|
|
||||||
|
|
||||||
if (!directory.exists()) {
|
|
||||||
if (!directory.mkdirs()) {
|
|
||||||
Log.w(TAG, "PreKey directory creation failed!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return directory;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] readBlob(FileInputStream in) throws IOException {
|
|
||||||
int length = readInteger(in);
|
|
||||||
byte[] blobBytes = new byte[length];
|
|
||||||
|
|
||||||
in.read(blobBytes, 0, blobBytes.length);
|
|
||||||
return blobBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeBlob(byte[] blobBytes, FileChannel out) throws IOException {
|
|
||||||
writeInteger(blobBytes.length, out);
|
|
||||||
out.write(ByteBuffer.wrap(blobBytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
private int readInteger(FileInputStream in) throws IOException {
|
|
||||||
byte[] integer = new byte[4];
|
|
||||||
in.read(integer, 0, integer.length);
|
|
||||||
return Conversions.byteArrayToInt(integer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeInteger(int value, FileChannel out) throws IOException {
|
|
||||||
byte[] valueBytes = Conversions.intToByteArray(value);
|
|
||||||
out.write(ByteBuffer.wrap(valueBytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -39,20 +39,22 @@ public class DatabaseFactory {
|
|||||||
|
|
||||||
private static DatabaseFactory instance;
|
private static DatabaseFactory instance;
|
||||||
|
|
||||||
private final SQLCipherOpenHelper databaseHelper;
|
private final SQLCipherOpenHelper databaseHelper;
|
||||||
private final SmsDatabase sms;
|
private final SmsDatabase sms;
|
||||||
private final MmsDatabase mms;
|
private final MmsDatabase mms;
|
||||||
private final AttachmentDatabase attachments;
|
private final AttachmentDatabase attachments;
|
||||||
private final MediaDatabase media;
|
private final MediaDatabase media;
|
||||||
private final ThreadDatabase thread;
|
private final ThreadDatabase thread;
|
||||||
private final MmsSmsDatabase mmsSmsDatabase;
|
private final MmsSmsDatabase mmsSmsDatabase;
|
||||||
private final IdentityDatabase identityDatabase;
|
private final IdentityDatabase identityDatabase;
|
||||||
private final DraftDatabase draftDatabase;
|
private final DraftDatabase draftDatabase;
|
||||||
private final PushDatabase pushDatabase;
|
private final PushDatabase pushDatabase;
|
||||||
private final GroupDatabase groupDatabase;
|
private final GroupDatabase groupDatabase;
|
||||||
private final RecipientDatabase recipientDatabase;
|
private final RecipientDatabase recipientDatabase;
|
||||||
private final ContactsDatabase contactsDatabase;
|
private final ContactsDatabase contactsDatabase;
|
||||||
private final GroupReceiptDatabase groupReceiptDatabase;
|
private final GroupReceiptDatabase groupReceiptDatabase;
|
||||||
|
private final OneTimePreKeyDatabase preKeyDatabase;
|
||||||
|
private final SignedPreKeyDatabase signedPreKeyDatabase;
|
||||||
|
|
||||||
public static DatabaseFactory getInstance(Context context) {
|
public static DatabaseFactory getInstance(Context context) {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
@ -115,6 +117,14 @@ public class DatabaseFactory {
|
|||||||
return getInstance(context).groupReceiptDatabase;
|
return getInstance(context).groupReceiptDatabase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static OneTimePreKeyDatabase getPreKeyDatabase(Context context) {
|
||||||
|
return getInstance(context).preKeyDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignedPreKeyDatabase getSignedPreKeyDatabase(Context context) {
|
||||||
|
return getInstance(context).signedPreKeyDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
private DatabaseFactory(@NonNull Context context) {
|
private DatabaseFactory(@NonNull Context context) {
|
||||||
SQLiteDatabase.loadLibs(context);
|
SQLiteDatabase.loadLibs(context);
|
||||||
|
|
||||||
@ -135,6 +145,8 @@ public class DatabaseFactory {
|
|||||||
this.recipientDatabase = new RecipientDatabase(context, databaseHelper);
|
this.recipientDatabase = new RecipientDatabase(context, databaseHelper);
|
||||||
this.groupReceiptDatabase = new GroupReceiptDatabase(context, databaseHelper);
|
this.groupReceiptDatabase = new GroupReceiptDatabase(context, databaseHelper);
|
||||||
this.contactsDatabase = new ContactsDatabase(context);
|
this.contactsDatabase = new ContactsDatabase(context);
|
||||||
|
this.preKeyDatabase = new OneTimePreKeyDatabase(context, databaseHelper);
|
||||||
|
this.signedPreKeyDatabase = new SignedPreKeyDatabase(context, databaseHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onApplicationLevelUpgrade(@NonNull Context context, @NonNull MasterSecret masterSecret,
|
public void onApplicationLevelUpgrade(@NonNull Context context, @NonNull MasterSecret masterSecret,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2011 Whisper Systems
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -35,6 +35,7 @@ import java.io.IOException;
|
|||||||
|
|
||||||
public class IdentityDatabase extends Database {
|
public class IdentityDatabase extends Database {
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = IdentityDatabase.class.getSimpleName();
|
private static final String TAG = IdentityDatabase.class.getSimpleName();
|
||||||
|
|
||||||
private static final String TABLE_NAME = "identities";
|
private static final String TABLE_NAME = "identities";
|
||||||
@ -218,7 +219,7 @@ public class IdentityDatabase extends Database {
|
|||||||
public class IdentityReader {
|
public class IdentityReader {
|
||||||
private final Cursor cursor;
|
private final Cursor cursor;
|
||||||
|
|
||||||
public IdentityReader(@NonNull Cursor cursor) {
|
IdentityReader(@NonNull Cursor cursor) {
|
||||||
this.cursor = cursor;
|
this.cursor = cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
package org.thoughtcrime.securesms.database;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import net.sqlcipher.database.SQLiteDatabase;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||||
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
|
import org.whispersystems.libsignal.InvalidKeyException;
|
||||||
|
import org.whispersystems.libsignal.ecc.Curve;
|
||||||
|
import org.whispersystems.libsignal.ecc.ECKeyPair;
|
||||||
|
import org.whispersystems.libsignal.ecc.ECPrivateKey;
|
||||||
|
import org.whispersystems.libsignal.ecc.ECPublicKey;
|
||||||
|
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class OneTimePreKeyDatabase extends Database {
|
||||||
|
|
||||||
|
private static final String TAG = OneTimePreKeyDatabase.class.getSimpleName();
|
||||||
|
|
||||||
|
public static final String TABLE_NAME = "one_time_prekeys";
|
||||||
|
private static final String ID = "_id";
|
||||||
|
public static final String KEY_ID = "key_id";
|
||||||
|
public static final String PUBLIC_KEY = "public_key";
|
||||||
|
public static final String PRIVATE_KEY = "private_key";
|
||||||
|
|
||||||
|
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
|
||||||
|
" (" + ID + " INTEGER PRIMARY KEY, " +
|
||||||
|
KEY_ID + " INTEGER UNIQUE, " +
|
||||||
|
PUBLIC_KEY + " TEXT NOT NULL, " +
|
||||||
|
PRIVATE_KEY + " TEXT NOT NULL);";
|
||||||
|
|
||||||
|
OneTimePreKeyDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||||
|
super(context, databaseHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable PreKeyRecord getPreKey(int keyId) {
|
||||||
|
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||||
|
|
||||||
|
try (Cursor cursor = database.query(TABLE_NAME, null, KEY_ID + " = ?",
|
||||||
|
new String[] {String.valueOf(keyId)},
|
||||||
|
null, null, null))
|
||||||
|
{
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
try {
|
||||||
|
ECPublicKey publicKey = Curve.decodePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PUBLIC_KEY))), 0);
|
||||||
|
ECPrivateKey privateKey = Curve.decodePrivatePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PRIVATE_KEY))));
|
||||||
|
|
||||||
|
return new PreKeyRecord(keyId, new ECKeyPair(publicKey, privateKey));
|
||||||
|
} catch (InvalidKeyException | IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void insertPreKey(int keyId, PreKeyRecord record) {
|
||||||
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||||
|
|
||||||
|
ContentValues contentValues = new ContentValues();
|
||||||
|
contentValues.put(KEY_ID, keyId);
|
||||||
|
contentValues.put(PUBLIC_KEY, Base64.encodeBytes(record.getKeyPair().getPublicKey().serialize()));
|
||||||
|
contentValues.put(PRIVATE_KEY, Base64.encodeBytes(record.getKeyPair().getPrivateKey().serialize()));
|
||||||
|
|
||||||
|
database.insert(TABLE_NAME, null, contentValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removePreKey(int keyId) {
|
||||||
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||||
|
database.delete(TABLE_NAME, KEY_ID + " = ?", new String[] {String.valueOf(keyId)});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
package org.thoughtcrime.securesms.database;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import net.sqlcipher.database.SQLiteDatabase;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||||
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
|
import org.whispersystems.libsignal.InvalidKeyException;
|
||||||
|
import org.whispersystems.libsignal.ecc.Curve;
|
||||||
|
import org.whispersystems.libsignal.ecc.ECKeyPair;
|
||||||
|
import org.whispersystems.libsignal.ecc.ECPrivateKey;
|
||||||
|
import org.whispersystems.libsignal.ecc.ECPublicKey;
|
||||||
|
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SignedPreKeyDatabase extends Database {
|
||||||
|
|
||||||
|
private static final String TAG = SignedPreKeyDatabase.class.getSimpleName();
|
||||||
|
|
||||||
|
public static final String TABLE_NAME = "signed_prekeys";
|
||||||
|
|
||||||
|
private static final String ID = "_id";
|
||||||
|
public static final String KEY_ID = "key_id";
|
||||||
|
public static final String PUBLIC_KEY = "public_key";
|
||||||
|
public static final String PRIVATE_KEY = "private_key";
|
||||||
|
public static final String SIGNATURE = "signature";
|
||||||
|
public static final String TIMESTAMP = "timestamp";
|
||||||
|
|
||||||
|
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
|
||||||
|
" (" + ID + " INTEGER PRIMARY KEY, " +
|
||||||
|
KEY_ID + " INTEGER UNIQUE, " +
|
||||||
|
PUBLIC_KEY + " TEXT NOT NULL, " +
|
||||||
|
PRIVATE_KEY + " TEXT NOT NULL, " +
|
||||||
|
SIGNATURE + " TEXT NOT NULL, " +
|
||||||
|
TIMESTAMP + " INTEGER DEFAULT 0);";
|
||||||
|
|
||||||
|
public SignedPreKeyDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||||
|
super(context, databaseHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable SignedPreKeyRecord getSignedPreKey(int keyId) {
|
||||||
|
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||||
|
|
||||||
|
try (Cursor cursor = database.query(TABLE_NAME, null, KEY_ID + " = ?",
|
||||||
|
new String[] {String.valueOf(keyId)},
|
||||||
|
null, null, null))
|
||||||
|
{
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
try {
|
||||||
|
ECPublicKey publicKey = Curve.decodePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PUBLIC_KEY))), 0);
|
||||||
|
ECPrivateKey privateKey = Curve.decodePrivatePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PRIVATE_KEY))));
|
||||||
|
byte[] signature = Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(SIGNATURE)));
|
||||||
|
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP));
|
||||||
|
|
||||||
|
return new SignedPreKeyRecord(keyId, timestamp, new ECKeyPair(publicKey, privateKey), signature);
|
||||||
|
} catch (InvalidKeyException | IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull List<SignedPreKeyRecord> getAllSignedPreKeys() {
|
||||||
|
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||||
|
List<SignedPreKeyRecord> results = new LinkedList<>();
|
||||||
|
|
||||||
|
try (Cursor cursor = database.query(TABLE_NAME, null, null, null, null, null, null)) {
|
||||||
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
|
try {
|
||||||
|
int keyId = cursor.getInt(cursor.getColumnIndexOrThrow(KEY_ID));
|
||||||
|
ECPublicKey publicKey = Curve.decodePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PUBLIC_KEY))), 0);
|
||||||
|
ECPrivateKey privateKey = Curve.decodePrivatePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PRIVATE_KEY))));
|
||||||
|
byte[] signature = Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(SIGNATURE)));
|
||||||
|
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP));
|
||||||
|
|
||||||
|
results.add(new SignedPreKeyRecord(keyId, timestamp, new ECKeyPair(publicKey, privateKey), signature));
|
||||||
|
} catch (InvalidKeyException | IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertSignedPreKey(int keyId, SignedPreKeyRecord record) {
|
||||||
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||||
|
|
||||||
|
ContentValues contentValues = new ContentValues();
|
||||||
|
contentValues.put(KEY_ID, keyId);
|
||||||
|
contentValues.put(PUBLIC_KEY, Base64.encodeBytes(record.getKeyPair().getPublicKey().serialize()));
|
||||||
|
contentValues.put(PRIVATE_KEY, Base64.encodeBytes(record.getKeyPair().getPrivateKey().serialize()));
|
||||||
|
contentValues.put(SIGNATURE, Base64.encodeBytes(record.getSignature()));
|
||||||
|
contentValues.put(TIMESTAMP, record.getTimestamp());
|
||||||
|
|
||||||
|
database.insert(TABLE_NAME, null, contentValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void removeSignedPreKey(int keyId) {
|
||||||
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||||
|
database.delete(TABLE_NAME, KEY_ID + " = ? AND " + SIGNATURE + " IS NOT NULL", new String[] {String.valueOf(keyId)});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,225 @@
|
|||||||
|
package org.thoughtcrime.securesms.database.helpers;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import net.sqlcipher.database.SQLiteDatabase;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
|
||||||
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
|
import org.thoughtcrime.securesms.util.Conversions;
|
||||||
|
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.whispersystems.libsignal.InvalidMessageException;
|
||||||
|
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||||
|
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
class PreKeyMigrationHelper {
|
||||||
|
|
||||||
|
private static final String PREKEY_DIRECTORY = "prekeys";
|
||||||
|
private static final String SIGNED_PREKEY_DIRECTORY = "signed_prekeys";
|
||||||
|
|
||||||
|
private static final int PLAINTEXT_VERSION = 2;
|
||||||
|
private static final int CURRENT_VERSION_MARKER = 2;
|
||||||
|
|
||||||
|
private static final String TAG = PreKeyMigrationHelper.class.getSimpleName();
|
||||||
|
|
||||||
|
static boolean migratePreKeys(Context context, SQLiteDatabase database) {
|
||||||
|
File[] preKeyFiles = getPreKeyDirectory(context).listFiles();
|
||||||
|
boolean clean = true;
|
||||||
|
|
||||||
|
if (preKeyFiles != null) {
|
||||||
|
for (File preKeyFile : preKeyFiles) {
|
||||||
|
if (!"index.dat".equals(preKeyFile.getName())) {
|
||||||
|
try {
|
||||||
|
PreKeyRecord preKey = new PreKeyRecord(loadSerializedRecord(preKeyFile));
|
||||||
|
|
||||||
|
ContentValues contentValues = new ContentValues();
|
||||||
|
contentValues.put(OneTimePreKeyDatabase.KEY_ID, preKey.getId());
|
||||||
|
contentValues.put(OneTimePreKeyDatabase.PUBLIC_KEY, Base64.encodeBytes(preKey.getKeyPair().getPublicKey().serialize()));
|
||||||
|
contentValues.put(OneTimePreKeyDatabase.PRIVATE_KEY, Base64.encodeBytes(preKey.getKeyPair().getPrivateKey().serialize()));
|
||||||
|
database.insert(OneTimePreKeyDatabase.TABLE_NAME, null, contentValues);
|
||||||
|
Log.w(TAG, "Migrated one-time prekey: " + preKey.getId());
|
||||||
|
} catch (IOException | InvalidMessageException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
clean = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File[] signedPreKeyFiles = getSignedPreKeyDirectory(context).listFiles();
|
||||||
|
|
||||||
|
if (signedPreKeyFiles != null) {
|
||||||
|
for (File signedPreKeyFile : signedPreKeyFiles) {
|
||||||
|
if (!"index.dat".equals(signedPreKeyFile.getName())) {
|
||||||
|
try {
|
||||||
|
SignedPreKeyRecord signedPreKey = new SignedPreKeyRecord(loadSerializedRecord(signedPreKeyFile));
|
||||||
|
|
||||||
|
ContentValues contentValues = new ContentValues();
|
||||||
|
contentValues.put(SignedPreKeyDatabase.KEY_ID, signedPreKey.getId());
|
||||||
|
contentValues.put(SignedPreKeyDatabase.PUBLIC_KEY, Base64.encodeBytes(signedPreKey.getKeyPair().getPublicKey().serialize()));
|
||||||
|
contentValues.put(SignedPreKeyDatabase.PRIVATE_KEY, Base64.encodeBytes(signedPreKey.getKeyPair().getPrivateKey().serialize()));
|
||||||
|
contentValues.put(SignedPreKeyDatabase.SIGNATURE, Base64.encodeBytes(signedPreKey.getSignature()));
|
||||||
|
contentValues.put(SignedPreKeyDatabase.TIMESTAMP, signedPreKey.getTimestamp());
|
||||||
|
database.insert(SignedPreKeyDatabase.TABLE_NAME, null, contentValues);
|
||||||
|
Log.w(TAG, "Migrated signed prekey: " + signedPreKey.getId());
|
||||||
|
} catch (IOException | InvalidMessageException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
clean = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File oneTimePreKeyIndex = new File(getPreKeyDirectory(context), PreKeyIndex.FILE_NAME);
|
||||||
|
File signedPreKeyIndex = new File(getSignedPreKeyDirectory(context), SignedPreKeyIndex.FILE_NAME);
|
||||||
|
|
||||||
|
if (oneTimePreKeyIndex.exists()) {
|
||||||
|
try {
|
||||||
|
InputStreamReader reader = new InputStreamReader(new FileInputStream(oneTimePreKeyIndex));
|
||||||
|
PreKeyIndex index = JsonUtils.fromJson(reader, PreKeyIndex.class);
|
||||||
|
reader.close();
|
||||||
|
|
||||||
|
Log.w(TAG, "Setting next prekey id: " + index.nextPreKeyId);
|
||||||
|
TextSecurePreferences.setNextPreKeyId(context, index.nextPreKeyId);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signedPreKeyIndex.exists()) {
|
||||||
|
try {
|
||||||
|
InputStreamReader reader = new InputStreamReader(new FileInputStream(signedPreKeyIndex));
|
||||||
|
SignedPreKeyIndex index = JsonUtils.fromJson(reader, SignedPreKeyIndex.class);
|
||||||
|
reader.close();
|
||||||
|
|
||||||
|
Log.w(TAG, "Setting next signed prekey id: " + index.nextSignedPreKeyId);
|
||||||
|
Log.w(TAG, "Setting active signed prekey id: " + index.activeSignedPreKeyId);
|
||||||
|
TextSecurePreferences.setNextSignedPreKeyId(context, index.nextSignedPreKeyId);
|
||||||
|
TextSecurePreferences.setActiveSignedPreKeyId(context, index.activeSignedPreKeyId);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clean;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cleanUpPreKeys(@NonNull Context context) {
|
||||||
|
File preKeyDirectory = getPreKeyDirectory(context);
|
||||||
|
File[] preKeyFiles = preKeyDirectory.listFiles();
|
||||||
|
|
||||||
|
if (preKeyFiles != null) {
|
||||||
|
for (File preKeyFile : preKeyFiles) {
|
||||||
|
Log.w(TAG, "Deleting: " + preKeyFile.getAbsolutePath());
|
||||||
|
preKeyFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.w(TAG, "Deleting: " + preKeyDirectory.getAbsolutePath());
|
||||||
|
preKeyDirectory.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
File signedPreKeyDirectory = getSignedPreKeyDirectory(context);
|
||||||
|
File[] signedPreKeyFiles = signedPreKeyDirectory.listFiles();
|
||||||
|
|
||||||
|
if (signedPreKeyFiles != null) {
|
||||||
|
for (File signedPreKeyFile : signedPreKeyFiles) {
|
||||||
|
Log.w(TAG, "Deleting: " + signedPreKeyFile.getAbsolutePath());
|
||||||
|
signedPreKeyFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.w(TAG, "Deleting: " + signedPreKeyDirectory.getAbsolutePath());
|
||||||
|
signedPreKeyDirectory.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] loadSerializedRecord(File recordFile)
|
||||||
|
throws IOException, InvalidMessageException
|
||||||
|
{
|
||||||
|
FileInputStream fin = new FileInputStream(recordFile);
|
||||||
|
int recordVersion = readInteger(fin);
|
||||||
|
|
||||||
|
if (recordVersion > CURRENT_VERSION_MARKER) {
|
||||||
|
throw new IOException("Invalid version: " + recordVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] serializedRecord = readBlob(fin);
|
||||||
|
|
||||||
|
if (recordVersion < PLAINTEXT_VERSION) {
|
||||||
|
throw new IOException("Migration didn't happen! " + recordFile.getAbsolutePath() + ", " + recordVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
fin.close();
|
||||||
|
return serializedRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File getPreKeyDirectory(Context context) {
|
||||||
|
return getRecordsDirectory(context, PREKEY_DIRECTORY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File getSignedPreKeyDirectory(Context context) {
|
||||||
|
return getRecordsDirectory(context, SIGNED_PREKEY_DIRECTORY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File getRecordsDirectory(Context context, String directoryName) {
|
||||||
|
File directory = new File(context.getFilesDir(), directoryName);
|
||||||
|
|
||||||
|
if (!directory.exists()) {
|
||||||
|
if (!directory.mkdirs()) {
|
||||||
|
Log.w(TAG, "PreKey directory creation failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] readBlob(FileInputStream in) throws IOException {
|
||||||
|
int length = readInteger(in);
|
||||||
|
byte[] blobBytes = new byte[length];
|
||||||
|
|
||||||
|
in.read(blobBytes, 0, blobBytes.length);
|
||||||
|
return blobBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int readInteger(FileInputStream in) throws IOException {
|
||||||
|
byte[] integer = new byte[4];
|
||||||
|
in.read(integer, 0, integer.length);
|
||||||
|
return Conversions.byteArrayToInt(integer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PreKeyIndex {
|
||||||
|
static final String FILE_NAME = "index.dat";
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private int nextPreKeyId;
|
||||||
|
|
||||||
|
public PreKeyIndex() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SignedPreKeyIndex {
|
||||||
|
static final String FILE_NAME = "index.dat";
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private int nextSignedPreKeyId;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private int activeSignedPreKeyId = -1;
|
||||||
|
|
||||||
|
public SignedPreKeyIndex() {}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -9,6 +9,7 @@ import net.sqlcipher.database.SQLiteDatabase;
|
|||||||
import net.sqlcipher.database.SQLiteDatabaseHook;
|
import net.sqlcipher.database.SQLiteDatabaseHook;
|
||||||
import net.sqlcipher.database.SQLiteOpenHelper;
|
import net.sqlcipher.database.SQLiteOpenHelper;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
@ -17,10 +18,13 @@ import org.thoughtcrime.securesms.database.GroupDatabase;
|
|||||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
|
||||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||||
|
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
|
||||||
@ -29,9 +33,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = SQLCipherOpenHelper.class.getSimpleName();
|
private static final String TAG = SQLCipherOpenHelper.class.getSimpleName();
|
||||||
|
|
||||||
private static final int RECIPIENT_CALL_RINGTONE_VERSION = 2;
|
private static final int RECIPIENT_CALL_RINGTONE_VERSION = 2;
|
||||||
|
private static final int MIGRATE_PREKEYS_VERSION = 3;
|
||||||
|
|
||||||
private static final int DATABASE_VERSION = 2;
|
private static final int DATABASE_VERSION = 3;
|
||||||
private static final String DATABASE_NAME = "signal.db";
|
private static final String DATABASE_NAME = "signal.db";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
@ -68,6 +73,8 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
db.execSQL(GroupDatabase.CREATE_TABLE);
|
db.execSQL(GroupDatabase.CREATE_TABLE);
|
||||||
db.execSQL(RecipientDatabase.CREATE_TABLE);
|
db.execSQL(RecipientDatabase.CREATE_TABLE);
|
||||||
db.execSQL(GroupReceiptDatabase.CREATE_TABLE);
|
db.execSQL(GroupReceiptDatabase.CREATE_TABLE);
|
||||||
|
db.execSQL(OneTimePreKeyDatabase.CREATE_TABLE);
|
||||||
|
db.execSQL(SignedPreKeyDatabase.CREATE_TABLE);
|
||||||
|
|
||||||
executeStatements(db, SmsDatabase.CREATE_INDEXS);
|
executeStatements(db, SmsDatabase.CREATE_INDEXS);
|
||||||
executeStatements(db, MmsDatabase.CREATE_INDEXS);
|
executeStatements(db, MmsDatabase.CREATE_INDEXS);
|
||||||
@ -87,6 +94,12 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
|
|
||||||
if (masterSecret != null) SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret, legacyDb, db, null);
|
if (masterSecret != null) SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret, legacyDb, db, null);
|
||||||
else TextSecurePreferences.setNeedsSqlCipherMigration(context, true);
|
else TextSecurePreferences.setNeedsSqlCipherMigration(context, true);
|
||||||
|
|
||||||
|
if (!PreKeyMigrationHelper.migratePreKeys(context, db)) {
|
||||||
|
ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
PreKeyMigrationHelper.cleanUpPreKeys(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,9 +107,33 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
Log.w(TAG, "Upgrading database: " + oldVersion + ", " + newVersion);
|
Log.w(TAG, "Upgrading database: " + oldVersion + ", " + newVersion);
|
||||||
|
|
||||||
if (oldVersion < RECIPIENT_CALL_RINGTONE_VERSION) {
|
db.beginTransaction();
|
||||||
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN call_ringtone TEXT DEFAULT NULL");
|
|
||||||
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN call_vibrate INTEGER DEFAULT " + RecipientDatabase.VibrateState.DEFAULT.getId());
|
try {
|
||||||
|
|
||||||
|
if (oldVersion < RECIPIENT_CALL_RINGTONE_VERSION) {
|
||||||
|
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN call_ringtone TEXT DEFAULT NULL");
|
||||||
|
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN call_vibrate INTEGER DEFAULT " + RecipientDatabase.VibrateState.DEFAULT.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldVersion < MIGRATE_PREKEYS_VERSION) {
|
||||||
|
db.execSQL("CREATE TABLE signed_prekeys (_id INTEGER PRIMARY KEY, key_id INTEGER UNIQUE, public_key TEXT NOT NULL, private_key TEXT NOT NULL, signature TEXT NOT NULL, timestamp INTEGER DEFAULT 0)");
|
||||||
|
db.execSQL("CREATE TABLE one_time_prekeys (_id INTEGER PRIMARY KEY, key_id INTEGER UNIQUE, public_key TEXT NOT NULL, private_key TEXT NOT NULL)");
|
||||||
|
|
||||||
|
if (!PreKeyMigrationHelper.migratePreKeys(context, db)) {
|
||||||
|
ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
PreKeyMigrationHelper.cleanUpPreKeys(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
} finally {
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldVersion < MIGRATE_PREKEYS_VERSION) {
|
||||||
|
PreKeyMigrationHelper.cleanUpPreKeys(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,8 +18,10 @@ import org.greenrobot.eventbus.EventBus;
|
|||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.jobs.requirements.SqlCipherMigrationRequirementProvider;
|
import org.thoughtcrime.securesms.jobs.requirements.SqlCipherMigrationRequirementProvider;
|
||||||
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
|
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
|
||||||
|
import org.whispersystems.libsignal.util.Medium;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -131,6 +133,34 @@ public class TextSecurePreferences {
|
|||||||
public static final String CALL_RINGTONE_PREF = "pref_call_ringtone";
|
public static final String CALL_RINGTONE_PREF = "pref_call_ringtone";
|
||||||
public static final String CALL_VIBRATE_PREF = "pref_call_vibrate";
|
public static final String CALL_VIBRATE_PREF = "pref_call_vibrate";
|
||||||
|
|
||||||
|
private static final String NEXT_PRE_KEY_ID = "pref_next_pre_key_id";
|
||||||
|
private static final String ACTIVE_SIGNED_PRE_KEY_ID = "pref_active_signed_pre_key_id";
|
||||||
|
private static final String NEXT_SIGNED_PRE_KEY_ID = "pref_next_signed_pre_key_id";
|
||||||
|
|
||||||
|
public static int getNextPreKeyId(@NonNull Context context) {
|
||||||
|
return getIntegerPreference(context, NEXT_PRE_KEY_ID, new SecureRandom().nextInt(Medium.MAX_VALUE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setNextPreKeyId(@NonNull Context context, int value) {
|
||||||
|
setIntegerPrefrence(context, NEXT_PRE_KEY_ID, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getNextSignedPreKeyId(@NonNull Context context) {
|
||||||
|
return getIntegerPreference(context, NEXT_SIGNED_PRE_KEY_ID, new SecureRandom().nextInt(Medium.MAX_VALUE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setNextSignedPreKeyId(@NonNull Context context, int value) {
|
||||||
|
setIntegerPrefrence(context, NEXT_SIGNED_PRE_KEY_ID, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getActiveSignedPreKeyId(@NonNull Context context) {
|
||||||
|
return getIntegerPreference(context, ACTIVE_SIGNED_PRE_KEY_ID, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setActiveSignedPreKeyId(@NonNull Context context, int value) {
|
||||||
|
setIntegerPrefrence(context, ACTIVE_SIGNED_PRE_KEY_ID, value);;
|
||||||
|
}
|
||||||
|
|
||||||
public static void setNeedsSqlCipherMigration(@NonNull Context context, boolean value) {
|
public static void setNeedsSqlCipherMigration(@NonNull Context context, boolean value) {
|
||||||
setBooleanPreference(context, NEEDS_SQLCIPHER_MIGRATION, value);
|
setBooleanPreference(context, NEEDS_SQLCIPHER_MIGRATION, value);
|
||||||
EventBus.getDefault().post(new SqlCipherMigrationRequirementProvider.SqlCipherNeedsMigrationEvent());
|
EventBus.getDefault().post(new SqlCipherMigrationRequirementProvider.SqlCipherNeedsMigrationEvent());
|
||||||
|
Loading…
Reference in New Issue
Block a user