mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
WIP: clean up signal protocol storage
This commit is contained in:
parent
1e1b3e02e1
commit
d8104c0d5c
@ -120,10 +120,7 @@ object FullBackupExporter {
|
||||
}
|
||||
|
||||
private inline fun shouldExportTable(table: String): Boolean {
|
||||
return table != SignedPreKeyDatabase.TABLE_NAME &&
|
||||
table != OneTimePreKeyDatabase.TABLE_NAME &&
|
||||
table != SessionDatabase.TABLE_NAME &&
|
||||
table != PushDatabase.TABLE_NAME &&
|
||||
return table != PushDatabase.TABLE_NAME &&
|
||||
|
||||
table != LokiBackupFilesDatabase.TABLE_NAME &&
|
||||
table != LokiAPIDatabase.openGroupProfilePictureTable &&
|
||||
|
@ -1,30 +0,0 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.session.libsignal.libsignal.SignalProtocolAddress;
|
||||
import org.session.libsignal.libsignal.state.SessionStore;
|
||||
import org.session.libsignal.service.api.push.SignalServiceAddress;
|
||||
|
||||
public class SessionUtil {
|
||||
|
||||
public static boolean hasSession(Context context, @NonNull Address address) {
|
||||
SessionStore sessionStore = new TextSecureSessionStore(context);
|
||||
SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(address.serialize(), SignalServiceAddress.DEFAULT_DEVICE_ID);
|
||||
|
||||
return sessionStore.containsSession(axolotlAddress);
|
||||
}
|
||||
|
||||
public static void archiveSiblingSessions(Context context, SignalProtocolAddress address) {
|
||||
TextSecureSessionStore sessionStore = new TextSecureSessionStore(context);
|
||||
sessionStore.archiveSiblingSessions(address);
|
||||
}
|
||||
|
||||
public static void archiveAllSessions(Context context) {
|
||||
new TextSecureSessionStore(context).archiveAllSessions();
|
||||
}
|
||||
|
||||
}
|
@ -2,150 +2,20 @@ package org.thoughtcrime.securesms.crypto.storage;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.session.libsignal.libsignal.IdentityKey;
|
||||
import org.session.libsignal.libsignal.IdentityKeyPair;
|
||||
import org.session.libsignal.libsignal.InvalidKeyIdException;
|
||||
import org.session.libsignal.libsignal.SignalProtocolAddress;
|
||||
import org.session.libsignal.libsignal.state.IdentityKeyStore;
|
||||
import org.session.libsignal.libsignal.state.PreKeyRecord;
|
||||
import org.session.libsignal.libsignal.state.PreKeyStore;
|
||||
import org.session.libsignal.libsignal.state.SessionRecord;
|
||||
import org.session.libsignal.libsignal.state.SessionStore;
|
||||
import org.session.libsignal.libsignal.state.SignalProtocolStore;
|
||||
import org.session.libsignal.libsignal.state.SignedPreKeyRecord;
|
||||
import org.session.libsignal.libsignal.state.SignedPreKeyStore;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SignalProtocolStoreImpl implements SignalProtocolStore {
|
||||
public class SignalProtocolStoreImpl implements IdentityKeyStore {
|
||||
|
||||
private final Context context;
|
||||
// private final PreKeyStore preKeyStore;
|
||||
// private final SignedPreKeyStore signedPreKeyStore;
|
||||
// private final IdentityKeyStore identityKeyStore;
|
||||
private final SessionStore sessionStore;
|
||||
|
||||
public SignalProtocolStoreImpl(Context context) {
|
||||
// this.preKeyStore = new TextSecurePreKeyStore(context);
|
||||
// this.signedPreKeyStore = new TextSecurePreKeyStore(context);
|
||||
// this.identityKeyStore = new TextSecureIdentityKeyStore(context);
|
||||
this.sessionStore = new TextSecureSessionStore(context);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKeyPair getIdentityKeyPair() {
|
||||
return IdentityKeyUtil.getIdentityKeyPair(context);
|
||||
// return identityKeyStore.getIdentityKeyPair();
|
||||
// throw new UnsupportedOperationException("This method will be removed with refactor.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocalRegistrationId() {
|
||||
// return identityKeyStore.getLocalRegistrationId();
|
||||
throw new UnsupportedOperationException("This method will be removed with refactor.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
|
||||
// return identityKeyStore.saveIdentity(address, identityKey);
|
||||
throw new UnsupportedOperationException("This method will be removed with refactor.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
|
||||
// return identityKeyStore.isTrustedIdentity(address, identityKey, direction);
|
||||
throw new UnsupportedOperationException("This method will be removed with refactor.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey getIdentity(SignalProtocolAddress address) {
|
||||
// return identityKeyStore.getIdentity(address);
|
||||
throw new UnsupportedOperationException("This method will be removed with refactor.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
|
||||
// return preKeyStore.loadPreKey(preKeyId);
|
||||
throw new UnsupportedOperationException("This method will be removed with refactor.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePreKey(int preKeyId, PreKeyRecord record) {
|
||||
// preKeyStore.storePreKey(preKeyId, record);
|
||||
throw new UnsupportedOperationException("This method will be removed with refactor.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsPreKey(int preKeyId) {
|
||||
// return preKeyStore.containsPreKey(preKeyId);
|
||||
throw new UnsupportedOperationException("This method will be removed with refactor.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePreKey(int preKeyId) {
|
||||
// preKeyStore.removePreKey(preKeyId);
|
||||
throw new UnsupportedOperationException("This method will be removed with refactor.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionRecord loadSession(SignalProtocolAddress axolotlAddress) {
|
||||
return sessionStore.loadSession(axolotlAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> getSubDeviceSessions(String number) {
|
||||
return sessionStore.getSubDeviceSessions(number);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeSession(SignalProtocolAddress axolotlAddress, SessionRecord record) {
|
||||
sessionStore.storeSession(axolotlAddress, record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSession(SignalProtocolAddress axolotlAddress) {
|
||||
return sessionStore.containsSession(axolotlAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSession(SignalProtocolAddress axolotlAddress) {
|
||||
sessionStore.deleteSession(axolotlAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAllSessions(String number) {
|
||||
sessionStore.deleteAllSessions(number);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
|
||||
throw new UnsupportedOperationException("This method will be removed with refactor.");
|
||||
// return signedPreKeyStore.loadSignedPreKey(signedPreKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SignedPreKeyRecord> loadSignedPreKeys() {
|
||||
throw new UnsupportedOperationException("This method will be removed with refactor.");
|
||||
// return signedPreKeyStore.loadSignedPreKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
|
||||
throw new UnsupportedOperationException("This method will be removed with refactor.");
|
||||
// signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSignedPreKey(int signedPreKeyId) {
|
||||
throw new UnsupportedOperationException("This method will be removed with refactor.");
|
||||
// return signedPreKeyStore.containsSignedPreKey(signedPreKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSignedPreKey(int signedPreKeyId) {
|
||||
throw new UnsupportedOperationException("This method will be removed with refactor.");
|
||||
// signedPreKeyStore.removeSignedPreKey(signedPreKeyId);
|
||||
}
|
||||
}
|
||||
|
@ -1,110 +0,0 @@
|
||||
package org.thoughtcrime.securesms.crypto.storage;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.SessionDatabase;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
import org.session.libsignal.libsignal.SignalProtocolAddress;
|
||||
import org.session.libsignal.libsignal.protocol.CiphertextMessage;
|
||||
import org.session.libsignal.libsignal.state.SessionRecord;
|
||||
import org.session.libsignal.libsignal.state.SessionStore;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TextSecureSessionStore implements SessionStore {
|
||||
|
||||
private static final String TAG = TextSecureSessionStore.class.getSimpleName();
|
||||
|
||||
private static final Object FILE_LOCK = new Object();
|
||||
|
||||
@NonNull private final Context context;
|
||||
|
||||
public TextSecureSessionStore(@NonNull Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionRecord loadSession(@NonNull SignalProtocolAddress address) {
|
||||
synchronized (FILE_LOCK) {
|
||||
SessionRecord sessionRecord = DatabaseFactory.getSessionDatabase(context).load(Address.fromSerialized(address.getName()), address.getDeviceId());
|
||||
|
||||
if (sessionRecord == null) {
|
||||
Log.w(TAG, "No existing session information found.");
|
||||
return new SessionRecord();
|
||||
}
|
||||
|
||||
return sessionRecord;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeSession(@NonNull SignalProtocolAddress address, @NonNull SessionRecord record) {
|
||||
synchronized (FILE_LOCK) {
|
||||
DatabaseFactory.getSessionDatabase(context).store(Address.fromSerialized(address.getName()), address.getDeviceId(), record);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSession(SignalProtocolAddress address) {
|
||||
synchronized (FILE_LOCK) {
|
||||
SessionRecord sessionRecord = DatabaseFactory.getSessionDatabase(context).load(Address.fromSerialized(address.getName()), address.getDeviceId());
|
||||
|
||||
return sessionRecord != null &&
|
||||
sessionRecord.getSessionState().hasSenderChain() &&
|
||||
sessionRecord.getSessionState().getSessionVersion() == CiphertextMessage.CURRENT_VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSession(SignalProtocolAddress address) {
|
||||
synchronized (FILE_LOCK) {
|
||||
DatabaseFactory.getSessionDatabase(context).delete(Address.fromSerialized(address.getName()), address.getDeviceId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAllSessions(String name) {
|
||||
synchronized (FILE_LOCK) {
|
||||
DatabaseFactory.getSessionDatabase(context).deleteAllFor(Address.fromSerialized(name));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> getSubDeviceSessions(String name) {
|
||||
synchronized (FILE_LOCK) {
|
||||
return DatabaseFactory.getSessionDatabase(context).getSubDevices(Address.fromSerialized(name));
|
||||
}
|
||||
}
|
||||
|
||||
public void archiveSiblingSessions(@NonNull SignalProtocolAddress address) {
|
||||
synchronized (FILE_LOCK) {
|
||||
List<SessionDatabase.SessionRow> sessions = DatabaseFactory.getSessionDatabase(context).getAllFor(Address.fromSerialized(address.getName()));
|
||||
|
||||
for (SessionDatabase.SessionRow row : sessions) {
|
||||
if (row.getDeviceId() != address.getDeviceId()) {
|
||||
row.getRecord().archiveCurrentState();
|
||||
storeSession(new SignalProtocolAddress(row.getAddress().serialize(), row.getDeviceId()), row.getRecord());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void archiveAllSessions(@NonNull String hexEncodedPublicKey) {
|
||||
SignalProtocolAddress address = new SignalProtocolAddress(hexEncodedPublicKey, -1);
|
||||
archiveSiblingSessions(address);
|
||||
}
|
||||
|
||||
public void archiveAllSessions() {
|
||||
synchronized (FILE_LOCK) {
|
||||
List<SessionDatabase.SessionRow> sessions = DatabaseFactory.getSessionDatabase(context).getAll();
|
||||
|
||||
for (SessionDatabase.SessionRow row : sessions) {
|
||||
row.getRecord().archiveCurrentState();
|
||||
storeSession(new SignalProtocolAddress(row.getAddress().serialize(), row.getDeviceId()), row.getRecord());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -53,9 +53,6 @@ public class DatabaseFactory {
|
||||
private final GroupDatabase groupDatabase;
|
||||
private final RecipientDatabase recipientDatabase;
|
||||
private final GroupReceiptDatabase groupReceiptDatabase;
|
||||
private final OneTimePreKeyDatabase preKeyDatabase;
|
||||
private final SignedPreKeyDatabase signedPreKeyDatabase;
|
||||
private final SessionDatabase sessionDatabase;
|
||||
private final SearchDatabase searchDatabase;
|
||||
private final JobDatabase jobDatabase;
|
||||
private final StickerDatabase stickerDatabase;
|
||||
@ -125,18 +122,6 @@ public class DatabaseFactory {
|
||||
return getInstance(context).groupReceiptDatabase;
|
||||
}
|
||||
|
||||
public static OneTimePreKeyDatabase getPreKeyDatabase(Context context) {
|
||||
return getInstance(context).preKeyDatabase;
|
||||
}
|
||||
|
||||
public static SignedPreKeyDatabase getSignedPreKeyDatabase(Context context) {
|
||||
return getInstance(context).signedPreKeyDatabase;
|
||||
}
|
||||
|
||||
public static SessionDatabase getSessionDatabase(Context context) {
|
||||
return getInstance(context).sessionDatabase;
|
||||
}
|
||||
|
||||
public static SearchDatabase getSearchDatabase(Context context) {
|
||||
return getInstance(context).searchDatabase;
|
||||
}
|
||||
@ -212,9 +197,6 @@ public class DatabaseFactory {
|
||||
this.groupDatabase = new GroupDatabase(context, databaseHelper);
|
||||
this.recipientDatabase = new RecipientDatabase(context, databaseHelper);
|
||||
this.groupReceiptDatabase = new GroupReceiptDatabase(context, databaseHelper);
|
||||
this.preKeyDatabase = new OneTimePreKeyDatabase(context, databaseHelper);
|
||||
this.signedPreKeyDatabase = new SignedPreKeyDatabase(context, databaseHelper);
|
||||
this.sessionDatabase = new SessionDatabase(context, databaseHelper);
|
||||
this.searchDatabase = new SearchDatabase(context, databaseHelper);
|
||||
this.jobDatabase = new JobDatabase(context, databaseHelper);
|
||||
this.stickerDatabase = new StickerDatabase(context, databaseHelper, attachmentSecret);
|
||||
|
@ -1,81 +0,0 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import androidx.annotation.Nullable;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.session.libsignal.utilities.Base64;
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.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.replace(TABLE_NAME, null, contentValues);
|
||||
}
|
||||
|
||||
public void removePreKey(int keyId) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
database.delete(TABLE_NAME, KEY_ID + " = ?", new String[] {String.valueOf(keyId)});
|
||||
}
|
||||
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
|
||||
import org.session.libsignal.libsignal.state.SessionRecord;
|
||||
import org.session.libsignal.service.api.push.SignalServiceAddress;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class SessionDatabase extends Database {
|
||||
|
||||
private static final String TAG = SessionDatabase.class.getSimpleName();
|
||||
|
||||
public static final String TABLE_NAME = "sessions";
|
||||
|
||||
private static final String ID = "_id";
|
||||
public static final String ADDRESS = "address";
|
||||
public static final String DEVICE = "device";
|
||||
public static final String RECORD = "record";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
|
||||
"(" + ID + " INTEGER PRIMARY KEY, " + ADDRESS + " TEXT NOT NULL, " +
|
||||
DEVICE + " INTEGER NOT NULL, " + RECORD + " BLOB NOT NULL, " +
|
||||
"UNIQUE(" + ADDRESS + "," + DEVICE + ") ON CONFLICT REPLACE);";
|
||||
|
||||
SessionDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public void store(@NonNull Address address, int deviceId, @NonNull SessionRecord record) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(ADDRESS, address.serialize());
|
||||
values.put(DEVICE, deviceId);
|
||||
values.put(RECORD, record.serialize());
|
||||
|
||||
database.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
}
|
||||
|
||||
public @Nullable SessionRecord load(@NonNull Address address, int deviceId) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
|
||||
try (Cursor cursor = database.query(TABLE_NAME, new String[]{RECORD},
|
||||
ADDRESS + " = ? AND " + DEVICE + " = ?",
|
||||
new String[] {address.serialize(), String.valueOf(deviceId)},
|
||||
null, null, null))
|
||||
{
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
try {
|
||||
return new SessionRecord(cursor.getBlob(cursor.getColumnIndexOrThrow(RECORD)));
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public @NonNull List<SessionRow> getAllFor(@NonNull Address address) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
List<SessionRow> results = new LinkedList<>();
|
||||
|
||||
try (Cursor cursor = database.query(TABLE_NAME, null,
|
||||
ADDRESS + " = ?",
|
||||
new String[] {address.serialize()},
|
||||
null, null, null))
|
||||
{
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
try {
|
||||
results.add(new SessionRow(address,
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE)),
|
||||
new SessionRecord(cursor.getBlob(cursor.getColumnIndexOrThrow(RECORD)))));
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public @NonNull List<SessionRow> getAll() {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
List<SessionRow> results = new LinkedList<>();
|
||||
|
||||
try (Cursor cursor = database.query(TABLE_NAME, null, null, null, null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
try {
|
||||
results.add(new SessionRow(Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE)),
|
||||
new SessionRecord(cursor.getBlob(cursor.getColumnIndexOrThrow(RECORD)))));
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public @NonNull List<Integer> getSubDevices(@NonNull Address address) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
List<Integer> results = new LinkedList<>();
|
||||
|
||||
try (Cursor cursor = database.query(TABLE_NAME, new String[] {DEVICE},
|
||||
ADDRESS + " = ?",
|
||||
new String[] {address.serialize()},
|
||||
null, null, null))
|
||||
{
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
int device = cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE));
|
||||
|
||||
if (device != SignalServiceAddress.DEFAULT_DEVICE_ID) {
|
||||
results.add(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public void delete(@NonNull Address address, int deviceId) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
|
||||
database.delete(TABLE_NAME, ADDRESS + " = ? AND " + DEVICE + " = ?",
|
||||
new String[] {address.serialize(), String.valueOf(deviceId)});
|
||||
}
|
||||
|
||||
public void deleteAllFor(@NonNull Address address) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
database.delete(TABLE_NAME, ADDRESS + " = ?", new String[] {address.serialize()});
|
||||
}
|
||||
|
||||
public static final class SessionRow {
|
||||
private final Address address;
|
||||
private final int deviceId;
|
||||
private final SessionRecord record;
|
||||
|
||||
public SessionRow(Address address, int deviceId, SessionRecord record) {
|
||||
this.address = address;
|
||||
this.deviceId = deviceId;
|
||||
this.record = record;
|
||||
}
|
||||
|
||||
public Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public int getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public SessionRecord getRecord() {
|
||||
return record;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
import org.session.libsignal.utilities.Base64;
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.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);";
|
||||
|
||||
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.replace(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)});
|
||||
}
|
||||
|
||||
}
|
@ -17,12 +17,9 @@ import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||
import org.thoughtcrime.securesms.database.JobDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
|
||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.SearchDatabase;
|
||||
import org.thoughtcrime.securesms.database.SessionDatabase;
|
||||
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
@ -94,9 +91,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(GroupDatabase.CREATE_TABLE);
|
||||
db.execSQL(RecipientDatabase.CREATE_TABLE);
|
||||
db.execSQL(GroupReceiptDatabase.CREATE_TABLE);
|
||||
db.execSQL(OneTimePreKeyDatabase.CREATE_TABLE);
|
||||
db.execSQL(SignedPreKeyDatabase.CREATE_TABLE);
|
||||
db.execSQL(SessionDatabase.CREATE_TABLE);
|
||||
for (String sql : SearchDatabase.CREATE_TABLE) {
|
||||
db.execSQL(sql);
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.dependencies;
|
||||
import android.content.Context;
|
||||
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.session.libsignal.service.api.SignalServiceAccountManager;
|
||||
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
|
||||
import org.session.libsignal.service.api.SignalServiceMessageSender;
|
||||
import org.session.libsignal.service.api.util.CredentialsProvider;
|
||||
@ -74,7 +73,6 @@ public class SignalCommunicationModule {
|
||||
private final Context context;
|
||||
private final SignalServiceNetworkAccess networkAccess;
|
||||
|
||||
private SignalServiceAccountManager accountManager;
|
||||
private SignalServiceMessageSender messageSender;
|
||||
private SignalServiceMessageReceiver messageReceiver;
|
||||
|
||||
|
@ -15,7 +15,6 @@ import com.annimon.stream.Collectors;
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.session.libsession.messaging.jobs.Data;
|
||||
import org.session.libsession.utilities.MediaTypes;
|
||||
import org.session.libsignal.metadata.InvalidMetadataMessageException;
|
||||
import org.session.libsignal.metadata.ProtocolInvalidMessageException;
|
||||
import org.session.libsignal.service.api.crypto.SignalServiceCipher;
|
||||
@ -37,7 +36,6 @@ import org.session.libsession.utilities.TextSecurePreferences;
|
||||
|
||||
import org.thoughtcrime.securesms.contactshare.ContactModelMapper;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
@ -47,11 +45,9 @@ import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.groups.GroupMessageProcessor;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
@ -70,15 +66,11 @@ import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.StickerSlide;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||
import org.session.libsignal.utilities.Hex;
|
||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
||||
import org.session.libsignal.libsignal.state.SignalProtocolStore;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.session.libsignal.service.api.SignalServiceMessageSender;
|
||||
import org.session.libsignal.service.api.messages.SignalServiceContent;
|
||||
@ -89,11 +81,9 @@ import org.session.libsignal.service.api.messages.SignalServiceGroup;
|
||||
import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage;
|
||||
import org.session.libsignal.service.api.messages.SignalServiceTypingMessage;
|
||||
import org.session.libsignal.service.api.messages.shared.SharedContact;
|
||||
import org.session.libsignal.service.api.push.SignalServiceAddress;
|
||||
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager;
|
||||
import org.session.libsignal.service.loki.utilities.PublicKeyValidation;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
|
@ -290,7 +290,6 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||
.asExpirationUpdate(message.isExpirationUpdate())
|
||||
.withProfileKey(profileKey.orNull())
|
||||
.withQuote(quote.orNull())
|
||||
.withSticker(sticker.orNull())
|
||||
.withSharedContacts(sharedContacts)
|
||||
.withPreviews(previews);
|
||||
}
|
||||
|
@ -263,7 +263,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
.withExpiration((int)(message.getExpiresIn() / 1000))
|
||||
.withProfileKey(profileKey.orNull())
|
||||
.withQuote(quote.orNull())
|
||||
.withSticker(sticker.orNull())
|
||||
.withSharedContacts(sharedContacts)
|
||||
.withPreviews(previews)
|
||||
.asExpirationUpdate(message.isExpirationUpdate())
|
||||
@ -277,7 +276,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
.withExpiration((int)(message.getExpiresIn() / 1000))
|
||||
.withProfileKey(profileKey.orNull())
|
||||
.withQuote(quote.orNull())
|
||||
.withSticker(sticker.orNull())
|
||||
.withSharedContacts(sharedContacts)
|
||||
.withPreviews(previews)
|
||||
.asExpirationUpdate(message.isExpirationUpdate())
|
||||
|
@ -210,7 +210,6 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
||||
.withBody(message.getBody())
|
||||
.withExpiration((int)(message.getExpiresIn() / 1000))
|
||||
.withProfileKey(profileKey.orNull())
|
||||
.asEndSessionMessage(message.isEndSession())
|
||||
.build();
|
||||
|
||||
SignalServiceDataMessage textSecureSelfSendMessage = SignalServiceDataMessage.newBuilder()
|
||||
@ -219,7 +218,6 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
||||
.withSyncTarget(destination.serialize())
|
||||
.withExpiration((int)(message.getExpiresIn() / 1000))
|
||||
.withProfileKey(profileKey.orNull())
|
||||
.asEndSessionMessage(message.isEndSession())
|
||||
.build();
|
||||
|
||||
if (userPublicKey.equals(address.getNumber())) {
|
||||
|
@ -5,14 +5,8 @@
|
||||
*/
|
||||
package org.session.libsignal.libsignal;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
|
||||
|
||||
import static org.session.libsignal.libsignal.state.StorageProtos.IdentityKeyPairStructure;
|
||||
|
||||
/**
|
||||
* Holder for public and private identity key pair.
|
||||
*
|
||||
@ -28,16 +22,6 @@ public class IdentityKeyPair {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
public IdentityKeyPair(byte[] serialized) throws InvalidKeyException {
|
||||
try {
|
||||
IdentityKeyPairStructure structure = IdentityKeyPairStructure.parseFrom(serialized);
|
||||
this.publicKey = new IdentityKey(structure.getPublicKey().toByteArray(), 0);
|
||||
this.privateKey = Curve.decodePrivatePoint(structure.getPrivateKey().toByteArray());
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new InvalidKeyException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public IdentityKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
@ -45,11 +29,4 @@ public class IdentityKeyPair {
|
||||
public ECPrivateKey getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
return IdentityKeyPairStructure.newBuilder()
|
||||
.setPublicKey(ByteString.copyFrom(publicKey.serialize()))
|
||||
.setPrivateKey(ByteString.copyFrom(privateKey.serialize()))
|
||||
.build().toByteArray();
|
||||
}
|
||||
}
|
||||
|
@ -1,187 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.groups;
|
||||
|
||||
import org.session.libsignal.libsignal.DuplicateMessageException;
|
||||
import org.session.libsignal.libsignal.InvalidKeyIdException;
|
||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
||||
import org.session.libsignal.libsignal.LegacyMessageException;
|
||||
import org.session.libsignal.libsignal.NoSessionException;
|
||||
import org.session.libsignal.libsignal.groups.ratchet.SenderChainKey;
|
||||
import org.session.libsignal.libsignal.groups.ratchet.SenderMessageKey;
|
||||
import org.session.libsignal.libsignal.groups.state.SenderKeyRecord;
|
||||
import org.session.libsignal.libsignal.groups.state.SenderKeyState;
|
||||
import org.session.libsignal.libsignal.groups.state.SenderKeyStore;
|
||||
import org.session.libsignal.libsignal.protocol.SenderKeyMessage;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* The main entry point for Signal Protocol group encrypt/decrypt operations.
|
||||
*
|
||||
* Once a session has been established with {@link org.session.libsignal.libsignal.groups.GroupSessionBuilder}
|
||||
* and a {@link org.session.libsignal.libsignal.protocol.SenderKeyDistributionMessage} has been
|
||||
* distributed to each member of the group, this class can be used for all subsequent encrypt/decrypt
|
||||
* operations within that session (ie: until group membership changes).
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class GroupCipher {
|
||||
|
||||
static final Object LOCK = new Object();
|
||||
|
||||
private final SenderKeyStore senderKeyStore;
|
||||
private final SenderKeyName senderKeyId;
|
||||
|
||||
public GroupCipher(SenderKeyStore senderKeyStore, SenderKeyName senderKeyId) {
|
||||
this.senderKeyStore = senderKeyStore;
|
||||
this.senderKeyId = senderKeyId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a message.
|
||||
*
|
||||
* @param paddedPlaintext The plaintext message bytes, optionally padded.
|
||||
* @return Ciphertext.
|
||||
* @throws NoSessionException
|
||||
*/
|
||||
public byte[] encrypt(byte[] paddedPlaintext) throws NoSessionException {
|
||||
synchronized (LOCK) {
|
||||
try {
|
||||
SenderKeyRecord record = senderKeyStore.loadSenderKey(senderKeyId);
|
||||
SenderKeyState senderKeyState = record.getSenderKeyState();
|
||||
SenderMessageKey senderKey = senderKeyState.getSenderChainKey().getSenderMessageKey();
|
||||
byte[] ciphertext = getCipherText(senderKey.getIv(), senderKey.getCipherKey(), paddedPlaintext);
|
||||
|
||||
SenderKeyMessage senderKeyMessage = new SenderKeyMessage(senderKeyState.getKeyId(),
|
||||
senderKey.getIteration(),
|
||||
ciphertext,
|
||||
senderKeyState.getSigningKeyPrivate());
|
||||
|
||||
senderKeyState.setSenderChainKey(senderKeyState.getSenderChainKey().getNext());
|
||||
|
||||
senderKeyStore.storeSenderKey(senderKeyId, record);
|
||||
|
||||
return senderKeyMessage.serialize();
|
||||
} catch (InvalidKeyIdException e) {
|
||||
throw new NoSessionException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decrypt(byte[] senderKeyMessageBytes) throws LegacyMessageException, InvalidMessageException, DuplicateMessageException, NoSessionException
|
||||
{
|
||||
synchronized (LOCK) {
|
||||
try {
|
||||
SenderKeyRecord record = senderKeyStore.loadSenderKey(senderKeyId);
|
||||
|
||||
if (record.isEmpty()) {
|
||||
throw new NoSessionException("No sender key for: " + senderKeyId);
|
||||
}
|
||||
|
||||
SenderKeyMessage senderKeyMessage = new SenderKeyMessage(senderKeyMessageBytes);
|
||||
SenderKeyState senderKeyState = record.getSenderKeyState(senderKeyMessage.getKeyId());
|
||||
|
||||
senderKeyMessage.verifySignature(senderKeyState.getSigningKeyPublic());
|
||||
|
||||
SenderMessageKey senderKey = getSenderKey(senderKeyState, senderKeyMessage.getIteration());
|
||||
|
||||
byte[] plaintext = getPlainText(senderKey.getIv(), senderKey.getCipherKey(), senderKeyMessage.getCipherText());
|
||||
|
||||
senderKeyStore.storeSenderKey(senderKeyId, record);
|
||||
|
||||
return plaintext;
|
||||
} catch (org.session.libsignal.libsignal.InvalidKeyException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (InvalidKeyIdException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SenderMessageKey getSenderKey(SenderKeyState senderKeyState, int iteration)
|
||||
throws DuplicateMessageException, InvalidMessageException
|
||||
{
|
||||
SenderChainKey senderChainKey = senderKeyState.getSenderChainKey();
|
||||
|
||||
if (senderChainKey.getIteration() > iteration) {
|
||||
if (senderKeyState.hasSenderMessageKey(iteration)) {
|
||||
return senderKeyState.removeSenderMessageKey(iteration);
|
||||
} else {
|
||||
throw new DuplicateMessageException("Received message with old counter: " +
|
||||
senderChainKey.getIteration() + " , " + iteration);
|
||||
}
|
||||
}
|
||||
|
||||
if (iteration - senderChainKey.getIteration() > 2000) {
|
||||
throw new InvalidMessageException("Over 2000 messages into the future!");
|
||||
}
|
||||
|
||||
while (senderChainKey.getIteration() < iteration) {
|
||||
senderKeyState.addSenderMessageKey(senderChainKey.getSenderMessageKey());
|
||||
senderChainKey = senderChainKey.getNext();
|
||||
}
|
||||
|
||||
senderKeyState.setSenderChainKey(senderChainKey.getNext());
|
||||
return senderChainKey.getSenderMessageKey();
|
||||
}
|
||||
|
||||
private byte[] getPlainText(byte[] iv, byte[] key, byte[] ciphertext)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
try {
|
||||
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), ivParameterSpec);
|
||||
|
||||
return cipher.doFinal(ciphertext);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch(java.security.InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getCipherText(byte[] iv, byte[] key, byte[] plaintext) {
|
||||
try {
|
||||
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
|
||||
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), ivParameterSpec);
|
||||
|
||||
return cipher.doFinal(plaintext);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (java.security.InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.groups;
|
||||
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.InvalidKeyIdException;
|
||||
import org.session.libsignal.libsignal.groups.state.SenderKeyRecord;
|
||||
import org.session.libsignal.libsignal.groups.state.SenderKeyState;
|
||||
import org.session.libsignal.libsignal.groups.state.SenderKeyStore;
|
||||
import org.session.libsignal.libsignal.protocol.SenderKeyDistributionMessage;
|
||||
import org.session.libsignal.libsignal.util.KeyHelper;
|
||||
|
||||
/**
|
||||
* GroupSessionBuilder is responsible for setting up group SenderKey encrypted sessions.
|
||||
*
|
||||
* Once a session has been established, {@link org.session.libsignal.libsignal.groups.GroupCipher}
|
||||
* can be used to encrypt/decrypt messages in that session.
|
||||
* <p>
|
||||
* The built sessions are unidirectional: they can be used either for sending or for receiving,
|
||||
* but not both.
|
||||
*
|
||||
* Sessions are constructed per (groupId + senderId + deviceId) tuple. Remote logical users
|
||||
* are identified by their senderId, and each logical recipientId can have multiple physical
|
||||
* devices.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class GroupSessionBuilder {
|
||||
|
||||
private final SenderKeyStore senderKeyStore;
|
||||
|
||||
public GroupSessionBuilder(SenderKeyStore senderKeyStore) {
|
||||
this.senderKeyStore = senderKeyStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a group session for receiving messages from senderKeyName.
|
||||
*
|
||||
* @param senderKeyName The (groupId, senderId, deviceId) tuple associated with the SenderKeyDistributionMessage.
|
||||
* @param senderKeyDistributionMessage A received SenderKeyDistributionMessage.
|
||||
*/
|
||||
public void process(SenderKeyName senderKeyName, SenderKeyDistributionMessage senderKeyDistributionMessage) {
|
||||
synchronized (GroupCipher.LOCK) {
|
||||
SenderKeyRecord senderKeyRecord = senderKeyStore.loadSenderKey(senderKeyName);
|
||||
senderKeyRecord.addSenderKeyState(senderKeyDistributionMessage.getId(),
|
||||
senderKeyDistributionMessage.getIteration(),
|
||||
senderKeyDistributionMessage.getChainKey(),
|
||||
senderKeyDistributionMessage.getSignatureKey());
|
||||
senderKeyStore.storeSenderKey(senderKeyName, senderKeyRecord);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a group session for sending messages.
|
||||
*
|
||||
* @param senderKeyName The (groupId, senderId, deviceId) tuple. In this case, 'senderId' should be the caller.
|
||||
* @return A SenderKeyDistributionMessage that is individually distributed to each member of the group.
|
||||
*/
|
||||
public SenderKeyDistributionMessage create(SenderKeyName senderKeyName) {
|
||||
synchronized (GroupCipher.LOCK) {
|
||||
try {
|
||||
SenderKeyRecord senderKeyRecord = senderKeyStore.loadSenderKey(senderKeyName);
|
||||
|
||||
if (senderKeyRecord.isEmpty()) {
|
||||
senderKeyRecord.setSenderKeyState(KeyHelper.generateSenderKeyId(),
|
||||
0,
|
||||
KeyHelper.generateSenderKey(),
|
||||
KeyHelper.generateSenderSigningKey());
|
||||
senderKeyStore.storeSenderKey(senderKeyName, senderKeyRecord);
|
||||
}
|
||||
|
||||
SenderKeyState state = senderKeyRecord.getSenderKeyState();
|
||||
|
||||
return new SenderKeyDistributionMessage(state.getKeyId(),
|
||||
state.getSenderChainKey().getIteration(),
|
||||
state.getSenderChainKey().getSeed(),
|
||||
state.getSigningKeyPublic());
|
||||
|
||||
} catch (InvalidKeyIdException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.groups;
|
||||
|
||||
import org.session.libsignal.libsignal.SignalProtocolAddress;
|
||||
|
||||
/**
|
||||
* A representation of a (groupId + senderId + deviceId) tuple.
|
||||
*/
|
||||
public class SenderKeyName {
|
||||
|
||||
private final String groupId;
|
||||
private final SignalProtocolAddress sender;
|
||||
|
||||
public SenderKeyName(String groupId, SignalProtocolAddress sender) {
|
||||
this.groupId = groupId;
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public SignalProtocolAddress getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public String serialize() {
|
||||
return groupId + "::" + sender.getName() + "::" + String.valueOf(sender.getDeviceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) return false;
|
||||
if (!(other instanceof SenderKeyName)) return false;
|
||||
|
||||
SenderKeyName that = (SenderKeyName)other;
|
||||
|
||||
return
|
||||
this.groupId.equals(that.groupId) &&
|
||||
this.sender.equals(that.sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.groupId.hashCode() ^ this.sender.hashCode();
|
||||
}
|
||||
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.groups.ratchet;
|
||||
|
||||
import org.session.libsignal.libsignal.groups.ratchet.SenderMessageKey;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Each SenderKey is a "chain" of keys, each derived from the previous.
|
||||
*
|
||||
* At any given point in time, the state of a SenderKey can be represented
|
||||
* as the current chain key value, along with its iteration count. From there,
|
||||
* subsequent iterations can be derived, as well as individual message keys from
|
||||
* each chain key.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class SenderChainKey {
|
||||
|
||||
private static final byte[] MESSAGE_KEY_SEED = {0x01};
|
||||
private static final byte[] CHAIN_KEY_SEED = {0x02};
|
||||
|
||||
private final int iteration;
|
||||
private final byte[] chainKey;
|
||||
|
||||
public SenderChainKey(int iteration, byte[] chainKey) {
|
||||
this.iteration = iteration;
|
||||
this.chainKey = chainKey;
|
||||
}
|
||||
|
||||
public int getIteration() {
|
||||
return iteration;
|
||||
}
|
||||
|
||||
public SenderMessageKey getSenderMessageKey() {
|
||||
return new SenderMessageKey(iteration, getDerivative(MESSAGE_KEY_SEED, chainKey));
|
||||
}
|
||||
|
||||
public SenderChainKey getNext() {
|
||||
return new SenderChainKey(iteration + 1, getDerivative(CHAIN_KEY_SEED, chainKey));
|
||||
}
|
||||
|
||||
public byte[] getSeed() {
|
||||
return chainKey;
|
||||
}
|
||||
|
||||
private byte[] getDerivative(byte[] seed, byte[] key) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(key, "HmacSHA256"));
|
||||
|
||||
return mac.doFinal(seed);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
|
||||
package org.session.libsignal.libsignal.groups.ratchet;
|
||||
|
||||
import org.session.libsignal.libsignal.kdf.HKDFv3;
|
||||
import org.session.libsignal.libsignal.util.ByteUtil;
|
||||
|
||||
/**
|
||||
* The final symmetric material (IV and Cipher Key) used for encrypting
|
||||
* individual SenderKey messages.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class SenderMessageKey {
|
||||
|
||||
private final int iteration;
|
||||
private final byte[] iv;
|
||||
private final byte[] cipherKey;
|
||||
private final byte[] seed;
|
||||
|
||||
public SenderMessageKey(int iteration, byte[] seed) {
|
||||
byte[] derivative = new HKDFv3().deriveSecrets(seed, "WhisperGroup".getBytes(), 48);
|
||||
byte[][] parts = ByteUtil.split(derivative, 16, 32);
|
||||
|
||||
this.iteration = iteration;
|
||||
this.seed = seed;
|
||||
this.iv = parts[0];
|
||||
this.cipherKey = parts[1];
|
||||
}
|
||||
|
||||
public int getIteration() {
|
||||
return iteration;
|
||||
}
|
||||
|
||||
public byte[] getIv() {
|
||||
return iv;
|
||||
}
|
||||
|
||||
public byte[] getCipherKey() {
|
||||
return cipherKey;
|
||||
}
|
||||
|
||||
public byte[] getSeed() {
|
||||
return seed;
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.groups.state;
|
||||
|
||||
import org.session.libsignal.libsignal.InvalidKeyIdException;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.libsignal.state.StorageProtos;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.session.libsignal.libsignal.state.StorageProtos.SenderKeyRecordStructure;
|
||||
|
||||
/**
|
||||
* A durable representation of a set of SenderKeyStates for a specific
|
||||
* SenderKeyName.
|
||||
*
|
||||
* @author Moxie Marlisnpike
|
||||
*/
|
||||
public class SenderKeyRecord {
|
||||
|
||||
private static final int MAX_STATES = 5;
|
||||
|
||||
private LinkedList<SenderKeyState> senderKeyStates = new LinkedList<SenderKeyState>();
|
||||
|
||||
public SenderKeyRecord() {}
|
||||
|
||||
public SenderKeyRecord(byte[] serialized) throws IOException {
|
||||
SenderKeyRecordStructure senderKeyRecordStructure = SenderKeyRecordStructure.parseFrom(serialized);
|
||||
|
||||
for (StorageProtos.SenderKeyStateStructure structure : senderKeyRecordStructure.getSenderKeyStatesList()) {
|
||||
this.senderKeyStates.add(new SenderKeyState(structure));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return senderKeyStates.isEmpty();
|
||||
}
|
||||
|
||||
public SenderKeyState getSenderKeyState() throws InvalidKeyIdException {
|
||||
if (!senderKeyStates.isEmpty()) {
|
||||
return senderKeyStates.get(0);
|
||||
} else {
|
||||
throw new InvalidKeyIdException("No key state in record!");
|
||||
}
|
||||
}
|
||||
|
||||
public SenderKeyState getSenderKeyState(int keyId) throws InvalidKeyIdException {
|
||||
for (SenderKeyState state : senderKeyStates) {
|
||||
if (state.getKeyId() == keyId) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidKeyIdException("No keys for: " + keyId);
|
||||
}
|
||||
|
||||
public void addSenderKeyState(int id, int iteration, byte[] chainKey, ECPublicKey signatureKey) {
|
||||
senderKeyStates.addFirst(new SenderKeyState(id, iteration, chainKey, signatureKey));
|
||||
|
||||
if (senderKeyStates.size() > MAX_STATES) {
|
||||
senderKeyStates.removeLast();
|
||||
}
|
||||
}
|
||||
|
||||
public void setSenderKeyState(int id, int iteration, byte[] chainKey, ECKeyPair signatureKey) {
|
||||
senderKeyStates.clear();
|
||||
senderKeyStates.add(new SenderKeyState(id, iteration, chainKey, signatureKey));
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
SenderKeyRecordStructure.Builder recordStructure = SenderKeyRecordStructure.newBuilder();
|
||||
|
||||
for (SenderKeyState senderKeyState : senderKeyStates) {
|
||||
recordStructure.addSenderKeyStates(senderKeyState.getStructure());
|
||||
}
|
||||
|
||||
return recordStructure.build().toByteArray();
|
||||
}
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.groups.state;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.libsignal.groups.ratchet.SenderChainKey;
|
||||
import org.session.libsignal.libsignal.groups.ratchet.SenderMessageKey;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.session.libsignal.libsignal.state.StorageProtos.SenderKeyStateStructure;
|
||||
|
||||
/**
|
||||
* Represents the state of an individual SenderKey ratchet.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class SenderKeyState {
|
||||
|
||||
private static final int MAX_MESSAGE_KEYS = 2000;
|
||||
|
||||
private SenderKeyStateStructure senderKeyStateStructure;
|
||||
|
||||
public SenderKeyState(int id, int iteration, byte[] chainKey, ECPublicKey signatureKey) {
|
||||
this(id, iteration, chainKey, signatureKey, Optional.<ECPrivateKey>absent());
|
||||
}
|
||||
|
||||
public SenderKeyState(int id, int iteration, byte[] chainKey, ECKeyPair signatureKey) {
|
||||
this(id, iteration, chainKey, signatureKey.getPublicKey(), Optional.of(signatureKey.getPrivateKey()));
|
||||
}
|
||||
|
||||
private SenderKeyState(int id, int iteration, byte[] chainKey,
|
||||
ECPublicKey signatureKeyPublic,
|
||||
Optional<ECPrivateKey> signatureKeyPrivate)
|
||||
{
|
||||
SenderKeyStateStructure.SenderChainKey senderChainKeyStructure =
|
||||
SenderKeyStateStructure.SenderChainKey.newBuilder()
|
||||
.setIteration(iteration)
|
||||
.setSeed(ByteString.copyFrom(chainKey))
|
||||
.build();
|
||||
|
||||
SenderKeyStateStructure.SenderSigningKey.Builder signingKeyStructure =
|
||||
SenderKeyStateStructure.SenderSigningKey.newBuilder()
|
||||
.setPublic(ByteString.copyFrom(signatureKeyPublic.serialize()));
|
||||
|
||||
if (signatureKeyPrivate.isPresent()) {
|
||||
signingKeyStructure.setPrivate(ByteString.copyFrom(signatureKeyPrivate.get().serialize()));
|
||||
}
|
||||
|
||||
this.senderKeyStateStructure = SenderKeyStateStructure.newBuilder()
|
||||
.setSenderKeyId(id)
|
||||
.setSenderChainKey(senderChainKeyStructure)
|
||||
.setSenderSigningKey(signingKeyStructure)
|
||||
.build();
|
||||
}
|
||||
|
||||
public SenderKeyState(SenderKeyStateStructure senderKeyStateStructure) {
|
||||
this.senderKeyStateStructure = senderKeyStateStructure;
|
||||
}
|
||||
|
||||
public int getKeyId() {
|
||||
return senderKeyStateStructure.getSenderKeyId();
|
||||
}
|
||||
|
||||
public SenderChainKey getSenderChainKey() {
|
||||
return new SenderChainKey(senderKeyStateStructure.getSenderChainKey().getIteration(),
|
||||
senderKeyStateStructure.getSenderChainKey().getSeed().toByteArray());
|
||||
}
|
||||
|
||||
public void setSenderChainKey(SenderChainKey chainKey) {
|
||||
SenderKeyStateStructure.SenderChainKey senderChainKeyStructure =
|
||||
SenderKeyStateStructure.SenderChainKey.newBuilder()
|
||||
.setIteration(chainKey.getIteration())
|
||||
.setSeed(ByteString.copyFrom(chainKey.getSeed()))
|
||||
.build();
|
||||
|
||||
this.senderKeyStateStructure = senderKeyStateStructure.toBuilder()
|
||||
.setSenderChainKey(senderChainKeyStructure)
|
||||
.build();
|
||||
}
|
||||
|
||||
public ECPublicKey getSigningKeyPublic() throws InvalidKeyException {
|
||||
return Curve.decodePoint(senderKeyStateStructure.getSenderSigningKey()
|
||||
.getPublic()
|
||||
.toByteArray(), 0);
|
||||
}
|
||||
|
||||
public ECPrivateKey getSigningKeyPrivate() {
|
||||
return Curve.decodePrivatePoint(senderKeyStateStructure.getSenderSigningKey()
|
||||
.getPrivate().toByteArray());
|
||||
}
|
||||
|
||||
public boolean hasSenderMessageKey(int iteration) {
|
||||
for (SenderKeyStateStructure.SenderMessageKey senderMessageKey : senderKeyStateStructure.getSenderMessageKeysList()) {
|
||||
if (senderMessageKey.getIteration() == iteration) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addSenderMessageKey(SenderMessageKey senderMessageKey) {
|
||||
SenderKeyStateStructure.SenderMessageKey senderMessageKeyStructure =
|
||||
SenderKeyStateStructure.SenderMessageKey.newBuilder()
|
||||
.setIteration(senderMessageKey.getIteration())
|
||||
.setSeed(ByteString.copyFrom(senderMessageKey.getSeed()))
|
||||
.build();
|
||||
|
||||
SenderKeyStateStructure.Builder builder = this.senderKeyStateStructure.toBuilder();
|
||||
|
||||
builder.addSenderMessageKeys(senderMessageKeyStructure);
|
||||
|
||||
if (builder.getSenderMessageKeysCount() > MAX_MESSAGE_KEYS) {
|
||||
builder.removeSenderMessageKeys(0);
|
||||
}
|
||||
|
||||
this.senderKeyStateStructure = builder.build();
|
||||
}
|
||||
|
||||
public SenderMessageKey removeSenderMessageKey(int iteration) {
|
||||
List<SenderKeyStateStructure.SenderMessageKey> keys = new LinkedList<SenderKeyStateStructure.SenderMessageKey>(senderKeyStateStructure.getSenderMessageKeysList());
|
||||
Iterator<SenderKeyStateStructure.SenderMessageKey> iterator = keys.iterator();
|
||||
|
||||
SenderKeyStateStructure.SenderMessageKey result = null;
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
SenderKeyStateStructure.SenderMessageKey senderMessageKey = iterator.next();
|
||||
|
||||
if (senderMessageKey.getIteration() == iteration) {
|
||||
result = senderMessageKey;
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.senderKeyStateStructure = this.senderKeyStateStructure.toBuilder()
|
||||
.clearSenderMessageKeys()
|
||||
.addAllSenderMessageKeys(keys)
|
||||
.build();
|
||||
|
||||
if (result != null) {
|
||||
return new SenderMessageKey(result.getIteration(), result.getSeed().toByteArray());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public SenderKeyStateStructure getStructure() {
|
||||
return senderKeyStateStructure;
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.groups.state;
|
||||
|
||||
import org.session.libsignal.libsignal.groups.SenderKeyName;
|
||||
import org.session.libsignal.libsignal.groups.state.SenderKeyRecord;
|
||||
|
||||
public interface SenderKeyStore {
|
||||
|
||||
/**
|
||||
* Commit to storage the {@link org.session.libsignal.libsignal.groups.state.SenderKeyRecord} for a
|
||||
* given (groupId + senderId + deviceId) tuple.
|
||||
*
|
||||
* @param senderKeyName the (groupId + senderId + deviceId) tuple.
|
||||
* @param record the current SenderKeyRecord for the specified senderKeyName.
|
||||
*/
|
||||
public void storeSenderKey(SenderKeyName senderKeyName, SenderKeyRecord record);
|
||||
|
||||
/**
|
||||
* Returns a copy of the {@link SenderKeyRecord}
|
||||
* corresponding to the (groupId + senderId + deviceId) tuple, or a new SenderKeyRecord if
|
||||
* one does not currently exist.
|
||||
* <p>
|
||||
* It is important that implementations return a copy of the current durable information. The
|
||||
* returned SenderKeyRecord may be modified, but those changes should not have an effect on the
|
||||
* durable session state (what is returned by subsequent calls to this method) without the
|
||||
* store method being called here first.
|
||||
*
|
||||
* @param senderKeyName The (groupId + senderId + deviceId) tuple.
|
||||
* @return a copy of the SenderKeyRecord corresponding to the (groupId + senderId + deviceId tuple, or
|
||||
* a new SenderKeyRecord if one does not currently exist.
|
||||
*/
|
||||
|
||||
public SenderKeyRecord loadSenderKey(SenderKeyName senderKeyName);
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
* <p>
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.protocol;
|
||||
|
||||
public interface CiphertextMessage {
|
||||
|
||||
public static final int CURRENT_VERSION = 3;
|
||||
|
||||
public static final int WHISPER_TYPE = 2;
|
||||
public static final int PREKEY_TYPE = 3;
|
||||
public static final int SENDERKEY_TYPE = 4;
|
||||
public static final int SENDERKEY_DISTRIBUTION_TYPE = 5;
|
||||
public static final int CLOSED_GROUP_CIPHERTEXT = 6;
|
||||
public static final int FALLBACK_MESSAGE_TYPE = 999; // Loki
|
||||
|
||||
// This should be the worst case (worse than V2). So not always accurate, but good enough for padding.
|
||||
public static final int ENCRYPTED_MESSAGE_OVERHEAD = 53;
|
||||
|
||||
public byte[] serialize();
|
||||
|
||||
public int getType();
|
||||
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.protocol;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.session.libsignal.libsignal.IdentityKey;
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
||||
import org.session.libsignal.libsignal.InvalidVersionException;
|
||||
import org.session.libsignal.libsignal.LegacyMessageException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.libsignal.util.ByteUtil;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
|
||||
|
||||
public class PreKeySignalMessage implements CiphertextMessage {
|
||||
|
||||
private final int version;
|
||||
private final int registrationId;
|
||||
private final Optional<Integer> preKeyId;
|
||||
private final int signedPreKeyId;
|
||||
private final ECPublicKey baseKey;
|
||||
private final IdentityKey identityKey;
|
||||
private final SignalMessage message;
|
||||
private final byte[] serialized;
|
||||
|
||||
public PreKeySignalMessage(byte[] serialized)
|
||||
throws InvalidMessageException, InvalidVersionException
|
||||
{
|
||||
try {
|
||||
this.version = ByteUtil.highBitsToInt(serialized[0]);
|
||||
|
||||
if (this.version > CiphertextMessage.CURRENT_VERSION) {
|
||||
throw new InvalidVersionException("Unknown version: " + this.version);
|
||||
}
|
||||
|
||||
if (this.version < CiphertextMessage.CURRENT_VERSION) {
|
||||
throw new LegacyMessageException("Legacy version: " + this.version);
|
||||
}
|
||||
|
||||
SignalProtos.PreKeySignalMessage preKeyWhisperMessage
|
||||
= SignalProtos.PreKeySignalMessage.parseFrom(ByteString.copyFrom(serialized, 1,
|
||||
serialized.length-1));
|
||||
|
||||
if (!preKeyWhisperMessage.hasSignedPreKeyId() ||
|
||||
!preKeyWhisperMessage.hasBaseKey() ||
|
||||
!preKeyWhisperMessage.hasIdentityKey() ||
|
||||
!preKeyWhisperMessage.hasMessage())
|
||||
{
|
||||
throw new InvalidMessageException("Incomplete message.");
|
||||
}
|
||||
|
||||
this.serialized = serialized;
|
||||
this.registrationId = preKeyWhisperMessage.getRegistrationId();
|
||||
this.preKeyId = preKeyWhisperMessage.hasPreKeyId() ? Optional.of(preKeyWhisperMessage.getPreKeyId()) : Optional.<Integer>absent();
|
||||
this.signedPreKeyId = preKeyWhisperMessage.hasSignedPreKeyId() ? preKeyWhisperMessage.getSignedPreKeyId() : -1;
|
||||
this.baseKey = Curve.decodePoint(preKeyWhisperMessage.getBaseKey().toByteArray(), 0);
|
||||
this.identityKey = new IdentityKey(Curve.decodePoint(preKeyWhisperMessage.getIdentityKey().toByteArray(), 0));
|
||||
this.message = new SignalMessage(preKeyWhisperMessage.getMessage().toByteArray());
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (LegacyMessageException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public PreKeySignalMessage(int messageVersion, int registrationId, Optional<Integer> preKeyId,
|
||||
int signedPreKeyId, ECPublicKey baseKey, IdentityKey identityKey,
|
||||
SignalMessage message)
|
||||
{
|
||||
this.version = messageVersion;
|
||||
this.registrationId = registrationId;
|
||||
this.preKeyId = preKeyId;
|
||||
this.signedPreKeyId = signedPreKeyId;
|
||||
this.baseKey = baseKey;
|
||||
this.identityKey = identityKey;
|
||||
this.message = message;
|
||||
|
||||
SignalProtos.PreKeySignalMessage.Builder builder =
|
||||
SignalProtos.PreKeySignalMessage.newBuilder()
|
||||
.setSignedPreKeyId(signedPreKeyId)
|
||||
.setBaseKey(ByteString.copyFrom(baseKey.serialize()))
|
||||
.setIdentityKey(ByteString.copyFrom(identityKey.serialize()))
|
||||
.setMessage(ByteString.copyFrom(message.serialize()))
|
||||
.setRegistrationId(registrationId);
|
||||
|
||||
if (preKeyId.isPresent()) {
|
||||
builder.setPreKeyId(preKeyId.get());
|
||||
}
|
||||
|
||||
byte[] versionBytes = {ByteUtil.intsToByteHighAndLow(this.version, CURRENT_VERSION)};
|
||||
byte[] messageBytes = builder.build().toByteArray();
|
||||
|
||||
this.serialized = ByteUtil.combine(versionBytes, messageBytes);
|
||||
}
|
||||
|
||||
public int getMessageVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public IdentityKey getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
public int getRegistrationId() {
|
||||
return registrationId;
|
||||
}
|
||||
|
||||
public Optional<Integer> getPreKeyId() {
|
||||
return preKeyId;
|
||||
}
|
||||
|
||||
public int getSignedPreKeyId() {
|
||||
return signedPreKeyId;
|
||||
}
|
||||
|
||||
public ECPublicKey getBaseKey() {
|
||||
return baseKey;
|
||||
}
|
||||
|
||||
public SignalMessage getWhisperMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return serialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return CiphertextMessage.PREKEY_TYPE;
|
||||
}
|
||||
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.protocol;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
||||
import org.session.libsignal.libsignal.LegacyMessageException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.libsignal.util.ByteUtil;
|
||||
|
||||
public class SenderKeyDistributionMessage implements CiphertextMessage {
|
||||
|
||||
private final int id;
|
||||
private final int iteration;
|
||||
private final byte[] chainKey;
|
||||
private final ECPublicKey signatureKey;
|
||||
private final byte[] serialized;
|
||||
|
||||
public SenderKeyDistributionMessage(int id, int iteration, byte[] chainKey, ECPublicKey signatureKey) {
|
||||
byte[] version = {ByteUtil.intsToByteHighAndLow(CURRENT_VERSION, CURRENT_VERSION)};
|
||||
byte[] protobuf = SignalProtos.SenderKeyDistributionMessage.newBuilder()
|
||||
.setId(id)
|
||||
.setIteration(iteration)
|
||||
.setChainKey(ByteString.copyFrom(chainKey))
|
||||
.setSigningKey(ByteString.copyFrom(signatureKey.serialize()))
|
||||
.build().toByteArray();
|
||||
|
||||
this.id = id;
|
||||
this.iteration = iteration;
|
||||
this.chainKey = chainKey;
|
||||
this.signatureKey = signatureKey;
|
||||
this.serialized = ByteUtil.combine(version, protobuf);
|
||||
}
|
||||
|
||||
public SenderKeyDistributionMessage(byte[] serialized) throws LegacyMessageException, InvalidMessageException {
|
||||
try {
|
||||
byte[][] messageParts = ByteUtil.split(serialized, 1, serialized.length - 1);
|
||||
byte version = messageParts[0][0];
|
||||
byte[] message = messageParts[1];
|
||||
|
||||
if (ByteUtil.highBitsToInt(version) < CiphertextMessage.CURRENT_VERSION) {
|
||||
throw new LegacyMessageException("Legacy message: " + ByteUtil.highBitsToInt(version));
|
||||
}
|
||||
|
||||
if (ByteUtil.highBitsToInt(version) > CURRENT_VERSION) {
|
||||
throw new InvalidMessageException("Unknown version: " + ByteUtil.highBitsToInt(version));
|
||||
}
|
||||
|
||||
SignalProtos.SenderKeyDistributionMessage distributionMessage = SignalProtos.SenderKeyDistributionMessage.parseFrom(message);
|
||||
|
||||
if (!distributionMessage.hasId() ||
|
||||
!distributionMessage.hasIteration() ||
|
||||
!distributionMessage.hasChainKey() ||
|
||||
!distributionMessage.hasSigningKey())
|
||||
{
|
||||
throw new InvalidMessageException("Incomplete message.");
|
||||
}
|
||||
|
||||
this.serialized = serialized;
|
||||
this.id = distributionMessage.getId();
|
||||
this.iteration = distributionMessage.getIteration();
|
||||
this.chainKey = distributionMessage.getChainKey().toByteArray();
|
||||
this.signatureKey = Curve.decodePoint(distributionMessage.getSigningKey().toByteArray(), 0);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return serialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return SENDERKEY_DISTRIBUTION_TYPE;
|
||||
}
|
||||
|
||||
public int getIteration() {
|
||||
return iteration;
|
||||
}
|
||||
|
||||
public byte[] getChainKey() {
|
||||
return chainKey;
|
||||
}
|
||||
|
||||
public ECPublicKey getSignatureKey() {
|
||||
return signatureKey;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.protocol;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
||||
import org.session.libsignal.libsignal.LegacyMessageException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.libsignal.protocol.CiphertextMessage;
|
||||
import org.session.libsignal.libsignal.util.ByteUtil;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
public class SenderKeyMessage implements CiphertextMessage {
|
||||
|
||||
private static final int SIGNATURE_LENGTH = 64;
|
||||
|
||||
private final int messageVersion;
|
||||
private final int keyId;
|
||||
private final int iteration;
|
||||
private final byte[] ciphertext;
|
||||
private final byte[] serialized;
|
||||
|
||||
public SenderKeyMessage(byte[] serialized) throws InvalidMessageException, LegacyMessageException {
|
||||
try {
|
||||
byte[][] messageParts = ByteUtil.split(serialized, 1, serialized.length - 1 - SIGNATURE_LENGTH, SIGNATURE_LENGTH);
|
||||
byte version = messageParts[0][0];
|
||||
byte[] message = messageParts[1];
|
||||
byte[] signature = messageParts[2];
|
||||
|
||||
if (ByteUtil.highBitsToInt(version) < 3) {
|
||||
throw new LegacyMessageException("Legacy message: " + ByteUtil.highBitsToInt(version));
|
||||
}
|
||||
|
||||
if (ByteUtil.highBitsToInt(version) > CURRENT_VERSION) {
|
||||
throw new InvalidMessageException("Unknown version: " + ByteUtil.highBitsToInt(version));
|
||||
}
|
||||
|
||||
SignalProtos.SenderKeyMessage senderKeyMessage = SignalProtos.SenderKeyMessage.parseFrom(message);
|
||||
|
||||
if (!senderKeyMessage.hasId() ||
|
||||
!senderKeyMessage.hasIteration() ||
|
||||
!senderKeyMessage.hasCiphertext())
|
||||
{
|
||||
throw new InvalidMessageException("Incomplete message.");
|
||||
}
|
||||
|
||||
this.serialized = serialized;
|
||||
this.messageVersion = ByteUtil.highBitsToInt(version);
|
||||
this.keyId = senderKeyMessage.getId();
|
||||
this.iteration = senderKeyMessage.getIteration();
|
||||
this.ciphertext = senderKeyMessage.getCiphertext().toByteArray();
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (ParseException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public SenderKeyMessage(int keyId, int iteration, byte[] ciphertext, ECPrivateKey signatureKey) {
|
||||
byte[] version = {ByteUtil.intsToByteHighAndLow(CURRENT_VERSION, CURRENT_VERSION)};
|
||||
byte[] message = SignalProtos.SenderKeyMessage.newBuilder()
|
||||
.setId(keyId)
|
||||
.setIteration(iteration)
|
||||
.setCiphertext(ByteString.copyFrom(ciphertext))
|
||||
.build().toByteArray();
|
||||
|
||||
byte[] signature = getSignature(signatureKey, ByteUtil.combine(version, message));
|
||||
|
||||
this.serialized = ByteUtil.combine(version, message, signature);
|
||||
this.messageVersion = CURRENT_VERSION;
|
||||
this.keyId = keyId;
|
||||
this.iteration = iteration;
|
||||
this.ciphertext = ciphertext;
|
||||
}
|
||||
|
||||
public int getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public int getIteration() {
|
||||
return iteration;
|
||||
}
|
||||
|
||||
public byte[] getCipherText() {
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
public void verifySignature(ECPublicKey signatureKey)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
try {
|
||||
byte[][] parts = ByteUtil.split(serialized, serialized.length - SIGNATURE_LENGTH, SIGNATURE_LENGTH);
|
||||
|
||||
if (!Curve.verifySignature(signatureKey, parts[0], parts[1])) {
|
||||
throw new InvalidMessageException("Invalid signature!");
|
||||
}
|
||||
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getSignature(ECPrivateKey signatureKey, byte[] serialized) {
|
||||
try {
|
||||
return Curve.calculateSignature(signatureKey, serialized);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return serialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return CiphertextMessage.SENDERKEY_TYPE;
|
||||
}
|
||||
}
|
@ -1,157 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.protocol;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.session.libsignal.libsignal.IdentityKey;
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
||||
import org.session.libsignal.libsignal.LegacyMessageException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.libsignal.protocol.CiphertextMessage;
|
||||
import org.session.libsignal.libsignal.util.ByteUtil;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.ParseException;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class SignalMessage implements CiphertextMessage {
|
||||
|
||||
private static final int MAC_LENGTH = 8;
|
||||
|
||||
private final int messageVersion;
|
||||
private final ECPublicKey senderRatchetKey;
|
||||
private final int counter;
|
||||
private final int previousCounter;
|
||||
private final byte[] ciphertext;
|
||||
private final byte[] serialized;
|
||||
|
||||
public SignalMessage(byte[] serialized) throws InvalidMessageException, LegacyMessageException {
|
||||
try {
|
||||
byte[][] messageParts = ByteUtil.split(serialized, 1, serialized.length - 1 - MAC_LENGTH, MAC_LENGTH);
|
||||
byte version = messageParts[0][0];
|
||||
byte[] message = messageParts[1];
|
||||
byte[] mac = messageParts[2];
|
||||
|
||||
if (ByteUtil.highBitsToInt(version) < CURRENT_VERSION) {
|
||||
throw new LegacyMessageException("Legacy message: " + ByteUtil.highBitsToInt(version));
|
||||
}
|
||||
|
||||
if (ByteUtil.highBitsToInt(version) > CURRENT_VERSION) {
|
||||
throw new InvalidMessageException("Unknown version: " + ByteUtil.highBitsToInt(version));
|
||||
}
|
||||
|
||||
SignalProtos.SignalMessage whisperMessage = SignalProtos.SignalMessage.parseFrom(message);
|
||||
|
||||
if (!whisperMessage.hasCiphertext() ||
|
||||
!whisperMessage.hasCounter() ||
|
||||
!whisperMessage.hasRatchetKey())
|
||||
{
|
||||
throw new InvalidMessageException("Incomplete message.");
|
||||
}
|
||||
|
||||
this.serialized = serialized;
|
||||
this.senderRatchetKey = Curve.decodePoint(whisperMessage.getRatchetKey().toByteArray(), 0);
|
||||
this.messageVersion = ByteUtil.highBitsToInt(version);
|
||||
this.counter = whisperMessage.getCounter();
|
||||
this.previousCounter = whisperMessage.getPreviousCounter();
|
||||
this.ciphertext = whisperMessage.getCiphertext().toByteArray();
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (ParseException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public SignalMessage(int messageVersion, SecretKeySpec macKey, ECPublicKey senderRatchetKey,
|
||||
int counter, int previousCounter, byte[] ciphertext,
|
||||
IdentityKey senderIdentityKey,
|
||||
IdentityKey receiverIdentityKey)
|
||||
{
|
||||
byte[] version = {ByteUtil.intsToByteHighAndLow(messageVersion, CURRENT_VERSION)};
|
||||
byte[] message = SignalProtos.SignalMessage.newBuilder()
|
||||
.setRatchetKey(ByteString.copyFrom(senderRatchetKey.serialize()))
|
||||
.setCounter(counter)
|
||||
.setPreviousCounter(previousCounter)
|
||||
.setCiphertext(ByteString.copyFrom(ciphertext))
|
||||
.build().toByteArray();
|
||||
|
||||
byte[] mac = getMac(senderIdentityKey, receiverIdentityKey, macKey, ByteUtil.combine(version, message));
|
||||
|
||||
this.serialized = ByteUtil.combine(version, message, mac);
|
||||
this.senderRatchetKey = senderRatchetKey;
|
||||
this.counter = counter;
|
||||
this.previousCounter = previousCounter;
|
||||
this.ciphertext = ciphertext;
|
||||
this.messageVersion = messageVersion;
|
||||
}
|
||||
|
||||
public ECPublicKey getSenderRatchetKey() {
|
||||
return senderRatchetKey;
|
||||
}
|
||||
|
||||
public int getMessageVersion() {
|
||||
return messageVersion;
|
||||
}
|
||||
|
||||
public int getCounter() {
|
||||
return counter;
|
||||
}
|
||||
|
||||
public byte[] getBody() {
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
public void verifyMac(IdentityKey senderIdentityKey, IdentityKey receiverIdentityKey, SecretKeySpec macKey)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
byte[][] parts = ByteUtil.split(serialized, serialized.length - MAC_LENGTH, MAC_LENGTH);
|
||||
byte[] ourMac = getMac(senderIdentityKey, receiverIdentityKey, macKey, parts[0]);
|
||||
byte[] theirMac = parts[1];
|
||||
|
||||
if (!MessageDigest.isEqual(ourMac, theirMac)) {
|
||||
throw new InvalidMessageException("Bad Mac!");
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getMac(IdentityKey senderIdentityKey,
|
||||
IdentityKey receiverIdentityKey,
|
||||
SecretKeySpec macKey, byte[] serialized)
|
||||
{
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(macKey);
|
||||
|
||||
mac.update(senderIdentityKey.getPublicKey().serialize());
|
||||
mac.update(receiverIdentityKey.getPublicKey().serialize());
|
||||
|
||||
byte[] fullMac = mac.doFinal(serialized);
|
||||
return ByteUtil.trim(fullMac, MAC_LENGTH);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (java.security.InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return serialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return CiphertextMessage.WHISPER_TYPE;
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.ratchet;
|
||||
|
||||
import org.session.libsignal.libsignal.IdentityKey;
|
||||
import org.session.libsignal.libsignal.IdentityKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
|
||||
public class AliceSignalProtocolParameters {
|
||||
|
||||
private final IdentityKeyPair ourIdentityKey;
|
||||
private final ECKeyPair ourBaseKey;
|
||||
|
||||
private final IdentityKey theirIdentityKey;
|
||||
private final ECPublicKey theirSignedPreKey;
|
||||
private final Optional<ECPublicKey> theirOneTimePreKey;
|
||||
private final ECPublicKey theirRatchetKey;
|
||||
|
||||
private AliceSignalProtocolParameters(IdentityKeyPair ourIdentityKey, ECKeyPair ourBaseKey,
|
||||
IdentityKey theirIdentityKey, ECPublicKey theirSignedPreKey,
|
||||
ECPublicKey theirRatchetKey, Optional<ECPublicKey> theirOneTimePreKey)
|
||||
{
|
||||
this.ourIdentityKey = ourIdentityKey;
|
||||
this.ourBaseKey = ourBaseKey;
|
||||
this.theirIdentityKey = theirIdentityKey;
|
||||
this.theirSignedPreKey = theirSignedPreKey;
|
||||
this.theirRatchetKey = theirRatchetKey;
|
||||
this.theirOneTimePreKey = theirOneTimePreKey;
|
||||
|
||||
if (ourIdentityKey == null || ourBaseKey == null || theirIdentityKey == null ||
|
||||
theirSignedPreKey == null || theirRatchetKey == null || theirOneTimePreKey == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Null values!");
|
||||
}
|
||||
}
|
||||
|
||||
public IdentityKeyPair getOurIdentityKey() {
|
||||
return ourIdentityKey;
|
||||
}
|
||||
|
||||
public ECKeyPair getOurBaseKey() {
|
||||
return ourBaseKey;
|
||||
}
|
||||
|
||||
public IdentityKey getTheirIdentityKey() {
|
||||
return theirIdentityKey;
|
||||
}
|
||||
|
||||
public ECPublicKey getTheirSignedPreKey() {
|
||||
return theirSignedPreKey;
|
||||
}
|
||||
|
||||
public Optional<ECPublicKey> getTheirOneTimePreKey() {
|
||||
return theirOneTimePreKey;
|
||||
}
|
||||
|
||||
public static Builder newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public ECPublicKey getTheirRatchetKey() {
|
||||
return theirRatchetKey;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private IdentityKeyPair ourIdentityKey;
|
||||
private ECKeyPair ourBaseKey;
|
||||
|
||||
private IdentityKey theirIdentityKey;
|
||||
private ECPublicKey theirSignedPreKey;
|
||||
private ECPublicKey theirRatchetKey;
|
||||
private Optional<ECPublicKey> theirOneTimePreKey;
|
||||
|
||||
public Builder setOurIdentityKey(IdentityKeyPair ourIdentityKey) {
|
||||
this.ourIdentityKey = ourIdentityKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOurBaseKey(ECKeyPair ourBaseKey) {
|
||||
this.ourBaseKey = ourBaseKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTheirRatchetKey(ECPublicKey theirRatchetKey) {
|
||||
this.theirRatchetKey = theirRatchetKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTheirIdentityKey(IdentityKey theirIdentityKey) {
|
||||
this.theirIdentityKey = theirIdentityKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTheirSignedPreKey(ECPublicKey theirSignedPreKey) {
|
||||
this.theirSignedPreKey = theirSignedPreKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTheirOneTimePreKey(Optional<ECPublicKey> theirOneTimePreKey) {
|
||||
this.theirOneTimePreKey = theirOneTimePreKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AliceSignalProtocolParameters create() {
|
||||
return new AliceSignalProtocolParameters(ourIdentityKey, ourBaseKey, theirIdentityKey,
|
||||
theirSignedPreKey, theirRatchetKey, theirOneTimePreKey);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.ratchet;
|
||||
|
||||
import org.session.libsignal.libsignal.IdentityKey;
|
||||
import org.session.libsignal.libsignal.IdentityKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
|
||||
public class BobSignalProtocolParameters {
|
||||
|
||||
private final IdentityKeyPair ourIdentityKey;
|
||||
private final ECKeyPair ourSignedPreKey;
|
||||
private final Optional<ECKeyPair> ourOneTimePreKey;
|
||||
private final ECKeyPair ourRatchetKey;
|
||||
|
||||
private final IdentityKey theirIdentityKey;
|
||||
private final ECPublicKey theirBaseKey;
|
||||
|
||||
BobSignalProtocolParameters(IdentityKeyPair ourIdentityKey, ECKeyPair ourSignedPreKey,
|
||||
ECKeyPair ourRatchetKey, Optional<ECKeyPair> ourOneTimePreKey,
|
||||
IdentityKey theirIdentityKey, ECPublicKey theirBaseKey)
|
||||
{
|
||||
this.ourIdentityKey = ourIdentityKey;
|
||||
this.ourSignedPreKey = ourSignedPreKey;
|
||||
this.ourRatchetKey = ourRatchetKey;
|
||||
this.ourOneTimePreKey = ourOneTimePreKey;
|
||||
this.theirIdentityKey = theirIdentityKey;
|
||||
this.theirBaseKey = theirBaseKey;
|
||||
|
||||
if (ourIdentityKey == null || ourSignedPreKey == null || ourRatchetKey == null ||
|
||||
ourOneTimePreKey == null || theirIdentityKey == null || theirBaseKey == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Null value!");
|
||||
}
|
||||
}
|
||||
|
||||
public IdentityKeyPair getOurIdentityKey() {
|
||||
return ourIdentityKey;
|
||||
}
|
||||
|
||||
public ECKeyPair getOurSignedPreKey() {
|
||||
return ourSignedPreKey;
|
||||
}
|
||||
|
||||
public Optional<ECKeyPair> getOurOneTimePreKey() {
|
||||
return ourOneTimePreKey;
|
||||
}
|
||||
|
||||
public IdentityKey getTheirIdentityKey() {
|
||||
return theirIdentityKey;
|
||||
}
|
||||
|
||||
public ECPublicKey getTheirBaseKey() {
|
||||
return theirBaseKey;
|
||||
}
|
||||
|
||||
public static Builder newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public ECKeyPair getOurRatchetKey() {
|
||||
return ourRatchetKey;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private IdentityKeyPair ourIdentityKey;
|
||||
private ECKeyPair ourSignedPreKey;
|
||||
private Optional<ECKeyPair> ourOneTimePreKey;
|
||||
private ECKeyPair ourRatchetKey;
|
||||
|
||||
private IdentityKey theirIdentityKey;
|
||||
private ECPublicKey theirBaseKey;
|
||||
|
||||
public Builder setOurIdentityKey(IdentityKeyPair ourIdentityKey) {
|
||||
this.ourIdentityKey = ourIdentityKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOurSignedPreKey(ECKeyPair ourSignedPreKey) {
|
||||
this.ourSignedPreKey = ourSignedPreKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOurOneTimePreKey(Optional<ECKeyPair> ourOneTimePreKey) {
|
||||
this.ourOneTimePreKey = ourOneTimePreKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTheirIdentityKey(IdentityKey theirIdentityKey) {
|
||||
this.theirIdentityKey = theirIdentityKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTheirBaseKey(ECPublicKey theirBaseKey) {
|
||||
this.theirBaseKey = theirBaseKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOurRatchetKey(ECKeyPair ourRatchetKey) {
|
||||
this.ourRatchetKey = ourRatchetKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BobSignalProtocolParameters create() {
|
||||
return new BobSignalProtocolParameters(ourIdentityKey, ourSignedPreKey, ourRatchetKey,
|
||||
ourOneTimePreKey, theirIdentityKey, theirBaseKey);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.ratchet;
|
||||
|
||||
|
||||
import org.session.libsignal.libsignal.kdf.DerivedMessageSecrets;
|
||||
import org.session.libsignal.libsignal.kdf.HKDF;
|
||||
import org.session.libsignal.libsignal.ratchet.MessageKeys;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class ChainKey {
|
||||
|
||||
private static final byte[] MESSAGE_KEY_SEED = {0x01};
|
||||
private static final byte[] CHAIN_KEY_SEED = {0x02};
|
||||
|
||||
private final HKDF kdf;
|
||||
private final byte[] key;
|
||||
private final int index;
|
||||
|
||||
public ChainKey(HKDF kdf, byte[] key, int index) {
|
||||
this.kdf = kdf;
|
||||
this.key = key;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public byte[] getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public ChainKey getNextChainKey() {
|
||||
byte[] nextKey = getBaseMaterial(CHAIN_KEY_SEED);
|
||||
return new ChainKey(kdf, nextKey, index + 1);
|
||||
}
|
||||
|
||||
public MessageKeys getMessageKeys() {
|
||||
byte[] inputKeyMaterial = getBaseMaterial(MESSAGE_KEY_SEED);
|
||||
byte[] keyMaterialBytes = kdf.deriveSecrets(inputKeyMaterial, "WhisperMessageKeys".getBytes(), DerivedMessageSecrets.SIZE);
|
||||
DerivedMessageSecrets keyMaterial = new DerivedMessageSecrets(keyMaterialBytes);
|
||||
|
||||
return new MessageKeys(keyMaterial.getCipherKey(), keyMaterial.getMacKey(), keyMaterial.getIv(), index);
|
||||
}
|
||||
|
||||
private byte[] getBaseMaterial(byte[] seed) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(key, "HmacSHA256"));
|
||||
|
||||
return mac.doFinal(seed);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.ratchet;
|
||||
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class MessageKeys {
|
||||
|
||||
private final SecretKeySpec cipherKey;
|
||||
private final SecretKeySpec macKey;
|
||||
private final IvParameterSpec iv;
|
||||
private final int counter;
|
||||
|
||||
public MessageKeys(SecretKeySpec cipherKey, SecretKeySpec macKey, IvParameterSpec iv, int counter) {
|
||||
this.cipherKey = cipherKey;
|
||||
this.macKey = macKey;
|
||||
this.iv = iv;
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
public SecretKeySpec getCipherKey() {
|
||||
return cipherKey;
|
||||
}
|
||||
|
||||
public SecretKeySpec getMacKey() {
|
||||
return macKey;
|
||||
}
|
||||
|
||||
public IvParameterSpec getIv() {
|
||||
return iv;
|
||||
}
|
||||
|
||||
public int getCounter() {
|
||||
return counter;
|
||||
}
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.ratchet;
|
||||
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.libsignal.kdf.HKDF;
|
||||
import org.session.libsignal.libsignal.kdf.HKDFv3;
|
||||
import org.session.libsignal.libsignal.protocol.CiphertextMessage;
|
||||
import org.session.libsignal.libsignal.ratchet.AliceSignalProtocolParameters;
|
||||
import org.session.libsignal.libsignal.ratchet.BobSignalProtocolParameters;
|
||||
import org.session.libsignal.libsignal.ratchet.SymmetricSignalProtocolParameters;
|
||||
import org.session.libsignal.libsignal.state.SessionState;
|
||||
import org.session.libsignal.libsignal.util.ByteUtil;
|
||||
import org.session.libsignal.libsignal.util.Pair;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class RatchetingSession {
|
||||
|
||||
public static void initializeSession(SessionState sessionState, SymmetricSignalProtocolParameters parameters)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
if (isAlice(parameters.getOurBaseKey().getPublicKey(), parameters.getTheirBaseKey())) {
|
||||
AliceSignalProtocolParameters.Builder aliceParameters = AliceSignalProtocolParameters.newBuilder();
|
||||
|
||||
aliceParameters.setOurBaseKey(parameters.getOurBaseKey())
|
||||
.setOurIdentityKey(parameters.getOurIdentityKey())
|
||||
.setTheirRatchetKey(parameters.getTheirRatchetKey())
|
||||
.setTheirIdentityKey(parameters.getTheirIdentityKey())
|
||||
.setTheirSignedPreKey(parameters.getTheirBaseKey())
|
||||
.setTheirOneTimePreKey(Optional.<ECPublicKey>absent());
|
||||
|
||||
RatchetingSession.initializeSession(sessionState, aliceParameters.create());
|
||||
} else {
|
||||
BobSignalProtocolParameters.Builder bobParameters = BobSignalProtocolParameters.newBuilder();
|
||||
|
||||
bobParameters.setOurIdentityKey(parameters.getOurIdentityKey())
|
||||
.setOurRatchetKey(parameters.getOurRatchetKey())
|
||||
.setOurSignedPreKey(parameters.getOurBaseKey())
|
||||
.setOurOneTimePreKey(Optional.<ECKeyPair>absent())
|
||||
.setTheirBaseKey(parameters.getTheirBaseKey())
|
||||
.setTheirIdentityKey(parameters.getTheirIdentityKey());
|
||||
|
||||
RatchetingSession.initializeSession(sessionState, bobParameters.create());
|
||||
}
|
||||
}
|
||||
|
||||
public static void initializeSession(SessionState sessionState, AliceSignalProtocolParameters parameters)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
try {
|
||||
sessionState.setSessionVersion(CiphertextMessage.CURRENT_VERSION);
|
||||
sessionState.setRemoteIdentityKey(parameters.getTheirIdentityKey());
|
||||
sessionState.setLocalIdentityKey(parameters.getOurIdentityKey().getPublicKey());
|
||||
|
||||
ECKeyPair sendingRatchetKey = Curve.generateKeyPair();
|
||||
ByteArrayOutputStream secrets = new ByteArrayOutputStream();
|
||||
|
||||
secrets.write(getDiscontinuityBytes());
|
||||
|
||||
secrets.write(Curve.calculateAgreement(parameters.getTheirSignedPreKey(),
|
||||
parameters.getOurIdentityKey().getPrivateKey()));
|
||||
secrets.write(Curve.calculateAgreement(parameters.getTheirIdentityKey().getPublicKey(),
|
||||
parameters.getOurBaseKey().getPrivateKey()));
|
||||
secrets.write(Curve.calculateAgreement(parameters.getTheirSignedPreKey(),
|
||||
parameters.getOurBaseKey().getPrivateKey()));
|
||||
|
||||
if (parameters.getTheirOneTimePreKey().isPresent()) {
|
||||
secrets.write(Curve.calculateAgreement(parameters.getTheirOneTimePreKey().get(),
|
||||
parameters.getOurBaseKey().getPrivateKey()));
|
||||
}
|
||||
|
||||
DerivedKeys derivedKeys = calculateDerivedKeys(secrets.toByteArray());
|
||||
Pair<RootKey, ChainKey> sendingChain = derivedKeys.getRootKey().createChain(parameters.getTheirRatchetKey(), sendingRatchetKey);
|
||||
|
||||
sessionState.addReceiverChain(parameters.getTheirRatchetKey(), derivedKeys.getChainKey());
|
||||
sessionState.setSenderChain(sendingRatchetKey, sendingChain.second());
|
||||
sessionState.setRootKey(sendingChain.first());
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void initializeSession(SessionState sessionState, BobSignalProtocolParameters parameters)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
|
||||
try {
|
||||
sessionState.setSessionVersion(CiphertextMessage.CURRENT_VERSION);
|
||||
sessionState.setRemoteIdentityKey(parameters.getTheirIdentityKey());
|
||||
sessionState.setLocalIdentityKey(parameters.getOurIdentityKey().getPublicKey());
|
||||
|
||||
ByteArrayOutputStream secrets = new ByteArrayOutputStream();
|
||||
|
||||
secrets.write(getDiscontinuityBytes());
|
||||
|
||||
secrets.write(Curve.calculateAgreement(parameters.getTheirIdentityKey().getPublicKey(),
|
||||
parameters.getOurSignedPreKey().getPrivateKey()));
|
||||
secrets.write(Curve.calculateAgreement(parameters.getTheirBaseKey(),
|
||||
parameters.getOurIdentityKey().getPrivateKey()));
|
||||
secrets.write(Curve.calculateAgreement(parameters.getTheirBaseKey(),
|
||||
parameters.getOurSignedPreKey().getPrivateKey()));
|
||||
|
||||
if (parameters.getOurOneTimePreKey().isPresent()) {
|
||||
secrets.write(Curve.calculateAgreement(parameters.getTheirBaseKey(),
|
||||
parameters.getOurOneTimePreKey().get().getPrivateKey()));
|
||||
}
|
||||
|
||||
DerivedKeys derivedKeys = calculateDerivedKeys(secrets.toByteArray());
|
||||
|
||||
sessionState.setSenderChain(parameters.getOurRatchetKey(), derivedKeys.getChainKey());
|
||||
sessionState.setRootKey(derivedKeys.getRootKey());
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] getDiscontinuityBytes() {
|
||||
byte[] discontinuity = new byte[32];
|
||||
Arrays.fill(discontinuity, (byte) 0xFF);
|
||||
return discontinuity;
|
||||
}
|
||||
|
||||
private static DerivedKeys calculateDerivedKeys(byte[] masterSecret) {
|
||||
HKDF kdf = new HKDFv3();
|
||||
byte[] derivedSecretBytes = kdf.deriveSecrets(masterSecret, "WhisperText".getBytes(), 64);
|
||||
byte[][] derivedSecrets = ByteUtil.split(derivedSecretBytes, 32, 32);
|
||||
|
||||
return new DerivedKeys(new RootKey(kdf, derivedSecrets[0]),
|
||||
new ChainKey(kdf, derivedSecrets[1], 0));
|
||||
}
|
||||
|
||||
private static boolean isAlice(ECPublicKey ourKey, ECPublicKey theirKey) {
|
||||
return ourKey.compareTo(theirKey) < 0;
|
||||
}
|
||||
|
||||
private static class DerivedKeys {
|
||||
private final RootKey rootKey;
|
||||
private final ChainKey chainKey;
|
||||
|
||||
private DerivedKeys(RootKey rootKey, ChainKey chainKey) {
|
||||
this.rootKey = rootKey;
|
||||
this.chainKey = chainKey;
|
||||
}
|
||||
|
||||
public RootKey getRootKey() {
|
||||
return rootKey;
|
||||
}
|
||||
|
||||
public ChainKey getChainKey() {
|
||||
return chainKey;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.ratchet;
|
||||
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.libsignal.kdf.DerivedRootSecrets;
|
||||
import org.session.libsignal.libsignal.kdf.HKDF;
|
||||
import org.session.libsignal.libsignal.ratchet.ChainKey;
|
||||
import org.session.libsignal.libsignal.util.ByteUtil;
|
||||
import org.session.libsignal.libsignal.util.Pair;
|
||||
|
||||
public class RootKey {
|
||||
|
||||
private final HKDF kdf;
|
||||
private final byte[] key;
|
||||
|
||||
public RootKey(HKDF kdf, byte[] key) {
|
||||
this.kdf = kdf;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public byte[] getKeyBytes() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public Pair<RootKey, ChainKey> createChain(ECPublicKey theirRatchetKey, ECKeyPair ourRatchetKey)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
byte[] sharedSecret = Curve.calculateAgreement(theirRatchetKey, ourRatchetKey.getPrivateKey());
|
||||
byte[] derivedSecretBytes = kdf.deriveSecrets(sharedSecret, key, "WhisperRatchet".getBytes(), DerivedRootSecrets.SIZE);
|
||||
DerivedRootSecrets derivedSecrets = new DerivedRootSecrets(derivedSecretBytes);
|
||||
|
||||
RootKey newRootKey = new RootKey(kdf, derivedSecrets.getRootKey());
|
||||
ChainKey newChainKey = new ChainKey(kdf, derivedSecrets.getChainKey(), 0);
|
||||
|
||||
return new Pair<RootKey, ChainKey>(newRootKey, newChainKey);
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.ratchet;
|
||||
|
||||
import org.session.libsignal.libsignal.IdentityKey;
|
||||
import org.session.libsignal.libsignal.IdentityKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
|
||||
public class SymmetricSignalProtocolParameters {
|
||||
|
||||
private final ECKeyPair ourBaseKey;
|
||||
private final ECKeyPair ourRatchetKey;
|
||||
private final IdentityKeyPair ourIdentityKey;
|
||||
|
||||
private final ECPublicKey theirBaseKey;
|
||||
private final ECPublicKey theirRatchetKey;
|
||||
private final IdentityKey theirIdentityKey;
|
||||
|
||||
SymmetricSignalProtocolParameters(ECKeyPair ourBaseKey, ECKeyPair ourRatchetKey,
|
||||
IdentityKeyPair ourIdentityKey, ECPublicKey theirBaseKey,
|
||||
ECPublicKey theirRatchetKey, IdentityKey theirIdentityKey)
|
||||
{
|
||||
this.ourBaseKey = ourBaseKey;
|
||||
this.ourRatchetKey = ourRatchetKey;
|
||||
this.ourIdentityKey = ourIdentityKey;
|
||||
this.theirBaseKey = theirBaseKey;
|
||||
this.theirRatchetKey = theirRatchetKey;
|
||||
this.theirIdentityKey = theirIdentityKey;
|
||||
|
||||
if (ourBaseKey == null || ourRatchetKey == null || ourIdentityKey == null ||
|
||||
theirBaseKey == null || theirRatchetKey == null || theirIdentityKey == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Null values!");
|
||||
}
|
||||
}
|
||||
|
||||
public ECKeyPair getOurBaseKey() {
|
||||
return ourBaseKey;
|
||||
}
|
||||
|
||||
public ECKeyPair getOurRatchetKey() {
|
||||
return ourRatchetKey;
|
||||
}
|
||||
|
||||
public IdentityKeyPair getOurIdentityKey() {
|
||||
return ourIdentityKey;
|
||||
}
|
||||
|
||||
public ECPublicKey getTheirBaseKey() {
|
||||
return theirBaseKey;
|
||||
}
|
||||
|
||||
public ECPublicKey getTheirRatchetKey() {
|
||||
return theirRatchetKey;
|
||||
}
|
||||
|
||||
public IdentityKey getTheirIdentityKey() {
|
||||
return theirIdentityKey;
|
||||
}
|
||||
|
||||
public static Builder newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private ECKeyPair ourBaseKey;
|
||||
private ECKeyPair ourRatchetKey;
|
||||
private IdentityKeyPair ourIdentityKey;
|
||||
|
||||
private ECPublicKey theirBaseKey;
|
||||
private ECPublicKey theirRatchetKey;
|
||||
private IdentityKey theirIdentityKey;
|
||||
|
||||
public Builder setOurBaseKey(ECKeyPair ourBaseKey) {
|
||||
this.ourBaseKey = ourBaseKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOurRatchetKey(ECKeyPair ourRatchetKey) {
|
||||
this.ourRatchetKey = ourRatchetKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOurIdentityKey(IdentityKeyPair ourIdentityKey) {
|
||||
this.ourIdentityKey = ourIdentityKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTheirBaseKey(ECPublicKey theirBaseKey) {
|
||||
this.theirBaseKey = theirBaseKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTheirRatchetKey(ECPublicKey theirRatchetKey) {
|
||||
this.theirRatchetKey = theirRatchetKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTheirIdentityKey(IdentityKey theirIdentityKey) {
|
||||
this.theirIdentityKey = theirIdentityKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SymmetricSignalProtocolParameters create() {
|
||||
return new SymmetricSignalProtocolParameters(ourBaseKey, ourRatchetKey, ourIdentityKey,
|
||||
theirBaseKey, theirRatchetKey, theirIdentityKey);
|
||||
}
|
||||
}
|
||||
}
|
@ -27,56 +27,4 @@ public interface IdentityKeyStore {
|
||||
*/
|
||||
public IdentityKeyPair getIdentityKeyPair();
|
||||
|
||||
/**
|
||||
* Return the local client's registration ID.
|
||||
* <p>
|
||||
* Clients should maintain a registration ID, a random number
|
||||
* between 1 and 16380 that's generated once at install time.
|
||||
*
|
||||
* @return the local client's registration ID.
|
||||
*/
|
||||
public int getLocalRegistrationId();
|
||||
|
||||
/**
|
||||
* Save a remote client's identity key
|
||||
* <p>
|
||||
* Store a remote client's identity key as trusted.
|
||||
*
|
||||
* @param address The address of the remote client.
|
||||
* @param identityKey The remote client's identity key.
|
||||
* @return True if the identity key replaces a previous identity, false if not
|
||||
*/
|
||||
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey);
|
||||
|
||||
|
||||
/**
|
||||
* Verify a remote client's identity key.
|
||||
* <p>
|
||||
* Determine whether a remote client's identity is trusted. Convention is
|
||||
* that the Signal Protocol is 'trust on first use.' This means that
|
||||
* an identity key is considered 'trusted' if there is no entry for the recipient
|
||||
* in the local store, or if it matches the saved key for a recipient in the local
|
||||
* store. Only if it mismatches an entry in the local store is it considered
|
||||
* 'untrusted.'
|
||||
*
|
||||
* Clients may wish to make a distinction as to how keys are trusted based on the
|
||||
* direction of travel. For instance, clients may wish to accept all 'incoming' identity
|
||||
* key changes, while only blocking identity key changes when sending a message.
|
||||
*
|
||||
* @param address The address of the remote client.
|
||||
* @param identityKey The identity key to verify.
|
||||
* @param direction The direction (sending or receiving) this identity is being used for.
|
||||
* @return true if trusted, false if untrusted.
|
||||
*/
|
||||
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction);
|
||||
|
||||
|
||||
/**
|
||||
* Return the saved public identity key for a remote client
|
||||
*
|
||||
* @param address The address of the remote client
|
||||
* @return The public identity key, or null if absent
|
||||
*/
|
||||
public IdentityKey getIdentity(SignalProtocolAddress address);
|
||||
|
||||
}
|
||||
|
@ -1,101 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.state;
|
||||
|
||||
import org.session.libsignal.libsignal.IdentityKey;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
|
||||
/**
|
||||
* A class that contains a remote PreKey and collection
|
||||
* of associated items.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class PreKeyBundle {
|
||||
|
||||
private int registrationId;
|
||||
|
||||
private int deviceId;
|
||||
|
||||
private int preKeyId;
|
||||
private ECPublicKey preKeyPublic;
|
||||
|
||||
private int signedPreKeyId;
|
||||
private ECPublicKey signedPreKeyPublic;
|
||||
private byte[] signedPreKeySignature;
|
||||
|
||||
private IdentityKey identityKey;
|
||||
|
||||
public PreKeyBundle(int registrationId, int deviceId, int preKeyId, ECPublicKey preKeyPublic,
|
||||
int signedPreKeyId, ECPublicKey signedPreKeyPublic, byte[] signedPreKeySignature,
|
||||
IdentityKey identityKey)
|
||||
{
|
||||
this.registrationId = registrationId;
|
||||
this.deviceId = deviceId;
|
||||
this.preKeyId = preKeyId;
|
||||
this.preKeyPublic = preKeyPublic;
|
||||
this.signedPreKeyId = signedPreKeyId;
|
||||
this.signedPreKeyPublic = signedPreKeyPublic;
|
||||
this.signedPreKeySignature = signedPreKeySignature;
|
||||
this.identityKey = identityKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the device ID this PreKey belongs to.
|
||||
*/
|
||||
public int getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the unique key ID for this PreKey.
|
||||
*/
|
||||
public int getPreKeyId() {
|
||||
return preKeyId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the public key for this PreKey.
|
||||
*/
|
||||
public ECPublicKey getPreKey() {
|
||||
return preKeyPublic;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the unique key ID for this signed prekey.
|
||||
*/
|
||||
public int getSignedPreKeyId() {
|
||||
return signedPreKeyId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the signed prekey for this PreKeyBundle.
|
||||
*/
|
||||
public ECPublicKey getSignedPreKey() {
|
||||
return signedPreKeyPublic;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the signature over the signed prekey.
|
||||
*/
|
||||
public byte[] getSignedPreKeySignature() {
|
||||
return signedPreKeySignature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link IdentityKey} of this PreKeys owner.
|
||||
*/
|
||||
public IdentityKey getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the registration ID associated with this PreKey.
|
||||
*/
|
||||
public int getRegistrationId() {
|
||||
return registrationId;
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.state;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.session.libsignal.libsignal.state.StorageProtos.PreKeyRecordStructure;
|
||||
|
||||
public class PreKeyRecord {
|
||||
|
||||
private PreKeyRecordStructure structure;
|
||||
|
||||
public PreKeyRecord(int id, ECKeyPair keyPair) {
|
||||
this.structure = PreKeyRecordStructure.newBuilder()
|
||||
.setId(id)
|
||||
.setPublicKey(ByteString.copyFrom(keyPair.getPublicKey()
|
||||
.serialize()))
|
||||
.setPrivateKey(ByteString.copyFrom(keyPair.getPrivateKey()
|
||||
.serialize()))
|
||||
.build();
|
||||
}
|
||||
|
||||
public PreKeyRecord(byte[] serialized) throws IOException {
|
||||
this.structure = PreKeyRecordStructure.parseFrom(serialized);
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return this.structure.getId();
|
||||
}
|
||||
|
||||
public ECKeyPair getKeyPair() {
|
||||
try {
|
||||
ECPublicKey publicKey = Curve.decodePoint(this.structure.getPublicKey().toByteArray(), 0);
|
||||
ECPrivateKey privateKey = Curve.decodePrivatePoint(this.structure.getPrivateKey().toByteArray());
|
||||
|
||||
return new ECKeyPair(publicKey, privateKey);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
return this.structure.toByteArray();
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.state;
|
||||
|
||||
import org.session.libsignal.libsignal.InvalidKeyIdException;
|
||||
import org.session.libsignal.libsignal.state.PreKeyRecord;
|
||||
|
||||
/**
|
||||
* An interface describing the local storage of {@link PreKeyRecord}s.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public interface PreKeyStore {
|
||||
|
||||
/**
|
||||
* Load a local PreKeyRecord.
|
||||
*
|
||||
* @param preKeyId the ID of the local PreKeyRecord.
|
||||
* @return the corresponding PreKeyRecord.
|
||||
* @throws InvalidKeyIdException when there is no corresponding PreKeyRecord.
|
||||
*/
|
||||
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException;
|
||||
|
||||
/**
|
||||
* Store a local PreKeyRecord.
|
||||
*
|
||||
* @param preKeyId the ID of the PreKeyRecord to store.
|
||||
* @param record the PreKeyRecord.
|
||||
*/
|
||||
public void storePreKey(int preKeyId, PreKeyRecord record);
|
||||
|
||||
/**
|
||||
* @param preKeyId A PreKeyRecord ID.
|
||||
* @return true if the store has a record for the preKeyId, otherwise false.
|
||||
*/
|
||||
public boolean containsPreKey(int preKeyId);
|
||||
|
||||
/**
|
||||
* Delete a PreKeyRecord from local storage.
|
||||
*
|
||||
* @param preKeyId The ID of the PreKeyRecord to remove.
|
||||
*/
|
||||
public void removePreKey(int preKeyId);
|
||||
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.state;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.session.libsignal.libsignal.state.StorageProtos.RecordStructure;
|
||||
import static org.session.libsignal.libsignal.state.StorageProtos.SessionStructure;
|
||||
|
||||
/**
|
||||
* A SessionRecord encapsulates the state of an ongoing session.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class SessionRecord {
|
||||
|
||||
private static final int ARCHIVED_STATES_MAX_LENGTH = 40;
|
||||
|
||||
private SessionState sessionState = new SessionState();
|
||||
private LinkedList<SessionState> previousStates = new LinkedList<SessionState>();
|
||||
private boolean fresh = false;
|
||||
|
||||
public SessionRecord() {
|
||||
this.fresh = true;
|
||||
}
|
||||
|
||||
public SessionRecord(SessionState sessionState) {
|
||||
this.sessionState = sessionState;
|
||||
this.fresh = false;
|
||||
}
|
||||
|
||||
public SessionRecord(byte[] serialized) throws IOException {
|
||||
RecordStructure record = RecordStructure.parseFrom(serialized);
|
||||
this.sessionState = new SessionState(record.getCurrentSession());
|
||||
this.fresh = false;
|
||||
|
||||
for (SessionStructure previousStructure : record.getPreviousSessionsList()) {
|
||||
previousStates.add(new SessionState(previousStructure));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasSessionState(int version, byte[] aliceBaseKey) {
|
||||
if (sessionState.getSessionVersion() == version &&
|
||||
Arrays.equals(aliceBaseKey, sessionState.getAliceBaseKey()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (SessionState state : previousStates) {
|
||||
if (state.getSessionVersion() == version &&
|
||||
Arrays.equals(aliceBaseKey, state.getAliceBaseKey()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public SessionState getSessionState() {
|
||||
return sessionState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of all currently maintained "previous" session states.
|
||||
*/
|
||||
public List<SessionState> getPreviousSessionStates() {
|
||||
return previousStates;
|
||||
}
|
||||
|
||||
public void removePreviousSessionStates() {
|
||||
previousStates.clear();
|
||||
}
|
||||
|
||||
public boolean isFresh() {
|
||||
return fresh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the current {@link SessionState} into the list of "previous" session states,
|
||||
* and replace the current {@link org.session.libsignal.libsignal.state.SessionState}
|
||||
* with a fresh reset instance.
|
||||
*/
|
||||
public void archiveCurrentState() {
|
||||
promoteState(new SessionState());
|
||||
}
|
||||
|
||||
public void promoteState(SessionState promotedState) {
|
||||
this.previousStates.addFirst(sessionState);
|
||||
this.sessionState = promotedState;
|
||||
|
||||
if (previousStates.size() > ARCHIVED_STATES_MAX_LENGTH) {
|
||||
previousStates.removeLast();
|
||||
}
|
||||
}
|
||||
|
||||
public void setState(SessionState sessionState) {
|
||||
this.sessionState = sessionState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a serialized version of the current SessionRecord.
|
||||
*/
|
||||
public byte[] serialize() {
|
||||
List<SessionStructure> previousStructures = new LinkedList<SessionStructure>();
|
||||
|
||||
for (SessionState previousState : previousStates) {
|
||||
previousStructures.add(previousState.getStructure());
|
||||
}
|
||||
|
||||
RecordStructure record = RecordStructure.newBuilder()
|
||||
.setCurrentSession(sessionState.getStructure())
|
||||
.addAllPreviousSessions(previousStructures)
|
||||
.build();
|
||||
|
||||
return record.toByteArray();
|
||||
}
|
||||
|
||||
}
|
@ -1,503 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
|
||||
package org.session.libsignal.libsignal.state;
|
||||
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.session.libsignal.libsignal.IdentityKey;
|
||||
import org.session.libsignal.libsignal.IdentityKeyPair;
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.libsignal.kdf.HKDF;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
import org.session.libsignal.libsignal.ratchet.ChainKey;
|
||||
import org.session.libsignal.libsignal.ratchet.MessageKeys;
|
||||
import org.session.libsignal.libsignal.ratchet.RootKey;
|
||||
import org.session.libsignal.libsignal.state.StorageProtos.SessionStructure.Chain;
|
||||
import org.session.libsignal.libsignal.state.StorageProtos.SessionStructure.PendingKeyExchange;
|
||||
import org.session.libsignal.libsignal.state.StorageProtos.SessionStructure.PendingPreKey;
|
||||
import org.session.libsignal.libsignal.util.Pair;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import static org.session.libsignal.libsignal.state.StorageProtos.SessionStructure;
|
||||
|
||||
public class SessionState {
|
||||
|
||||
private static final int MAX_MESSAGE_KEYS = 2000;
|
||||
|
||||
private SessionStructure sessionStructure;
|
||||
|
||||
public SessionState() {
|
||||
this.sessionStructure = SessionStructure.newBuilder().build();
|
||||
}
|
||||
|
||||
public SessionState(SessionStructure sessionStructure) {
|
||||
this.sessionStructure = sessionStructure;
|
||||
}
|
||||
|
||||
public SessionState(SessionState copy) {
|
||||
this.sessionStructure = copy.sessionStructure.toBuilder().build();
|
||||
}
|
||||
|
||||
public SessionStructure getStructure() {
|
||||
return sessionStructure;
|
||||
}
|
||||
|
||||
public byte[] getAliceBaseKey() {
|
||||
return this.sessionStructure.getAliceBaseKey().toByteArray();
|
||||
}
|
||||
|
||||
public void setAliceBaseKey(byte[] aliceBaseKey) {
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.setAliceBaseKey(ByteString.copyFrom(aliceBaseKey))
|
||||
.build();
|
||||
}
|
||||
|
||||
public void setSessionVersion(int version) {
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.setSessionVersion(version)
|
||||
.build();
|
||||
}
|
||||
|
||||
public int getSessionVersion() {
|
||||
int sessionVersion = this.sessionStructure.getSessionVersion();
|
||||
|
||||
if (sessionVersion == 0) return 2;
|
||||
else return sessionVersion;
|
||||
}
|
||||
|
||||
public void setRemoteIdentityKey(IdentityKey identityKey) {
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.setRemoteIdentityPublic(ByteString.copyFrom(identityKey.serialize()))
|
||||
.build();
|
||||
}
|
||||
|
||||
public void setLocalIdentityKey(IdentityKey identityKey) {
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.setLocalIdentityPublic(ByteString.copyFrom(identityKey.serialize()))
|
||||
.build();
|
||||
}
|
||||
|
||||
public IdentityKey getRemoteIdentityKey() {
|
||||
try {
|
||||
if (!this.sessionStructure.hasRemoteIdentityPublic()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new IdentityKey(this.sessionStructure.getRemoteIdentityPublic().toByteArray(), 0);
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.w("SessionRecordV2", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public IdentityKey getLocalIdentityKey() {
|
||||
try {
|
||||
return new IdentityKey(this.sessionStructure.getLocalIdentityPublic().toByteArray(), 0);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public int getPreviousCounter() {
|
||||
return sessionStructure.getPreviousCounter();
|
||||
}
|
||||
|
||||
public void setPreviousCounter(int previousCounter) {
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.setPreviousCounter(previousCounter)
|
||||
.build();
|
||||
}
|
||||
|
||||
public RootKey getRootKey() {
|
||||
return new RootKey(HKDF.createFor(getSessionVersion()),
|
||||
this.sessionStructure.getRootKey().toByteArray());
|
||||
}
|
||||
|
||||
public void setRootKey(RootKey rootKey) {
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.setRootKey(ByteString.copyFrom(rootKey.getKeyBytes()))
|
||||
.build();
|
||||
}
|
||||
|
||||
public ECPublicKey getSenderRatchetKey() {
|
||||
try {
|
||||
return Curve.decodePoint(sessionStructure.getSenderChain().getSenderRatchetKey().toByteArray(), 0);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public ECKeyPair getSenderRatchetKeyPair() {
|
||||
ECPublicKey publicKey = getSenderRatchetKey();
|
||||
ECPrivateKey privateKey = Curve.decodePrivatePoint(sessionStructure.getSenderChain()
|
||||
.getSenderRatchetKeyPrivate()
|
||||
.toByteArray());
|
||||
|
||||
return new ECKeyPair(publicKey, privateKey);
|
||||
}
|
||||
|
||||
public boolean hasReceiverChain(ECPublicKey senderEphemeral) {
|
||||
return getReceiverChain(senderEphemeral) != null;
|
||||
}
|
||||
|
||||
public boolean hasSenderChain() {
|
||||
return sessionStructure.hasSenderChain();
|
||||
}
|
||||
|
||||
private Pair<Chain,Integer> getReceiverChain(ECPublicKey senderEphemeral) {
|
||||
List<Chain> receiverChains = sessionStructure.getReceiverChainsList();
|
||||
int index = 0;
|
||||
|
||||
for (Chain receiverChain : receiverChains) {
|
||||
try {
|
||||
ECPublicKey chainSenderRatchetKey = Curve.decodePoint(receiverChain.getSenderRatchetKey().toByteArray(), 0);
|
||||
|
||||
if (chainSenderRatchetKey.equals(senderEphemeral)) {
|
||||
return new Pair<Chain, Integer>(receiverChain,index);
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.w("SessionRecordV2", e);
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ChainKey getReceiverChainKey(ECPublicKey senderEphemeral) {
|
||||
Pair<Chain,Integer> receiverChainAndIndex = getReceiverChain(senderEphemeral);
|
||||
Chain receiverChain = receiverChainAndIndex.first();
|
||||
|
||||
if (receiverChain == null) {
|
||||
return null;
|
||||
} else {
|
||||
return new ChainKey(HKDF.createFor(getSessionVersion()),
|
||||
receiverChain.getChainKey().getKey().toByteArray(),
|
||||
receiverChain.getChainKey().getIndex());
|
||||
}
|
||||
}
|
||||
|
||||
public void addReceiverChain(ECPublicKey senderRatchetKey, ChainKey chainKey) {
|
||||
Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder()
|
||||
.setKey(ByteString.copyFrom(chainKey.getKey()))
|
||||
.setIndex(chainKey.getIndex())
|
||||
.build();
|
||||
|
||||
Chain chain = Chain.newBuilder()
|
||||
.setChainKey(chainKeyStructure)
|
||||
.setSenderRatchetKey(ByteString.copyFrom(senderRatchetKey.serialize()))
|
||||
.build();
|
||||
|
||||
this.sessionStructure = this.sessionStructure.toBuilder().addReceiverChains(chain).build();
|
||||
|
||||
if (this.sessionStructure.getReceiverChainsList().size() > 5) {
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.removeReceiverChains(0)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
public void setSenderChain(ECKeyPair senderRatchetKeyPair, ChainKey chainKey) {
|
||||
Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder()
|
||||
.setKey(ByteString.copyFrom(chainKey.getKey()))
|
||||
.setIndex(chainKey.getIndex())
|
||||
.build();
|
||||
|
||||
Chain senderChain = Chain.newBuilder()
|
||||
.setSenderRatchetKey(ByteString.copyFrom(senderRatchetKeyPair.getPublicKey().serialize()))
|
||||
.setSenderRatchetKeyPrivate(ByteString.copyFrom(senderRatchetKeyPair.getPrivateKey().serialize()))
|
||||
.setChainKey(chainKeyStructure)
|
||||
.build();
|
||||
|
||||
this.sessionStructure = this.sessionStructure.toBuilder().setSenderChain(senderChain).build();
|
||||
}
|
||||
|
||||
public ChainKey getSenderChainKey() {
|
||||
Chain.ChainKey chainKeyStructure = sessionStructure.getSenderChain().getChainKey();
|
||||
return new ChainKey(HKDF.createFor(getSessionVersion()),
|
||||
chainKeyStructure.getKey().toByteArray(), chainKeyStructure.getIndex());
|
||||
}
|
||||
|
||||
|
||||
public void setSenderChainKey(ChainKey nextChainKey) {
|
||||
Chain.ChainKey chainKey = Chain.ChainKey.newBuilder()
|
||||
.setKey(ByteString.copyFrom(nextChainKey.getKey()))
|
||||
.setIndex(nextChainKey.getIndex())
|
||||
.build();
|
||||
|
||||
Chain chain = sessionStructure.getSenderChain().toBuilder()
|
||||
.setChainKey(chainKey).build();
|
||||
|
||||
this.sessionStructure = this.sessionStructure.toBuilder().setSenderChain(chain).build();
|
||||
}
|
||||
|
||||
public boolean hasMessageKeys(ECPublicKey senderEphemeral, int counter) {
|
||||
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
|
||||
Chain chain = chainAndIndex.first();
|
||||
|
||||
if (chain == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Chain.MessageKey> messageKeyList = chain.getMessageKeysList();
|
||||
|
||||
for (Chain.MessageKey messageKey : messageKeyList) {
|
||||
if (messageKey.getIndex() == counter) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public MessageKeys removeMessageKeys(ECPublicKey senderEphemeral, int counter) {
|
||||
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
|
||||
Chain chain = chainAndIndex.first();
|
||||
|
||||
if (chain == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Chain.MessageKey> messageKeyList = new LinkedList<Chain.MessageKey>(chain.getMessageKeysList());
|
||||
Iterator<Chain.MessageKey> messageKeyIterator = messageKeyList.iterator();
|
||||
MessageKeys result = null;
|
||||
|
||||
while (messageKeyIterator.hasNext()) {
|
||||
Chain.MessageKey messageKey = messageKeyIterator.next();
|
||||
|
||||
if (messageKey.getIndex() == counter) {
|
||||
result = new MessageKeys(new SecretKeySpec(messageKey.getCipherKey().toByteArray(), "AES"),
|
||||
new SecretKeySpec(messageKey.getMacKey().toByteArray(), "HmacSHA256"),
|
||||
new IvParameterSpec(messageKey.getIv().toByteArray()),
|
||||
messageKey.getIndex());
|
||||
|
||||
messageKeyIterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Chain updatedChain = chain.toBuilder().clearMessageKeys()
|
||||
.addAllMessageKeys(messageKeyList)
|
||||
.build();
|
||||
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.setReceiverChains(chainAndIndex.second(), updatedChain)
|
||||
.build();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setMessageKeys(ECPublicKey senderEphemeral, MessageKeys messageKeys) {
|
||||
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
|
||||
Chain chain = chainAndIndex.first();
|
||||
Chain.MessageKey messageKeyStructure = Chain.MessageKey.newBuilder()
|
||||
.setCipherKey(ByteString.copyFrom(messageKeys.getCipherKey().getEncoded()))
|
||||
.setMacKey(ByteString.copyFrom(messageKeys.getMacKey().getEncoded()))
|
||||
.setIndex(messageKeys.getCounter())
|
||||
.setIv(ByteString.copyFrom(messageKeys.getIv().getIV()))
|
||||
.build();
|
||||
|
||||
Chain.Builder updatedChain = chain.toBuilder().addMessageKeys(messageKeyStructure);
|
||||
|
||||
if (updatedChain.getMessageKeysCount() > MAX_MESSAGE_KEYS) {
|
||||
updatedChain.removeMessageKeys(0);
|
||||
}
|
||||
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.setReceiverChains(chainAndIndex.second(),
|
||||
updatedChain.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
public void setReceiverChainKey(ECPublicKey senderEphemeral, ChainKey chainKey) {
|
||||
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
|
||||
Chain chain = chainAndIndex.first();
|
||||
|
||||
Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder()
|
||||
.setKey(ByteString.copyFrom(chainKey.getKey()))
|
||||
.setIndex(chainKey.getIndex())
|
||||
.build();
|
||||
|
||||
Chain updatedChain = chain.toBuilder().setChainKey(chainKeyStructure).build();
|
||||
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.setReceiverChains(chainAndIndex.second(), updatedChain)
|
||||
.build();
|
||||
}
|
||||
|
||||
public void setPendingKeyExchange(int sequence,
|
||||
ECKeyPair ourBaseKey,
|
||||
ECKeyPair ourRatchetKey,
|
||||
IdentityKeyPair ourIdentityKey)
|
||||
{
|
||||
PendingKeyExchange structure =
|
||||
PendingKeyExchange.newBuilder()
|
||||
.setSequence(sequence)
|
||||
.setLocalBaseKey(ByteString.copyFrom(ourBaseKey.getPublicKey().serialize()))
|
||||
.setLocalBaseKeyPrivate(ByteString.copyFrom(ourBaseKey.getPrivateKey().serialize()))
|
||||
.setLocalRatchetKey(ByteString.copyFrom(ourRatchetKey.getPublicKey().serialize()))
|
||||
.setLocalRatchetKeyPrivate(ByteString.copyFrom(ourRatchetKey.getPrivateKey().serialize()))
|
||||
.setLocalIdentityKey(ByteString.copyFrom(ourIdentityKey.getPublicKey().serialize()))
|
||||
.setLocalIdentityKeyPrivate(ByteString.copyFrom(ourIdentityKey.getPrivateKey().serialize()))
|
||||
.build();
|
||||
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.setPendingKeyExchange(structure)
|
||||
.build();
|
||||
}
|
||||
|
||||
public int getPendingKeyExchangeSequence() {
|
||||
return sessionStructure.getPendingKeyExchange().getSequence();
|
||||
}
|
||||
|
||||
public ECKeyPair getPendingKeyExchangeBaseKey() throws InvalidKeyException {
|
||||
ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange()
|
||||
.getLocalBaseKey().toByteArray(), 0);
|
||||
|
||||
ECPrivateKey privateKey = Curve.decodePrivatePoint(sessionStructure.getPendingKeyExchange()
|
||||
.getLocalBaseKeyPrivate()
|
||||
.toByteArray());
|
||||
|
||||
return new ECKeyPair(publicKey, privateKey);
|
||||
}
|
||||
|
||||
public ECKeyPair getPendingKeyExchangeRatchetKey() throws InvalidKeyException {
|
||||
ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange()
|
||||
.getLocalRatchetKey().toByteArray(), 0);
|
||||
|
||||
ECPrivateKey privateKey = Curve.decodePrivatePoint(sessionStructure.getPendingKeyExchange()
|
||||
.getLocalRatchetKeyPrivate()
|
||||
.toByteArray());
|
||||
|
||||
return new ECKeyPair(publicKey, privateKey);
|
||||
}
|
||||
|
||||
public IdentityKeyPair getPendingKeyExchangeIdentityKey() throws InvalidKeyException {
|
||||
IdentityKey publicKey = new IdentityKey(sessionStructure.getPendingKeyExchange()
|
||||
.getLocalIdentityKey().toByteArray(), 0);
|
||||
|
||||
ECPrivateKey privateKey = Curve.decodePrivatePoint(sessionStructure.getPendingKeyExchange()
|
||||
.getLocalIdentityKeyPrivate()
|
||||
.toByteArray());
|
||||
|
||||
return new IdentityKeyPair(publicKey, privateKey);
|
||||
}
|
||||
|
||||
public boolean hasPendingKeyExchange() {
|
||||
return sessionStructure.hasPendingKeyExchange();
|
||||
}
|
||||
|
||||
public void setUnacknowledgedPreKeyMessage(Optional<Integer> preKeyId, int signedPreKeyId, ECPublicKey baseKey) {
|
||||
PendingPreKey.Builder pending = PendingPreKey.newBuilder()
|
||||
.setSignedPreKeyId(signedPreKeyId)
|
||||
.setBaseKey(ByteString.copyFrom(baseKey.serialize()));
|
||||
|
||||
if (preKeyId.isPresent()) {
|
||||
pending.setPreKeyId(preKeyId.get());
|
||||
}
|
||||
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.setPendingPreKey(pending.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
public boolean hasUnacknowledgedPreKeyMessage() {
|
||||
return this.sessionStructure.hasPendingPreKey();
|
||||
}
|
||||
|
||||
public UnacknowledgedPreKeyMessageItems getUnacknowledgedPreKeyMessageItems() {
|
||||
try {
|
||||
Optional<Integer> preKeyId;
|
||||
|
||||
if (sessionStructure.getPendingPreKey().hasPreKeyId()) {
|
||||
preKeyId = Optional.of(sessionStructure.getPendingPreKey().getPreKeyId());
|
||||
} else {
|
||||
preKeyId = Optional.absent();
|
||||
}
|
||||
|
||||
return
|
||||
new UnacknowledgedPreKeyMessageItems(preKeyId,
|
||||
sessionStructure.getPendingPreKey().getSignedPreKeyId(),
|
||||
Curve.decodePoint(sessionStructure.getPendingPreKey()
|
||||
.getBaseKey()
|
||||
.toByteArray(), 0));
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearUnacknowledgedPreKeyMessage() {
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.clearPendingPreKey()
|
||||
.build();
|
||||
}
|
||||
|
||||
public void setRemoteRegistrationId(int registrationId) {
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.setRemoteRegistrationId(registrationId)
|
||||
.build();
|
||||
}
|
||||
|
||||
public int getRemoteRegistrationId() {
|
||||
return this.sessionStructure.getRemoteRegistrationId();
|
||||
}
|
||||
|
||||
public void setLocalRegistrationId(int registrationId) {
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.setLocalRegistrationId(registrationId)
|
||||
.build();
|
||||
}
|
||||
|
||||
public int getLocalRegistrationId() {
|
||||
return this.sessionStructure.getLocalRegistrationId();
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
return sessionStructure.toByteArray();
|
||||
}
|
||||
|
||||
public static class UnacknowledgedPreKeyMessageItems {
|
||||
private final Optional<Integer> preKeyId;
|
||||
private final int signedPreKeyId;
|
||||
private final ECPublicKey baseKey;
|
||||
|
||||
public UnacknowledgedPreKeyMessageItems(Optional<Integer> preKeyId,
|
||||
int signedPreKeyId,
|
||||
ECPublicKey baseKey)
|
||||
{
|
||||
this.preKeyId = preKeyId;
|
||||
this.signedPreKeyId = signedPreKeyId;
|
||||
this.baseKey = baseKey;
|
||||
}
|
||||
|
||||
|
||||
public Optional<Integer> getPreKeyId() {
|
||||
return preKeyId;
|
||||
}
|
||||
|
||||
public int getSignedPreKeyId() {
|
||||
return signedPreKeyId;
|
||||
}
|
||||
|
||||
public ECPublicKey getBaseKey() {
|
||||
return baseKey;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.state;
|
||||
|
||||
import org.session.libsignal.libsignal.SignalProtocolAddress;
|
||||
import org.session.libsignal.libsignal.state.SessionRecord;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The interface to the durable store of session state information
|
||||
* for remote clients.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public interface SessionStore {
|
||||
|
||||
/**
|
||||
* Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple,
|
||||
* or a new SessionRecord if one does not currently exist.
|
||||
* <p>
|
||||
* It is important that implementations return a copy of the current durable information. The
|
||||
* returned SessionRecord may be modified, but those changes should not have an effect on the
|
||||
* durable session state (what is returned by subsequent calls to this method) without the
|
||||
* store method being called here first.
|
||||
*
|
||||
* @param address The name and device ID of the remote client.
|
||||
* @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or
|
||||
* a new SessionRecord if one does not currently exist.
|
||||
*/
|
||||
public SessionRecord loadSession(SignalProtocolAddress address);
|
||||
|
||||
/**
|
||||
* Returns all known devices with active sessions for a recipient
|
||||
*
|
||||
* @param name the name of the client.
|
||||
* @return all known sub-devices with active sessions.
|
||||
*/
|
||||
public List<Integer> getSubDeviceSessions(String name);
|
||||
|
||||
/**
|
||||
* Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple.
|
||||
* @param address the address of the remote client.
|
||||
* @param record the current SessionRecord for the remote client.
|
||||
*/
|
||||
public void storeSession(SignalProtocolAddress address, SessionRecord record);
|
||||
|
||||
/**
|
||||
* Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple.
|
||||
* @param address the address of the remote client.
|
||||
* @return true if a {@link SessionRecord} exists, false otherwise.
|
||||
*/
|
||||
public boolean containsSession(SignalProtocolAddress address);
|
||||
|
||||
/**
|
||||
* Remove a {@link SessionRecord} for a recipientId + deviceId tuple.
|
||||
*
|
||||
* @param address the address of the remote client.
|
||||
*/
|
||||
public void deleteSession(SignalProtocolAddress address);
|
||||
|
||||
/**
|
||||
* Remove the {@link SessionRecord}s corresponding to all devices of a recipientId.
|
||||
*
|
||||
* @param name the name of the remote client.
|
||||
*/
|
||||
public void deleteAllSessions(String name);
|
||||
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.state;
|
||||
|
||||
public interface SignalProtocolStore
|
||||
extends IdentityKeyStore, PreKeyStore, SessionStore, SignedPreKeyStore
|
||||
{
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.state;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.session.libsignal.libsignal.state.StorageProtos.SignedPreKeyRecordStructure;
|
||||
|
||||
public class SignedPreKeyRecord {
|
||||
|
||||
private SignedPreKeyRecordStructure structure;
|
||||
|
||||
public SignedPreKeyRecord(int id, long timestamp, ECKeyPair keyPair, byte[] signature) {
|
||||
this.structure = SignedPreKeyRecordStructure.newBuilder()
|
||||
.setId(id)
|
||||
.setPublicKey(ByteString.copyFrom(keyPair.getPublicKey()
|
||||
.serialize()))
|
||||
.setPrivateKey(ByteString.copyFrom(keyPair.getPrivateKey()
|
||||
.serialize()))
|
||||
.setSignature(ByteString.copyFrom(signature))
|
||||
.setTimestamp(timestamp)
|
||||
.build();
|
||||
}
|
||||
|
||||
public SignedPreKeyRecord(byte[] serialized) throws IOException {
|
||||
this.structure = SignedPreKeyRecordStructure.parseFrom(serialized);
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return this.structure.getId();
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return this.structure.getTimestamp();
|
||||
}
|
||||
|
||||
public ECKeyPair getKeyPair() {
|
||||
try {
|
||||
ECPublicKey publicKey = Curve.decodePoint(this.structure.getPublicKey().toByteArray(), 0);
|
||||
ECPrivateKey privateKey = Curve.decodePrivatePoint(this.structure.getPrivateKey().toByteArray());
|
||||
|
||||
return new ECKeyPair(publicKey, privateKey);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return this.structure.getSignature().toByteArray();
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
return this.structure.toByteArray();
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.state;
|
||||
|
||||
import org.session.libsignal.libsignal.InvalidKeyIdException;
|
||||
import org.session.libsignal.libsignal.state.SignedPreKeyRecord;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface SignedPreKeyStore {
|
||||
|
||||
|
||||
/**
|
||||
* Load a local SignedPreKeyRecord.
|
||||
*
|
||||
* @param signedPreKeyId the ID of the local SignedPreKeyRecord.
|
||||
* @return the corresponding SignedPreKeyRecord.
|
||||
* @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord.
|
||||
*/
|
||||
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException;
|
||||
|
||||
/**
|
||||
* Load all local SignedPreKeyRecords.
|
||||
*
|
||||
* @return All stored SignedPreKeyRecords.
|
||||
*/
|
||||
public List<SignedPreKeyRecord> loadSignedPreKeys();
|
||||
|
||||
/**
|
||||
* Store a local SignedPreKeyRecord.
|
||||
*
|
||||
* @param signedPreKeyId the ID of the SignedPreKeyRecord to store.
|
||||
* @param record the SignedPreKeyRecord.
|
||||
*/
|
||||
public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record);
|
||||
|
||||
/**
|
||||
* @param signedPreKeyId A SignedPreKeyRecord ID.
|
||||
* @return true if the store has a record for the signedPreKeyId, otherwise false.
|
||||
*/
|
||||
public boolean containsSignedPreKey(int signedPreKeyId);
|
||||
|
||||
/**
|
||||
* Delete a SignedPreKeyRecord from local storage.
|
||||
*
|
||||
* @param signedPreKeyId The ID of the SignedPreKeyRecord to remove.
|
||||
*/
|
||||
public void removeSignedPreKey(int signedPreKeyId);
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.state.impl;
|
||||
|
||||
import org.session.libsignal.libsignal.IdentityKey;
|
||||
import org.session.libsignal.libsignal.IdentityKeyPair;
|
||||
import org.session.libsignal.libsignal.SignalProtocolAddress;
|
||||
import org.session.libsignal.libsignal.state.IdentityKeyStore;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class InMemoryIdentityKeyStore implements IdentityKeyStore {
|
||||
|
||||
private final Map<SignalProtocolAddress, IdentityKey> trustedKeys = new HashMap<SignalProtocolAddress, IdentityKey>();
|
||||
|
||||
private final IdentityKeyPair identityKeyPair;
|
||||
private final int localRegistrationId;
|
||||
|
||||
public InMemoryIdentityKeyStore(IdentityKeyPair identityKeyPair, int localRegistrationId) {
|
||||
this.identityKeyPair = identityKeyPair;
|
||||
this.localRegistrationId = localRegistrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKeyPair getIdentityKeyPair() {
|
||||
return identityKeyPair;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocalRegistrationId() {
|
||||
return localRegistrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
|
||||
IdentityKey existing = trustedKeys.get(address);
|
||||
|
||||
if (!identityKey.equals(existing)) {
|
||||
trustedKeys.put(address, identityKey);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
|
||||
IdentityKey trusted = trustedKeys.get(address);
|
||||
return (trusted == null || trusted.equals(identityKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey getIdentity(SignalProtocolAddress address) {
|
||||
return trustedKeys.get(address);
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.state.impl;
|
||||
|
||||
import org.session.libsignal.libsignal.InvalidKeyIdException;
|
||||
import org.session.libsignal.libsignal.state.PreKeyRecord;
|
||||
import org.session.libsignal.libsignal.state.PreKeyStore;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class InMemoryPreKeyStore implements PreKeyStore {
|
||||
|
||||
private final Map<Integer, byte[]> store = new HashMap<Integer, byte[]>();
|
||||
|
||||
@Override
|
||||
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
|
||||
try {
|
||||
if (!store.containsKey(preKeyId)) {
|
||||
throw new InvalidKeyIdException("No such prekeyrecord!");
|
||||
}
|
||||
|
||||
return new PreKeyRecord(store.get(preKeyId));
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePreKey(int preKeyId, PreKeyRecord record) {
|
||||
store.put(preKeyId, record.serialize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsPreKey(int preKeyId) {
|
||||
return store.containsKey(preKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePreKey(int preKeyId) {
|
||||
store.remove(preKeyId);
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.state.impl;
|
||||
|
||||
import org.session.libsignal.libsignal.SignalProtocolAddress;
|
||||
import org.session.libsignal.libsignal.state.SessionRecord;
|
||||
import org.session.libsignal.libsignal.state.SessionStore;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class InMemorySessionStore implements SessionStore {
|
||||
|
||||
private Map<SignalProtocolAddress, byte[]> sessions = new HashMap<SignalProtocolAddress, byte[]>();
|
||||
|
||||
public InMemorySessionStore() {}
|
||||
|
||||
@Override
|
||||
public synchronized SessionRecord loadSession(SignalProtocolAddress remoteAddress) {
|
||||
try {
|
||||
if (containsSession(remoteAddress)) {
|
||||
return new SessionRecord(sessions.get(remoteAddress));
|
||||
} else {
|
||||
return new SessionRecord();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<Integer> getSubDeviceSessions(String name) {
|
||||
List<Integer> deviceIds = new LinkedList<Integer>();
|
||||
|
||||
for (SignalProtocolAddress key : sessions.keySet()) {
|
||||
if (key.getName().equals(name) &&
|
||||
key.getDeviceId() != 1)
|
||||
{
|
||||
deviceIds.add(key.getDeviceId());
|
||||
}
|
||||
}
|
||||
|
||||
return deviceIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void storeSession(SignalProtocolAddress address, SessionRecord record) {
|
||||
sessions.put(address, record.serialize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean containsSession(SignalProtocolAddress address) {
|
||||
return sessions.containsKey(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void deleteSession(SignalProtocolAddress address) {
|
||||
sessions.remove(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void deleteAllSessions(String name) {
|
||||
for (SignalProtocolAddress key : sessions.keySet()) {
|
||||
if (key.getName().equals(name)) {
|
||||
sessions.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.state.impl;
|
||||
|
||||
import org.session.libsignal.libsignal.SignalProtocolAddress;
|
||||
import org.session.libsignal.libsignal.IdentityKey;
|
||||
import org.session.libsignal.libsignal.IdentityKeyPair;
|
||||
import org.session.libsignal.libsignal.InvalidKeyIdException;
|
||||
import org.session.libsignal.libsignal.state.SignalProtocolStore;
|
||||
import org.session.libsignal.libsignal.state.PreKeyRecord;
|
||||
import org.session.libsignal.libsignal.state.SessionRecord;
|
||||
import org.session.libsignal.libsignal.state.SignedPreKeyRecord;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class InMemorySignalProtocolStore implements SignalProtocolStore {
|
||||
|
||||
private final InMemoryPreKeyStore preKeyStore = new InMemoryPreKeyStore();
|
||||
private final InMemorySessionStore sessionStore = new InMemorySessionStore();
|
||||
private final InMemorySignedPreKeyStore signedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
|
||||
private final InMemoryIdentityKeyStore identityKeyStore;
|
||||
|
||||
public InMemorySignalProtocolStore(IdentityKeyPair identityKeyPair, int registrationId) {
|
||||
this.identityKeyStore = new InMemoryIdentityKeyStore(identityKeyPair, registrationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKeyPair getIdentityKeyPair() {
|
||||
return identityKeyStore.getIdentityKeyPair();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocalRegistrationId() {
|
||||
return identityKeyStore.getLocalRegistrationId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
|
||||
return identityKeyStore.saveIdentity(address, identityKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
|
||||
return identityKeyStore.isTrustedIdentity(address, identityKey, direction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey getIdentity(SignalProtocolAddress address) {
|
||||
return identityKeyStore.getIdentity(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
|
||||
return preKeyStore.loadPreKey(preKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePreKey(int preKeyId, PreKeyRecord record) {
|
||||
preKeyStore.storePreKey(preKeyId, record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsPreKey(int preKeyId) {
|
||||
return preKeyStore.containsPreKey(preKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePreKey(int preKeyId) {
|
||||
preKeyStore.removePreKey(preKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionRecord loadSession(SignalProtocolAddress address) {
|
||||
return sessionStore.loadSession(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> getSubDeviceSessions(String name) {
|
||||
return sessionStore.getSubDeviceSessions(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeSession(SignalProtocolAddress address, SessionRecord record) {
|
||||
sessionStore.storeSession(address, record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSession(SignalProtocolAddress address) {
|
||||
return sessionStore.containsSession(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSession(SignalProtocolAddress address) {
|
||||
sessionStore.deleteSession(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAllSessions(String name) {
|
||||
sessionStore.deleteAllSessions(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
|
||||
return signedPreKeyStore.loadSignedPreKey(signedPreKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SignedPreKeyRecord> loadSignedPreKeys() {
|
||||
return signedPreKeyStore.loadSignedPreKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
|
||||
signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSignedPreKey(int signedPreKeyId) {
|
||||
return signedPreKeyStore.containsSignedPreKey(signedPreKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSignedPreKey(int signedPreKeyId) {
|
||||
signedPreKeyStore.removeSignedPreKey(signedPreKeyId);
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
package org.session.libsignal.libsignal.state.impl;
|
||||
|
||||
import org.session.libsignal.libsignal.InvalidKeyIdException;
|
||||
import org.session.libsignal.libsignal.state.SignedPreKeyRecord;
|
||||
import org.session.libsignal.libsignal.state.SignedPreKeyStore;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class InMemorySignedPreKeyStore implements SignedPreKeyStore {
|
||||
|
||||
private final Map<Integer, byte[]> store = new HashMap<Integer, byte[]>();
|
||||
|
||||
@Override
|
||||
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
|
||||
try {
|
||||
if (!store.containsKey(signedPreKeyId)) {
|
||||
throw new InvalidKeyIdException("No such signedprekeyrecord! " + signedPreKeyId);
|
||||
}
|
||||
|
||||
return new SignedPreKeyRecord(store.get(signedPreKeyId));
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SignedPreKeyRecord> loadSignedPreKeys() {
|
||||
try {
|
||||
List<SignedPreKeyRecord> results = new LinkedList<SignedPreKeyRecord>();
|
||||
|
||||
for (byte[] serialized : store.values()) {
|
||||
results.add(new SignedPreKeyRecord(serialized));
|
||||
}
|
||||
|
||||
return results;
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
|
||||
store.put(signedPreKeyId, record.serialize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSignedPreKey(int signedPreKeyId) {
|
||||
return store.containsKey(signedPreKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSignedPreKey(int signedPreKeyId) {
|
||||
store.remove(signedPreKeyId);
|
||||
}
|
||||
}
|
@ -10,13 +10,9 @@ import org.session.libsignal.libsignal.IdentityKeyPair;
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.state.PreKeyRecord;
|
||||
import org.session.libsignal.libsignal.state.SignedPreKeyRecord;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Helper class for generating keys of different types.
|
||||
@ -26,19 +22,6 @@ import java.util.List;
|
||||
public class KeyHelper {
|
||||
|
||||
private KeyHelper() {}
|
||||
|
||||
/**
|
||||
* Generate an identity key pair. Clients should only do this once,
|
||||
* at install time.
|
||||
*
|
||||
* @return the generated IdentityKeyPair.
|
||||
*/
|
||||
public static IdentityKeyPair generateIdentityKeyPair() {
|
||||
ECKeyPair keyPair = Curve.generateKeyPair();
|
||||
IdentityKey publicKey = new IdentityKey(keyPair.getPublicKey());
|
||||
return new IdentityKeyPair(publicKey, keyPair.getPrivateKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a registration ID. Clients should only do this once,
|
||||
* at install time.
|
||||
@ -59,79 +42,4 @@ public class KeyHelper {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static int getRandomSequence(int max) {
|
||||
try {
|
||||
return SecureRandom.getInstance("SHA1PRNG").nextInt(max);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a list of PreKeys. Clients should do this at install time, and
|
||||
* subsequently any time the list of PreKeys stored on the server runs low.
|
||||
* <p>
|
||||
* PreKey IDs are shorts, so they will eventually be repeated. Clients should
|
||||
* store PreKeys in a circular buffer, so that they are repeated as infrequently
|
||||
* as possible.
|
||||
*
|
||||
* @param start The starting PreKey ID, inclusive.
|
||||
* @param count The number of PreKeys to generate.
|
||||
* @return the list of generated PreKeyRecords.
|
||||
*/
|
||||
public static List<PreKeyRecord> generatePreKeys(int start, int count) {
|
||||
List<PreKeyRecord> results = new LinkedList<PreKeyRecord>();
|
||||
|
||||
start--;
|
||||
|
||||
for (int i=0;i<count;i++) {
|
||||
results.add(new PreKeyRecord(((start + i) % (0xFFFFFF-1)) + 1, Curve.generateKeyPair()));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a signed PreKey
|
||||
*
|
||||
* @param identityKeyPair The local client's identity key pair.
|
||||
* @param signedPreKeyId The PreKey id to assign the generated signed PreKey
|
||||
*
|
||||
* @return the generated signed PreKey
|
||||
* @throws InvalidKeyException when the provided identity key is invalid
|
||||
*/
|
||||
public static SignedPreKeyRecord generateSignedPreKey(IdentityKeyPair identityKeyPair, int signedPreKeyId)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
ECKeyPair keyPair = Curve.generateKeyPair();
|
||||
byte[] signature = Curve.calculateSignature(identityKeyPair.getPrivateKey(), keyPair.getPublicKey().serialize());
|
||||
|
||||
return new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature);
|
||||
}
|
||||
|
||||
|
||||
public static ECKeyPair generateSenderSigningKey() {
|
||||
return Curve.generateKeyPair();
|
||||
}
|
||||
|
||||
public static byte[] generateSenderKey() {
|
||||
try {
|
||||
byte[] key = new byte[32];
|
||||
SecureRandom.getInstance("SHA1PRNG").nextBytes(key);
|
||||
|
||||
return key;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static int generateSenderKeyId() {
|
||||
try {
|
||||
return SecureRandom.getInstance("SHA1PRNG").nextInt(Integer.MAX_VALUE);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,87 +0,0 @@
|
||||
package org.session.libsignal.metadata.protocol;
|
||||
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.session.libsignal.metadata.InvalidMetadataMessageException;
|
||||
import org.session.libsignal.metadata.InvalidMetadataVersionException;
|
||||
import org.session.libsignal.metadata.SignalProtos;
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.ecc.Curve;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.libsignal.util.ByteUtil;
|
||||
|
||||
public class UnidentifiedSenderMessage {
|
||||
|
||||
private static final int CIPHERTEXT_VERSION = 1;
|
||||
|
||||
private final int version;
|
||||
private final ECPublicKey ephemeral;
|
||||
private final byte[] encryptedStatic;
|
||||
private final byte[] encryptedMessage;
|
||||
private final byte[] serialized;
|
||||
|
||||
public UnidentifiedSenderMessage(byte[] serialized)
|
||||
throws InvalidMetadataMessageException, InvalidMetadataVersionException
|
||||
{
|
||||
try {
|
||||
this.version = ByteUtil.highBitsToInt(serialized[0]);
|
||||
|
||||
if (version > CIPHERTEXT_VERSION) {
|
||||
throw new InvalidMetadataVersionException("Unknown version: " + this.version);
|
||||
}
|
||||
|
||||
SignalProtos.UnidentifiedSenderMessage unidentifiedSenderMessage = SignalProtos.UnidentifiedSenderMessage.parseFrom(ByteString.copyFrom(serialized, 1, serialized.length - 1));
|
||||
|
||||
if (!unidentifiedSenderMessage.hasEphemeralPublic() ||
|
||||
!unidentifiedSenderMessage.hasEncryptedStatic() ||
|
||||
!unidentifiedSenderMessage.hasEncryptedMessage())
|
||||
{
|
||||
throw new InvalidMetadataMessageException("Missing fields");
|
||||
}
|
||||
|
||||
this.ephemeral = Curve.decodePoint(unidentifiedSenderMessage.getEphemeralPublic().toByteArray(), 0);
|
||||
this.encryptedStatic = unidentifiedSenderMessage.getEncryptedStatic().toByteArray();
|
||||
this.encryptedMessage = unidentifiedSenderMessage.getEncryptedMessage().toByteArray();
|
||||
this.serialized = serialized;
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new InvalidMetadataMessageException(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidMetadataMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public UnidentifiedSenderMessage(ECPublicKey ephemeral, byte[] encryptedStatic, byte[] encryptedMessage) {
|
||||
this.version = CIPHERTEXT_VERSION;
|
||||
this.ephemeral = ephemeral;
|
||||
this.encryptedStatic = encryptedStatic;
|
||||
this.encryptedMessage = encryptedMessage;
|
||||
|
||||
byte[] versionBytes = {ByteUtil.intsToByteHighAndLow(CIPHERTEXT_VERSION, CIPHERTEXT_VERSION)};
|
||||
byte[] messageBytes = SignalProtos.UnidentifiedSenderMessage.newBuilder()
|
||||
.setEncryptedMessage(ByteString.copyFrom(encryptedMessage))
|
||||
.setEncryptedStatic(ByteString.copyFrom(encryptedStatic))
|
||||
.setEphemeralPublic(ByteString.copyFrom(ephemeral.serialize()))
|
||||
.build()
|
||||
.toByteArray();
|
||||
|
||||
this.serialized = ByteUtil.combine(versionBytes, messageBytes);
|
||||
}
|
||||
|
||||
public ECPublicKey getEphemeral() {
|
||||
return ephemeral;
|
||||
}
|
||||
|
||||
public byte[] getEncryptedStatic() {
|
||||
return encryptedStatic;
|
||||
}
|
||||
|
||||
public byte[] getEncryptedMessage() {
|
||||
return encryptedMessage;
|
||||
}
|
||||
|
||||
public byte[] getSerialized() {
|
||||
return serialized;
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
package org.session.libsignal.metadata.protocol;
|
||||
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.session.libsignal.metadata.InvalidMetadataMessageException;
|
||||
import org.session.libsignal.metadata.SignalProtos;
|
||||
import org.session.libsignal.metadata.certificate.InvalidCertificateException;
|
||||
import org.session.libsignal.metadata.certificate.SenderCertificate;
|
||||
import org.session.libsignal.libsignal.protocol.CiphertextMessage;
|
||||
|
||||
public class UnidentifiedSenderMessageContent {
|
||||
|
||||
private final int type;
|
||||
private final SenderCertificate senderCertificate;
|
||||
private final byte[] content;
|
||||
private final byte[] serialized;
|
||||
|
||||
public UnidentifiedSenderMessageContent(byte[] serialized) throws InvalidMetadataMessageException, InvalidCertificateException {
|
||||
try {
|
||||
SignalProtos.UnidentifiedSenderMessage.Message message = SignalProtos.UnidentifiedSenderMessage.Message.parseFrom(serialized);
|
||||
|
||||
if (!message.hasType() || !message.hasSenderCertificate() || !message.hasContent()) {
|
||||
throw new InvalidMetadataMessageException("Missing fields");
|
||||
}
|
||||
|
||||
switch (message.getType()) {
|
||||
case MESSAGE: this.type = CiphertextMessage.WHISPER_TYPE; break;
|
||||
case PREKEY_MESSAGE: this.type = CiphertextMessage.PREKEY_TYPE; break;
|
||||
case FALLBACK_MESSAGE: this.type = CiphertextMessage.FALLBACK_MESSAGE_TYPE; break;
|
||||
default: throw new InvalidMetadataMessageException("Unknown type: " + message.getType().getNumber());
|
||||
}
|
||||
|
||||
this.senderCertificate = new SenderCertificate(message.getSenderCertificate().toByteArray());
|
||||
this.content = message.getContent().toByteArray();
|
||||
this.serialized = serialized;
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new InvalidMetadataMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public UnidentifiedSenderMessageContent(int type, SenderCertificate senderCertificate, byte[] content) {
|
||||
try {
|
||||
this.serialized = SignalProtos.UnidentifiedSenderMessage.Message.newBuilder()
|
||||
.setType(SignalProtos.UnidentifiedSenderMessage.Message.Type.valueOf(getProtoType(type)))
|
||||
.setSenderCertificate(SignalProtos.SenderCertificate.parseFrom(senderCertificate.getSerialized()))
|
||||
.setContent(ByteString.copyFrom(content))
|
||||
.build()
|
||||
.toByteArray();
|
||||
|
||||
this.type = type;
|
||||
this.senderCertificate = senderCertificate;
|
||||
this.content = content;
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public SenderCertificate getSenderCertificate() {
|
||||
return senderCertificate;
|
||||
}
|
||||
|
||||
public byte[] getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public byte[] getSerialized() {
|
||||
return serialized;
|
||||
}
|
||||
|
||||
private int getProtoType(int type) {
|
||||
switch (type) {
|
||||
case CiphertextMessage.WHISPER_TYPE: return SignalProtos.UnidentifiedSenderMessage.Message.Type.MESSAGE_VALUE;
|
||||
case CiphertextMessage.PREKEY_TYPE: return SignalProtos.UnidentifiedSenderMessage.Message.Type.PREKEY_MESSAGE_VALUE;
|
||||
case CiphertextMessage.FALLBACK_MESSAGE_TYPE: return SignalProtos.UnidentifiedSenderMessage.Message.Type.FALLBACK_MESSAGE_VALUE;
|
||||
default: throw new AssertionError(type);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,442 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
|
||||
package org.session.libsignal.service.api;
|
||||
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.whispersystems.curve25519.Curve25519;
|
||||
import org.whispersystems.curve25519.Curve25519KeyPair;
|
||||
import org.session.libsignal.libsignal.IdentityKey;
|
||||
import org.session.libsignal.libsignal.IdentityKeyPair;
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
import org.session.libsignal.libsignal.state.PreKeyRecord;
|
||||
import org.session.libsignal.libsignal.state.SignedPreKeyRecord;
|
||||
import org.session.libsignal.libsignal.util.Pair;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.session.libsignal.service.api.crypto.InvalidCiphertextException;
|
||||
import org.session.libsignal.service.api.crypto.ProfileCipher;
|
||||
import org.session.libsignal.service.api.crypto.ProfileCipherOutputStream;
|
||||
import org.session.libsignal.service.api.messages.calls.TurnServerInfo;
|
||||
import org.session.libsignal.service.api.push.ContactTokenDetails;
|
||||
import org.session.libsignal.service.api.push.SignedPreKeyEntity;
|
||||
import org.session.libsignal.service.api.util.CredentialsProvider;
|
||||
import org.session.libsignal.service.api.util.StreamDetails;
|
||||
import org.session.libsignal.service.internal.configuration.SignalServiceConfiguration;
|
||||
import org.session.libsignal.service.internal.contacts.crypto.ContactDiscoveryCipher;
|
||||
import org.session.libsignal.service.internal.contacts.crypto.Quote;
|
||||
import org.session.libsignal.service.internal.contacts.crypto.RemoteAttestation;
|
||||
import org.session.libsignal.service.internal.contacts.crypto.RemoteAttestationKeys;
|
||||
import org.session.libsignal.service.internal.contacts.crypto.UnauthenticatedQuoteException;
|
||||
import org.session.libsignal.service.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
import org.session.libsignal.service.internal.contacts.entities.DiscoveryRequest;
|
||||
import org.session.libsignal.service.internal.contacts.entities.DiscoveryResponse;
|
||||
import org.session.libsignal.service.internal.contacts.entities.RemoteAttestationRequest;
|
||||
import org.session.libsignal.service.internal.contacts.entities.RemoteAttestationResponse;
|
||||
import org.session.libsignal.service.internal.crypto.ProvisioningCipher;
|
||||
import org.session.libsignal.service.internal.push.ProfileAvatarData;
|
||||
import org.session.libsignal.service.internal.push.PushServiceSocket;
|
||||
import org.session.libsignal.service.internal.push.http.ProfileCipherOutputStreamFactory;
|
||||
import org.session.libsignal.utilities.Base64;
|
||||
import org.session.libsignal.service.internal.util.StaticCredentialsProvider;
|
||||
import org.session.libsignal.service.internal.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.session.libsignal.service.internal.push.ProvisioningProtos.ProvisionMessage;
|
||||
|
||||
/**
|
||||
* The main interface for creating, registering, and
|
||||
* managing a Signal Service account.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class SignalServiceAccountManager {
|
||||
|
||||
private static final String TAG = SignalServiceAccountManager.class.getSimpleName();
|
||||
|
||||
private final PushServiceSocket pushServiceSocket;
|
||||
private final String user;
|
||||
private final String userAgent;
|
||||
|
||||
/**
|
||||
* Construct a SignalServiceAccountManager.
|
||||
*
|
||||
* @param configuration The URL for the Signal Service.
|
||||
* @param user A Signal Service phone number.
|
||||
* @param password A Signal Service password.
|
||||
* @param userAgent A string which identifies the client software.
|
||||
*/
|
||||
public SignalServiceAccountManager(SignalServiceConfiguration configuration,
|
||||
String user, String password,
|
||||
String userAgent)
|
||||
{
|
||||
this(configuration, new StaticCredentialsProvider(user, password, null), userAgent);
|
||||
}
|
||||
|
||||
public SignalServiceAccountManager(SignalServiceConfiguration configuration,
|
||||
CredentialsProvider credentialsProvider,
|
||||
String userAgent)
|
||||
{
|
||||
this.pushServiceSocket = new PushServiceSocket(configuration, credentialsProvider, userAgent);
|
||||
this.user = credentialsProvider.getUser();
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
|
||||
public byte[] getSenderCertificate() throws IOException {
|
||||
return this.pushServiceSocket.getSenderCertificate();
|
||||
}
|
||||
|
||||
public void setPin(Optional<String> pin) throws IOException {
|
||||
if (pin.isPresent()) {
|
||||
this.pushServiceSocket.setPin(pin.get());
|
||||
} else {
|
||||
this.pushServiceSocket.removePin();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register/Unregister a Google Cloud Messaging registration ID.
|
||||
*
|
||||
* @param gcmRegistrationId The GCM id to register. A call with an absent value will unregister.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void setGcmId(Optional<String> gcmRegistrationId) throws IOException {
|
||||
if (gcmRegistrationId.isPresent()) {
|
||||
this.pushServiceSocket.registerGcmId(gcmRegistrationId.get());
|
||||
} else {
|
||||
this.pushServiceSocket.unregisterGcmId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request an SMS verification code. On success, the server will send
|
||||
* an SMS verification code to this Signal user.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void requestSmsVerificationCode(boolean androidSmsRetrieverSupported, Optional<String> captchaToken) throws IOException {
|
||||
this.pushServiceSocket.requestSmsVerificationCode(androidSmsRetrieverSupported, captchaToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a Voice verification code. On success, the server will
|
||||
* make a voice call to this Signal user.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void requestVoiceVerificationCode(Locale locale, Optional<String> captchaToken) throws IOException {
|
||||
this.pushServiceSocket.requestVoiceVerificationCode(locale, captchaToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a Signal Service account with a received SMS or voice verification code.
|
||||
*
|
||||
* @param verificationCode The verification code received via SMS or Voice
|
||||
* (see {@link #requestSmsVerificationCode} and
|
||||
* {@link #requestVoiceVerificationCode}).
|
||||
* @param signalingKey 52 random bytes. A 32 byte AES key and a 20 byte Hmac256 key,
|
||||
* concatenated.
|
||||
* @param signalProtocolRegistrationId A random 14-bit number that identifies this Signal install.
|
||||
* This value should remain consistent across registrations for the
|
||||
* same install, but probabilistically differ across registrations
|
||||
* for separate installs.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void verifyAccountWithCode(String verificationCode, String signalingKey, int signalProtocolRegistrationId, boolean fetchesMessages, String pin,
|
||||
byte[] unidentifiedAccessKey, boolean unrestrictedUnidentifiedAccess)
|
||||
throws IOException
|
||||
{
|
||||
this.pushServiceSocket.verifyAccountCode(verificationCode, signalingKey,
|
||||
signalProtocolRegistrationId,
|
||||
fetchesMessages, pin,
|
||||
unidentifiedAccessKey,
|
||||
unrestrictedUnidentifiedAccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh account attributes with server.
|
||||
*
|
||||
* @param signalingKey 52 random bytes. A 32 byte AES key and a 20 byte Hmac256 key, concatenated.
|
||||
* @param signalProtocolRegistrationId A random 14-bit number that identifies this Signal install.
|
||||
* This value should remain consistent across registrations for the same
|
||||
* install, but probabilistically differ across registrations for
|
||||
* separate installs.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void setAccountAttributes(String signalingKey, int signalProtocolRegistrationId, boolean fetchesMessages, String pin,
|
||||
byte[] unidentifiedAccessKey, boolean unrestrictedUnidentifiedAccess)
|
||||
throws IOException
|
||||
{
|
||||
this.pushServiceSocket.setAccountAttributes(signalingKey, signalProtocolRegistrationId, fetchesMessages, pin,
|
||||
unidentifiedAccessKey, unrestrictedUnidentifiedAccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an identity key, signed prekey, and list of one time prekeys
|
||||
* with the server.
|
||||
*
|
||||
* @param identityKey The client's long-term identity keypair.
|
||||
* @param signedPreKey The client's signed prekey.
|
||||
* @param oneTimePreKeys The client's list of one-time prekeys.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void setPreKeys(IdentityKey identityKey, SignedPreKeyRecord signedPreKey, List<PreKeyRecord> oneTimePreKeys)
|
||||
throws IOException
|
||||
{
|
||||
this.pushServiceSocket.registerPreKeys(identityKey, signedPreKey, oneTimePreKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The server's count of currently available (eg. unused) prekeys for this user.
|
||||
* @throws IOException
|
||||
*/
|
||||
public int getPreKeysCount() throws IOException {
|
||||
return this.pushServiceSocket.getAvailablePreKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the client's signed prekey.
|
||||
*
|
||||
* @param signedPreKey The client's new signed prekey.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void setSignedPreKey(SignedPreKeyRecord signedPreKey) throws IOException {
|
||||
this.pushServiceSocket.setCurrentSignedPreKey(signedPreKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The server's view of the client's current signed prekey.
|
||||
* @throws IOException
|
||||
*/
|
||||
public SignedPreKeyEntity getSignedPreKey() throws IOException {
|
||||
return this.pushServiceSocket.getCurrentSignedPreKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a contact is currently registered with the server.
|
||||
*
|
||||
* @param e164number The contact to check.
|
||||
* @return An optional ContactTokenDetails, present if registered, absent if not.
|
||||
* @throws IOException
|
||||
*/
|
||||
public Optional<ContactTokenDetails> getContact(String e164number) throws IOException {
|
||||
String contactToken = createDirectoryServerToken(e164number, true);
|
||||
ContactTokenDetails contactTokenDetails = this.pushServiceSocket.getContactTokenDetails(contactToken);
|
||||
|
||||
if (contactTokenDetails != null) {
|
||||
contactTokenDetails.setNumber(e164number);
|
||||
}
|
||||
|
||||
return Optional.fromNullable(contactTokenDetails);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks which contacts in a set are registered with the server.
|
||||
*
|
||||
* @param e164numbers The contacts to check.
|
||||
* @return A list of ContactTokenDetails for the registered users.
|
||||
* @throws IOException
|
||||
*/
|
||||
public List<ContactTokenDetails> getContacts(Set<String> e164numbers)
|
||||
throws IOException
|
||||
{
|
||||
Map<String, String> contactTokensMap = createDirectoryServerTokenMap(e164numbers);
|
||||
List<ContactTokenDetails> activeTokens = this.pushServiceSocket.retrieveDirectory(contactTokensMap.keySet());
|
||||
|
||||
for (ContactTokenDetails activeToken : activeTokens) {
|
||||
activeToken.setNumber(contactTokensMap.get(activeToken.getToken()));
|
||||
}
|
||||
|
||||
return activeTokens;
|
||||
}
|
||||
|
||||
public List<String> getRegisteredUsers(KeyStore iasKeyStore, Set<String> e164numbers, String mrenclave)
|
||||
throws IOException, Quote.InvalidQuoteFormatException, UnauthenticatedQuoteException, SignatureException, UnauthenticatedResponseException
|
||||
{
|
||||
try {
|
||||
String authorization = this.pushServiceSocket.getContactDiscoveryAuthorization();
|
||||
Curve25519 curve = Curve25519.getInstance(Curve25519.BEST);
|
||||
Curve25519KeyPair keyPair = curve.generateKeyPair();
|
||||
|
||||
ContactDiscoveryCipher cipher = new ContactDiscoveryCipher();
|
||||
RemoteAttestationRequest attestationRequest = new RemoteAttestationRequest(keyPair.getPublicKey());
|
||||
Pair<RemoteAttestationResponse, List<String>> attestationResponse = this.pushServiceSocket.getContactDiscoveryRemoteAttestation(authorization, attestationRequest, mrenclave);
|
||||
|
||||
RemoteAttestationKeys keys = new RemoteAttestationKeys(keyPair, attestationResponse.first().getServerEphemeralPublic(), attestationResponse.first().getServerStaticPublic());
|
||||
Quote quote = new Quote(attestationResponse.first().getQuote());
|
||||
byte[] requestId = cipher.getRequestId(keys, attestationResponse.first());
|
||||
|
||||
cipher.verifyServerQuote(quote, attestationResponse.first().getServerStaticPublic(), mrenclave);
|
||||
cipher.verifyIasSignature(iasKeyStore, attestationResponse.first().getCertificates(), attestationResponse.first().getSignatureBody(), attestationResponse.first().getSignature(), quote);
|
||||
|
||||
RemoteAttestation remoteAttestation = new RemoteAttestation(requestId, keys);
|
||||
List<String> addressBook = new LinkedList<String>();
|
||||
|
||||
for (String e164number : e164numbers) {
|
||||
addressBook.add(e164number.substring(1));
|
||||
}
|
||||
|
||||
DiscoveryRequest request = cipher.createDiscoveryRequest(addressBook, remoteAttestation);
|
||||
DiscoveryResponse response = this.pushServiceSocket.getContactDiscoveryRegisteredUsers(authorization, request, attestationResponse.second(), mrenclave);
|
||||
byte[] data = cipher.getDiscoveryResponseData(response, remoteAttestation);
|
||||
|
||||
Iterator<String> addressBookIterator = addressBook.iterator();
|
||||
List<String> results = new LinkedList<String>();
|
||||
|
||||
for (byte aData : data) {
|
||||
String candidate = addressBookIterator.next();
|
||||
|
||||
if (aData != 0) results.add('+' + candidate);
|
||||
}
|
||||
|
||||
return results;
|
||||
} catch (InvalidCiphertextException e) {
|
||||
throw new UnauthenticatedResponseException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void reportContactDiscoveryServiceMatch() {
|
||||
try {
|
||||
this.pushServiceSocket.reportContactDiscoveryServiceMatch();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Request to indicate a contact discovery result match failed. Ignoring.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void reportContactDiscoveryServiceMismatch() {
|
||||
try {
|
||||
this.pushServiceSocket.reportContactDiscoveryServiceMismatch();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Request to indicate a contact discovery result mismatch failed. Ignoring.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void reportContactDiscoveryServiceAttestationError(String reason) {
|
||||
try {
|
||||
this.pushServiceSocket.reportContactDiscoveryServiceAttestationError(reason);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Request to indicate a contact discovery attestation error failed. Ignoring.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void reportContactDiscoveryServiceUnexpectedError(String reason) {
|
||||
try {
|
||||
this.pushServiceSocket.reportContactDiscoveryServiceUnexpectedError(reason);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Request to indicate a contact discovery unexpected error failed. Ignoring.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getNewDeviceVerificationCode() throws IOException {
|
||||
return this.pushServiceSocket.getNewDeviceVerificationCode();
|
||||
}
|
||||
|
||||
public void addDevice(String deviceIdentifier,
|
||||
ECPublicKey deviceKey,
|
||||
IdentityKeyPair identityKeyPair,
|
||||
Optional<byte[]> profileKey,
|
||||
String code)
|
||||
throws InvalidKeyException, IOException
|
||||
{
|
||||
ProvisioningCipher cipher = new ProvisioningCipher(deviceKey);
|
||||
ProvisionMessage.Builder message = ProvisionMessage.newBuilder()
|
||||
.setIdentityKeyPublic(ByteString.copyFrom(identityKeyPair.getPublicKey().serialize()))
|
||||
.setIdentityKeyPrivate(ByteString.copyFrom(identityKeyPair.getPrivateKey().serialize()))
|
||||
.setNumber(user)
|
||||
.setProvisioningCode(code);
|
||||
|
||||
if (profileKey.isPresent()) {
|
||||
message.setProfileKey(ByteString.copyFrom(profileKey.get()));
|
||||
}
|
||||
|
||||
byte[] ciphertext = cipher.encrypt(message.build());
|
||||
this.pushServiceSocket.sendProvisioningMessage(deviceIdentifier, ciphertext);
|
||||
}
|
||||
|
||||
public void removeDevice(long deviceId) throws IOException {
|
||||
this.pushServiceSocket.removeDevice(deviceId);
|
||||
}
|
||||
|
||||
public TurnServerInfo getTurnServerInfo() throws IOException {
|
||||
return this.pushServiceSocket.getTurnServerInfo();
|
||||
}
|
||||
|
||||
public void setProfileName(byte[] key, String name)
|
||||
throws IOException
|
||||
{
|
||||
if (name == null) name = "";
|
||||
|
||||
String ciphertextName = Base64.encodeBytesWithoutPadding(new ProfileCipher(key).encryptName(name.getBytes("UTF-8"), ProfileCipher.NAME_PADDED_LENGTH));
|
||||
|
||||
this.pushServiceSocket.setProfileName(ciphertextName);
|
||||
}
|
||||
|
||||
public void setProfileAvatar(byte[] key, StreamDetails avatar)
|
||||
throws IOException
|
||||
{
|
||||
ProfileAvatarData profileAvatarData = null;
|
||||
|
||||
if (avatar != null) {
|
||||
profileAvatarData = new ProfileAvatarData(avatar.getStream(),
|
||||
ProfileCipherOutputStream.getCiphertextLength(avatar.getLength()),
|
||||
avatar.getContentType(),
|
||||
new ProfileCipherOutputStreamFactory(key));
|
||||
}
|
||||
|
||||
this.pushServiceSocket.setProfileAvatar(profileAvatarData);
|
||||
}
|
||||
|
||||
public void setSoTimeoutMillis(long soTimeoutMillis) {
|
||||
this.pushServiceSocket.setSoTimeoutMillis(soTimeoutMillis);
|
||||
}
|
||||
|
||||
public void cancelInFlightRequests() {
|
||||
this.pushServiceSocket.cancelInFlightRequests();
|
||||
}
|
||||
|
||||
private String createDirectoryServerToken(String e164number, boolean urlSafe) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA1");
|
||||
byte[] token = Util.trim(digest.digest(e164number.getBytes()), 10);
|
||||
String encoded = Base64.encodeBytesWithoutPadding(token);
|
||||
|
||||
if (urlSafe) return encoded.replace('+', '-').replace('/', '_');
|
||||
else return encoded;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> createDirectoryServerTokenMap(Collection<String> e164numbers) {
|
||||
Map<String, String> tokenMap = new HashMap<String, String>(e164numbers.size());
|
||||
|
||||
for (String number : e164numbers) {
|
||||
tokenMap.put(createDirectoryServerToken(number, false), number);
|
||||
}
|
||||
|
||||
return tokenMap;
|
||||
}
|
||||
|
||||
}
|
@ -108,12 +108,6 @@ public class SignalServiceMessageReceiver {
|
||||
return retrieveAttachment(pointer, destination, maxSizeBytes, null);
|
||||
}
|
||||
|
||||
public SignalServiceProfile retrieveProfile(SignalServiceAddress address, Optional<UnidentifiedAccess> unidentifiedAccess)
|
||||
throws IOException
|
||||
{
|
||||
return socket.retrieveProfile(address, unidentifiedAccess);
|
||||
}
|
||||
|
||||
public InputStream retrieveProfileAvatar(String path, File destination, byte[] profileKey, int maxSizeBytes)
|
||||
throws IOException
|
||||
{
|
||||
@ -209,10 +203,6 @@ public class SignalServiceMessageReceiver {
|
||||
return new SignalServiceMessagePipe(webSocket, Optional.of(credentialsProvider));
|
||||
}
|
||||
|
||||
public List<SignalServiceEnvelope> retrieveMessages() throws IOException {
|
||||
return retrieveMessages(new NullMessageReceivedCallback());
|
||||
}
|
||||
|
||||
public List<SignalServiceEnvelope> retrieveMessages(MessageReceivedCallback callback)
|
||||
throws IOException
|
||||
{
|
||||
|
@ -9,8 +9,8 @@ import com.google.protobuf.ByteString;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
||||
import org.session.libsignal.libsignal.state.IdentityKeyStore;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
import org.session.libsignal.libsignal.state.SignalProtocolStore;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.session.libsignal.service.api.crypto.AttachmentCipherOutputStream;
|
||||
import org.session.libsignal.service.api.crypto.UnidentifiedAccess;
|
||||
@ -29,12 +29,10 @@ import org.session.libsignal.service.api.push.SignalServiceAddress;
|
||||
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
|
||||
import org.session.libsignal.service.api.push.exceptions.UnregisteredUserException;
|
||||
import org.session.libsignal.service.api.util.CredentialsProvider;
|
||||
import org.session.libsignal.service.internal.configuration.SignalServiceConfiguration;
|
||||
import org.session.libsignal.service.internal.crypto.PaddingInputStream;
|
||||
import org.session.libsignal.service.internal.push.OutgoingPushMessage;
|
||||
import org.session.libsignal.service.internal.push.OutgoingPushMessageList;
|
||||
import org.session.libsignal.service.internal.push.PushAttachmentData;
|
||||
import org.session.libsignal.service.internal.push.PushServiceSocket;
|
||||
import org.session.libsignal.service.internal.push.PushTransportDetails;
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos;
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.AttachmentPointer;
|
||||
@ -62,7 +60,6 @@ import org.session.libsignal.service.loki.api.opengroups.PublicChatMessage;
|
||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol;
|
||||
import org.session.libsignal.service.loki.database.LokiMessageDatabaseProtocol;
|
||||
import org.session.libsignal.service.loki.database.LokiOpenGroupDatabaseProtocol;
|
||||
import org.session.libsignal.service.loki.database.LokiPreKeyBundleDatabaseProtocol;
|
||||
import org.session.libsignal.service.loki.database.LokiThreadDatabaseProtocol;
|
||||
import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol;
|
||||
import org.session.libsignal.service.loki.utilities.TTLUtilities;
|
||||
@ -94,7 +91,7 @@ public class SignalServiceMessageSender {
|
||||
|
||||
private static final String TAG = SignalServiceMessageSender.class.getSimpleName();
|
||||
|
||||
private final SignalProtocolStore store;
|
||||
private final IdentityKeyStore store;
|
||||
private final SignalServiceAddress localAddress;
|
||||
|
||||
private final AtomicReference<Optional<SignalServiceMessagePipe>> pipe;
|
||||
@ -118,7 +115,7 @@ public class SignalServiceMessageSender {
|
||||
* @param store The SignalProtocolStore.
|
||||
*/
|
||||
public SignalServiceMessageSender(String user, String password,
|
||||
SignalProtocolStore store,
|
||||
IdentityKeyStore store,
|
||||
Optional<SignalServiceMessagePipe> pipe,
|
||||
Optional<SignalServiceMessagePipe> unidentifiedPipe,
|
||||
String userPublicKey,
|
||||
@ -134,7 +131,7 @@ public class SignalServiceMessageSender {
|
||||
}
|
||||
|
||||
public SignalServiceMessageSender(CredentialsProvider credentialsProvider,
|
||||
SignalProtocolStore store,
|
||||
IdentityKeyStore store,
|
||||
Optional<SignalServiceMessagePipe> pipe,
|
||||
Optional<SignalServiceMessagePipe> unidentifiedPipe,
|
||||
String userPublicKey,
|
||||
|
@ -6,7 +6,6 @@
|
||||
|
||||
package org.session.libsignal.service.api.messages;
|
||||
|
||||
import org.session.libsignal.libsignal.state.PreKeyBundle;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.session.libsignal.service.api.messages.shared.SharedContact;
|
||||
import org.session.libsignal.service.api.push.SignalServiceAddress;
|
||||
@ -259,14 +258,10 @@ public class SignalServiceDataMessage {
|
||||
private long timestamp;
|
||||
private SignalServiceGroup group;
|
||||
private String body;
|
||||
private boolean endSession;
|
||||
private int expiresInSeconds;
|
||||
private boolean expirationUpdate;
|
||||
private byte[] profileKey;
|
||||
private boolean profileKeyUpdate;
|
||||
private Quote quote;
|
||||
private Sticker sticker;
|
||||
private PreKeyBundle preKeyBundle;
|
||||
private String syncTarget;
|
||||
|
||||
private Builder() {}
|
||||
@ -301,15 +296,6 @@ public class SignalServiceDataMessage {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder asEndSessionMessage() {
|
||||
return asEndSessionMessage(true);
|
||||
}
|
||||
|
||||
public Builder asEndSessionMessage(boolean endSession) {
|
||||
this.endSession = endSession;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder asExpirationUpdate() {
|
||||
return asExpirationUpdate(true);
|
||||
}
|
||||
@ -329,11 +315,6 @@ public class SignalServiceDataMessage {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder asProfileKeyUpdate(boolean profileKeyUpdate) {
|
||||
this.profileKeyUpdate = profileKeyUpdate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withQuote(Quote quote) {
|
||||
this.quote = quote;
|
||||
return this;
|
||||
@ -354,16 +335,6 @@ public class SignalServiceDataMessage {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withSticker(Sticker sticker) {
|
||||
this.sticker = sticker;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPreKeyBundle(PreKeyBundle preKeyBundle) {
|
||||
this.preKeyBundle = preKeyBundle;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SignalServiceDataMessage build() {
|
||||
if (timestamp == 0) timestamp = System.currentTimeMillis();
|
||||
// closedGroupUpdate is always null because we don't use SignalServiceDataMessage to send them (we use ClosedGroupUpdateMessageSendJob)
|
||||
|
@ -1,8 +0,0 @@
|
||||
package org.session.libsignal.service.api.messages;
|
||||
|
||||
import org.session.libsignal.service.loki.utilities.TTLUtilities;
|
||||
|
||||
public class SignalServiceNullMessage {
|
||||
|
||||
public int getTTL() { return TTLUtilities.getTTL(TTLUtilities.MessageType.Ephemeral); }
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package org.session.libsignal.service.api.messages.calls;
|
||||
|
||||
|
||||
public class AnswerMessage {
|
||||
|
||||
private final long id;
|
||||
private final String description;
|
||||
|
||||
public AnswerMessage(long id, String description) {
|
||||
this.id = id;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package org.session.libsignal.service.api.messages.calls;
|
||||
|
||||
|
||||
public class BusyMessage {
|
||||
|
||||
private final long id;
|
||||
|
||||
public BusyMessage(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package org.session.libsignal.service.api.messages.calls;
|
||||
|
||||
|
||||
public class HangupMessage {
|
||||
|
||||
private final long id;
|
||||
|
||||
public HangupMessage(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package org.session.libsignal.service.api.messages.calls;
|
||||
|
||||
|
||||
public class IceUpdateMessage {
|
||||
|
||||
private final long id;
|
||||
private final String sdpMid;
|
||||
private final int sdpMLineIndex;
|
||||
private final String sdp;
|
||||
|
||||
public IceUpdateMessage(long id, String sdpMid, int sdpMLineIndex, String sdp) {
|
||||
this.id = id;
|
||||
this.sdpMid = sdpMid;
|
||||
this.sdpMLineIndex = sdpMLineIndex;
|
||||
this.sdp = sdp;
|
||||
}
|
||||
|
||||
public String getSdpMid() {
|
||||
return sdpMid;
|
||||
}
|
||||
|
||||
public int getSdpMLineIndex() {
|
||||
return sdpMLineIndex;
|
||||
}
|
||||
|
||||
public String getSdp() {
|
||||
return sdp;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package org.session.libsignal.service.api.messages.calls;
|
||||
|
||||
|
||||
public class OfferMessage {
|
||||
|
||||
private final long id;
|
||||
private final String description;
|
||||
|
||||
public OfferMessage(long id, String description) {
|
||||
this.id = id;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
package org.session.libsignal.service.api.messages.calls;
|
||||
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.session.libsignal.service.loki.utilities.TTLUtilities;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class SignalServiceCallMessage {
|
||||
|
||||
private final Optional<OfferMessage> offerMessage;
|
||||
private final Optional<AnswerMessage> answerMessage;
|
||||
private final Optional<HangupMessage> hangupMessage;
|
||||
private final Optional<BusyMessage> busyMessage;
|
||||
private final Optional<List<IceUpdateMessage>> iceUpdateMessages;
|
||||
|
||||
private SignalServiceCallMessage(Optional<OfferMessage> offerMessage,
|
||||
Optional<AnswerMessage> answerMessage,
|
||||
Optional<List<IceUpdateMessage>> iceUpdateMessages,
|
||||
Optional<HangupMessage> hangupMessage,
|
||||
Optional<BusyMessage> busyMessage)
|
||||
{
|
||||
this.offerMessage = offerMessage;
|
||||
this.answerMessage = answerMessage;
|
||||
this.iceUpdateMessages = iceUpdateMessages;
|
||||
this.hangupMessage = hangupMessage;
|
||||
this.busyMessage = busyMessage;
|
||||
}
|
||||
|
||||
public static SignalServiceCallMessage forOffer(OfferMessage offerMessage) {
|
||||
return new SignalServiceCallMessage(Optional.of(offerMessage),
|
||||
Optional.<AnswerMessage>absent(),
|
||||
Optional.<List<IceUpdateMessage>>absent(),
|
||||
Optional.<HangupMessage>absent(),
|
||||
Optional.<BusyMessage>absent());
|
||||
}
|
||||
|
||||
public static SignalServiceCallMessage forAnswer(AnswerMessage answerMessage) {
|
||||
return new SignalServiceCallMessage(Optional.<OfferMessage>absent(),
|
||||
Optional.of(answerMessage),
|
||||
Optional.<List<IceUpdateMessage>>absent(),
|
||||
Optional.<HangupMessage>absent(),
|
||||
Optional.<BusyMessage>absent());
|
||||
}
|
||||
|
||||
public static SignalServiceCallMessage forIceUpdates(List<IceUpdateMessage> iceUpdateMessages) {
|
||||
return new SignalServiceCallMessage(Optional.<OfferMessage>absent(),
|
||||
Optional.<AnswerMessage>absent(),
|
||||
Optional.of(iceUpdateMessages),
|
||||
Optional.<HangupMessage>absent(),
|
||||
Optional.<BusyMessage>absent());
|
||||
}
|
||||
|
||||
public static SignalServiceCallMessage forIceUpdate(final IceUpdateMessage iceUpdateMessage) {
|
||||
List<IceUpdateMessage> iceUpdateMessages = new LinkedList<IceUpdateMessage>();
|
||||
iceUpdateMessages.add(iceUpdateMessage);
|
||||
|
||||
return new SignalServiceCallMessage(Optional.<OfferMessage>absent(),
|
||||
Optional.<AnswerMessage>absent(),
|
||||
Optional.of(iceUpdateMessages),
|
||||
Optional.<HangupMessage>absent(),
|
||||
Optional.<BusyMessage>absent());
|
||||
}
|
||||
|
||||
public static SignalServiceCallMessage forHangup(HangupMessage hangupMessage) {
|
||||
return new SignalServiceCallMessage(Optional.<OfferMessage>absent(),
|
||||
Optional.<AnswerMessage>absent(),
|
||||
Optional.<List<IceUpdateMessage>>absent(),
|
||||
Optional.of(hangupMessage),
|
||||
Optional.<BusyMessage>absent());
|
||||
}
|
||||
|
||||
public static SignalServiceCallMessage forBusy(BusyMessage busyMessage) {
|
||||
return new SignalServiceCallMessage(Optional.<OfferMessage>absent(),
|
||||
Optional.<AnswerMessage>absent(),
|
||||
Optional.<List<IceUpdateMessage>>absent(),
|
||||
Optional.<HangupMessage>absent(),
|
||||
Optional.of(busyMessage));
|
||||
}
|
||||
|
||||
|
||||
public static SignalServiceCallMessage empty() {
|
||||
return new SignalServiceCallMessage(Optional.<OfferMessage>absent(),
|
||||
Optional.<AnswerMessage>absent(),
|
||||
Optional.<List<IceUpdateMessage>>absent(),
|
||||
Optional.<HangupMessage>absent(),
|
||||
Optional.<BusyMessage>absent());
|
||||
}
|
||||
|
||||
public Optional<List<IceUpdateMessage>> getIceUpdateMessages() {
|
||||
return iceUpdateMessages;
|
||||
}
|
||||
|
||||
public Optional<AnswerMessage> getAnswerMessage() {
|
||||
return answerMessage;
|
||||
}
|
||||
|
||||
public Optional<OfferMessage> getOfferMessage() {
|
||||
return offerMessage;
|
||||
}
|
||||
|
||||
public Optional<HangupMessage> getHangupMessage() {
|
||||
return hangupMessage;
|
||||
}
|
||||
|
||||
public Optional<BusyMessage> getBusyMessage() {
|
||||
return busyMessage;
|
||||
}
|
||||
|
||||
public int getTTL() { return TTLUtilities.getTTL(TTLUtilities.MessageType.Call); }
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package org.session.libsignal.service.api.messages.calls;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TurnServerInfo {
|
||||
|
||||
@JsonProperty
|
||||
private String username;
|
||||
|
||||
@JsonProperty
|
||||
private String password;
|
||||
|
||||
@JsonProperty
|
||||
private List<String> urls;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public List<String> getUrls() {
|
||||
return urls;
|
||||
}
|
||||
}
|
@ -12,14 +12,10 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import org.session.libsignal.libsignal.IdentityKey;
|
||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
||||
import org.session.libsignal.utilities.logging.Log;
|
||||
import org.session.libsignal.libsignal.state.PreKeyBundle;
|
||||
import org.session.libsignal.libsignal.state.PreKeyRecord;
|
||||
import org.session.libsignal.libsignal.state.SignedPreKeyRecord;
|
||||
import org.session.libsignal.libsignal.util.Pair;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.session.libsignal.service.api.crypto.UnidentifiedAccess;
|
||||
import org.session.libsignal.service.api.messages.SignalServiceAttachment.ProgressListener;
|
||||
import org.session.libsignal.service.api.messages.calls.TurnServerInfo;
|
||||
import org.session.libsignal.service.api.profiles.SignalServiceProfile;
|
||||
import org.session.libsignal.service.api.push.ContactTokenDetails;
|
||||
import org.session.libsignal.service.api.push.SignalServiceAddress;
|
||||
@ -157,110 +153,6 @@ public class PushServiceSocket {
|
||||
this.random = new SecureRandom();
|
||||
}
|
||||
|
||||
public void requestSmsVerificationCode(boolean androidSmsRetriever, Optional<String> captchaToken) throws IOException {
|
||||
String path = String.format(CREATE_ACCOUNT_SMS_PATH, credentialsProvider.getUser(), androidSmsRetriever ? "android-ng" : "android");
|
||||
|
||||
if (captchaToken.isPresent()) {
|
||||
path += "&captcha=" + captchaToken.get();
|
||||
}
|
||||
|
||||
makeServiceRequest(path, "GET", null, NO_HEADERS, new ResponseCodeHandler() {
|
||||
@Override
|
||||
public void handle(int responseCode) throws NonSuccessfulResponseCodeException {
|
||||
if (responseCode == 402) {
|
||||
throw new CaptchaRequiredException();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void requestVoiceVerificationCode(Locale locale, Optional<String> captchaToken) throws IOException {
|
||||
Map<String, String> headers = locale != null ? Collections.singletonMap("Accept-Language", locale.getLanguage() + "-" + locale.getCountry()) : NO_HEADERS;
|
||||
String path = String.format(CREATE_ACCOUNT_VOICE_PATH, credentialsProvider.getUser());
|
||||
|
||||
if (captchaToken.isPresent()) {
|
||||
path += "?captcha=" + captchaToken.get();
|
||||
}
|
||||
|
||||
makeServiceRequest(path, "GET", null, headers, new ResponseCodeHandler() {
|
||||
@Override
|
||||
public void handle(int responseCode) throws NonSuccessfulResponseCodeException {
|
||||
if (responseCode == 402) {
|
||||
throw new CaptchaRequiredException();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void verifyAccountCode(String verificationCode, String signalingKey, int registrationId, boolean fetchesMessages, String pin,
|
||||
byte[] unidentifiedAccessKey, boolean unrestrictedUnidentifiedAccess)
|
||||
throws IOException
|
||||
{
|
||||
AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, registrationId, fetchesMessages, pin,
|
||||
unidentifiedAccessKey, unrestrictedUnidentifiedAccess);
|
||||
makeServiceRequest(String.format(VERIFY_ACCOUNT_CODE_PATH, verificationCode),
|
||||
"PUT", JsonUtil.toJson(signalingKeyEntity));
|
||||
}
|
||||
|
||||
public void setAccountAttributes(String signalingKey, int registrationId, boolean fetchesMessages, String pin,
|
||||
byte[] unidentifiedAccessKey, boolean unrestrictedUnidentifiedAccess)
|
||||
throws IOException
|
||||
{
|
||||
AccountAttributes accountAttributes = new AccountAttributes(signalingKey, registrationId, fetchesMessages, pin,
|
||||
unidentifiedAccessKey, unrestrictedUnidentifiedAccess);
|
||||
makeServiceRequest(SET_ACCOUNT_ATTRIBUTES, "PUT", JsonUtil.toJson(accountAttributes));
|
||||
}
|
||||
|
||||
public String getNewDeviceVerificationCode() throws IOException {
|
||||
String responseText = makeServiceRequest(PROVISIONING_CODE_PATH, "GET", null);
|
||||
return JsonUtil.fromJson(responseText, DeviceCode.class).getVerificationCode();
|
||||
}
|
||||
|
||||
public void removeDevice(long deviceId) throws IOException {
|
||||
makeServiceRequest(String.format(DEVICE_PATH, String.valueOf(deviceId)), "DELETE", null);
|
||||
}
|
||||
|
||||
public void sendProvisioningMessage(String destination, byte[] body) throws IOException {
|
||||
makeServiceRequest(String.format(PROVISIONING_MESSAGE_PATH, destination), "PUT",
|
||||
JsonUtil.toJson(new ProvisioningMessage(Base64.encodeBytes(body))));
|
||||
}
|
||||
|
||||
public void registerGcmId(String gcmRegistrationId) throws IOException {
|
||||
GcmRegistrationId registration = new GcmRegistrationId(gcmRegistrationId, true);
|
||||
makeServiceRequest(REGISTER_GCM_PATH, "PUT", JsonUtil.toJson(registration));
|
||||
}
|
||||
|
||||
public void unregisterGcmId() throws IOException {
|
||||
makeServiceRequest(REGISTER_GCM_PATH, "DELETE", null);
|
||||
}
|
||||
|
||||
public void setPin(String pin) throws IOException {
|
||||
RegistrationLock accountLock = new RegistrationLock(pin);
|
||||
makeServiceRequest(PIN_PATH, "PUT", JsonUtil.toJson(accountLock));
|
||||
}
|
||||
|
||||
public void removePin() throws IOException {
|
||||
makeServiceRequest(PIN_PATH, "DELETE", null);
|
||||
}
|
||||
|
||||
public byte[] getSenderCertificate() throws IOException {
|
||||
String responseText = makeServiceRequest(SENDER_CERTIFICATE_PATH, "GET", null);
|
||||
return JsonUtil.fromJson(responseText, SenderCertificate.class).getCertificate();
|
||||
}
|
||||
|
||||
public SendMessageResponse sendMessage(OutgoingPushMessageList bundle, Optional<UnidentifiedAccess> unidentifiedAccess)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
String responseText = makeServiceRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", JsonUtil.toJson(bundle), NO_HEADERS, unidentifiedAccess);
|
||||
|
||||
if (responseText == null) return new SendMessageResponse(false);
|
||||
else return JsonUtil.fromJson(responseText, SendMessageResponse.class);
|
||||
} catch (NotFoundException nfe) {
|
||||
throw new UnregisteredUserException(bundle.getDestination(), nfe);
|
||||
}
|
||||
}
|
||||
|
||||
public List<SignalServiceEnvelopeEntity> getMessages() throws IOException {
|
||||
String responseText = makeServiceRequest(String.format(MESSAGE_PATH, ""), "GET", null);
|
||||
return JsonUtil.fromJson(responseText, SignalServiceEnvelopeEntityList.class).getMessages();
|
||||
@ -270,159 +162,6 @@ public class PushServiceSocket {
|
||||
makeServiceRequest(String.format(SENDER_ACK_MESSAGE_PATH, sender, timestamp), "DELETE", null);
|
||||
}
|
||||
|
||||
public void acknowledgeMessage(String uuid) throws IOException {
|
||||
makeServiceRequest(String.format(UUID_ACK_MESSAGE_PATH, uuid), "DELETE", null);
|
||||
}
|
||||
|
||||
public void registerPreKeys(IdentityKey identityKey,
|
||||
SignedPreKeyRecord signedPreKey,
|
||||
List<PreKeyRecord> records)
|
||||
throws IOException
|
||||
{
|
||||
List<PreKeyEntity> entities = new LinkedList<PreKeyEntity>();
|
||||
|
||||
for (PreKeyRecord record : records) {
|
||||
PreKeyEntity entity = new PreKeyEntity(record.getId(),
|
||||
record.getKeyPair().getPublicKey());
|
||||
|
||||
entities.add(entity);
|
||||
}
|
||||
|
||||
SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(),
|
||||
signedPreKey.getKeyPair().getPublicKey(),
|
||||
signedPreKey.getSignature());
|
||||
|
||||
makeServiceRequest(String.format(PREKEY_PATH, ""), "PUT",
|
||||
JsonUtil.toJson(new PreKeyState(entities, signedPreKeyEntity, identityKey)));
|
||||
}
|
||||
|
||||
public int getAvailablePreKeys() throws IOException {
|
||||
String responseText = makeServiceRequest(PREKEY_METADATA_PATH, "GET", null);
|
||||
PreKeyStatus preKeyStatus = JsonUtil.fromJson(responseText, PreKeyStatus.class);
|
||||
|
||||
return preKeyStatus.getCount();
|
||||
}
|
||||
|
||||
public List<PreKeyBundle> getPreKeys(SignalServiceAddress destination,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
int deviceIdInteger)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
String deviceId = String.valueOf(deviceIdInteger);
|
||||
|
||||
if (deviceId.equals("1"))
|
||||
deviceId = "*";
|
||||
|
||||
String path = String.format(PREKEY_DEVICE_PATH, destination.getNumber(), deviceId);
|
||||
|
||||
if (destination.getRelay().isPresent()) {
|
||||
path = path + "?relay=" + destination.getRelay().get();
|
||||
}
|
||||
|
||||
String responseText = makeServiceRequest(path, "GET", null, NO_HEADERS, unidentifiedAccess);
|
||||
PreKeyResponse response = JsonUtil.fromJson(responseText, PreKeyResponse.class);
|
||||
List<PreKeyBundle> bundles = new LinkedList<PreKeyBundle>();
|
||||
|
||||
for (PreKeyResponseItem device : response.getDevices()) {
|
||||
ECPublicKey preKey = null;
|
||||
ECPublicKey signedPreKey = null;
|
||||
byte[] signedPreKeySignature = null;
|
||||
int preKeyId = -1;
|
||||
int signedPreKeyId = -1;
|
||||
|
||||
if (device.getSignedPreKey() != null) {
|
||||
signedPreKey = device.getSignedPreKey().getPublicKey();
|
||||
signedPreKeyId = device.getSignedPreKey().getKeyId();
|
||||
signedPreKeySignature = device.getSignedPreKey().getSignature();
|
||||
}
|
||||
|
||||
if (device.getPreKey() != null) {
|
||||
preKeyId = device.getPreKey().getKeyId();
|
||||
preKey = device.getPreKey().getPublicKey();
|
||||
}
|
||||
|
||||
bundles.add(new PreKeyBundle(device.getRegistrationId(), device.getDeviceId(), preKeyId,
|
||||
preKey, signedPreKeyId, signedPreKey, signedPreKeySignature,
|
||||
response.getIdentityKey()));
|
||||
}
|
||||
|
||||
return bundles;
|
||||
} catch (NotFoundException nfe) {
|
||||
throw new UnregisteredUserException(destination.getNumber(), nfe);
|
||||
}
|
||||
}
|
||||
|
||||
public PreKeyBundle getPreKey(SignalServiceAddress destination, int deviceId) throws IOException {
|
||||
try {
|
||||
String path = String.format(PREKEY_DEVICE_PATH, destination.getNumber(),
|
||||
String.valueOf(deviceId));
|
||||
|
||||
if (destination.getRelay().isPresent()) {
|
||||
path = path + "?relay=" + destination.getRelay().get();
|
||||
}
|
||||
|
||||
String responseText = makeServiceRequest(path, "GET", null);
|
||||
PreKeyResponse response = JsonUtil.fromJson(responseText, PreKeyResponse.class);
|
||||
|
||||
if (response.getDevices() == null || response.getDevices().size() < 1)
|
||||
throw new IOException("Empty prekey list");
|
||||
|
||||
PreKeyResponseItem device = response.getDevices().get(0);
|
||||
ECPublicKey preKey = null;
|
||||
ECPublicKey signedPreKey = null;
|
||||
byte[] signedPreKeySignature = null;
|
||||
int preKeyId = -1;
|
||||
int signedPreKeyId = -1;
|
||||
|
||||
if (device.getPreKey() != null) {
|
||||
preKeyId = device.getPreKey().getKeyId();
|
||||
preKey = device.getPreKey().getPublicKey();
|
||||
}
|
||||
|
||||
if (device.getSignedPreKey() != null) {
|
||||
signedPreKeyId = device.getSignedPreKey().getKeyId();
|
||||
signedPreKey = device.getSignedPreKey().getPublicKey();
|
||||
signedPreKeySignature = device.getSignedPreKey().getSignature();
|
||||
}
|
||||
|
||||
return new PreKeyBundle(device.getRegistrationId(), device.getDeviceId(), preKeyId, preKey,
|
||||
signedPreKeyId, signedPreKey, signedPreKeySignature, response.getIdentityKey());
|
||||
} catch (NotFoundException nfe) {
|
||||
throw new UnregisteredUserException(destination.getNumber(), nfe);
|
||||
}
|
||||
}
|
||||
|
||||
public SignedPreKeyEntity getCurrentSignedPreKey() throws IOException {
|
||||
try {
|
||||
String responseText = makeServiceRequest(SIGNED_PREKEY_PATH, "GET", null);
|
||||
return JsonUtil.fromJson(responseText, SignedPreKeyEntity.class);
|
||||
} catch (NotFoundException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setCurrentSignedPreKey(SignedPreKeyRecord signedPreKey) throws IOException {
|
||||
SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(),
|
||||
signedPreKey.getKeyPair().getPublicKey(),
|
||||
signedPreKey.getSignature());
|
||||
makeServiceRequest(SIGNED_PREKEY_PATH, "PUT", JsonUtil.toJson(signedPreKeyEntity));
|
||||
}
|
||||
|
||||
public void retrieveAttachment(long attachmentId, File destination, int maxSizeBytes, ProgressListener listener)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||
{
|
||||
downloadFromCdn(destination, String.format(ATTACHMENT_DOWNLOAD_PATH, attachmentId), maxSizeBytes, listener);
|
||||
}
|
||||
|
||||
public void retrieveSticker(File destination, byte[] packId, int stickerId)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||
{
|
||||
String hexPackId = Hex.toStringCondensed(packId);
|
||||
downloadFromCdn(destination, String.format(STICKER_PATH, hexPackId, stickerId), 1024 * 1024, null);
|
||||
}
|
||||
|
||||
public byte[] retrieveSticker(byte[] packId, int stickerId)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||
{
|
||||
@ -457,172 +196,6 @@ public class PushServiceSocket {
|
||||
}
|
||||
}
|
||||
|
||||
public void retrieveProfileAvatar(String path, File destination, int maxSizeBytes)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||
{
|
||||
downloadFromCdn(destination, path, maxSizeBytes, null);
|
||||
}
|
||||
|
||||
public void setProfileName(String name) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
||||
makeServiceRequest(String.format(PROFILE_PATH, "name/" + (name == null ? "" : URLEncoder.encode(name))), "PUT", "");
|
||||
}
|
||||
|
||||
public void setProfileAvatar(ProfileAvatarData profileAvatar)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||
{
|
||||
String response = makeServiceRequest(String.format(PROFILE_PATH, "form/avatar"), "GET", null);
|
||||
ProfileAvatarUploadAttributes formAttributes;
|
||||
|
||||
try {
|
||||
formAttributes = JsonUtil.fromJson(response, ProfileAvatarUploadAttributes.class);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new NonSuccessfulResponseCodeException("Unable to parse entity");
|
||||
}
|
||||
|
||||
if (profileAvatar != null) {
|
||||
uploadToCdn("", formAttributes.getAcl(), formAttributes.getKey(),
|
||||
formAttributes.getPolicy(), formAttributes.getAlgorithm(),
|
||||
formAttributes.getCredential(), formAttributes.getDate(),
|
||||
formAttributes.getSignature(), profileAvatar.getData(),
|
||||
profileAvatar.getContentType(), profileAvatar.getDataLength(),
|
||||
profileAvatar.getOutputStreamFactory(), null);
|
||||
}
|
||||
}
|
||||
|
||||
public List<ContactTokenDetails> retrieveDirectory(Set<String> contactTokens)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||
{
|
||||
try {
|
||||
ContactTokenList contactTokenList = new ContactTokenList(new LinkedList<String>(contactTokens));
|
||||
String response = makeServiceRequest(DIRECTORY_TOKENS_PATH, "PUT", JsonUtil.toJson(contactTokenList));
|
||||
ContactTokenDetailsList activeTokens = JsonUtil.fromJson(response, ContactTokenDetailsList.class);
|
||||
|
||||
return activeTokens.getContacts();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new NonSuccessfulResponseCodeException("Unable to parse entity");
|
||||
}
|
||||
}
|
||||
|
||||
public ContactTokenDetails getContactTokenDetails(String contactToken) throws IOException {
|
||||
try {
|
||||
String response = makeServiceRequest(String.format(DIRECTORY_VERIFY_PATH, contactToken), "GET", null);
|
||||
return JsonUtil.fromJson(response, ContactTokenDetails.class);
|
||||
} catch (NotFoundException nfe) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getContactDiscoveryAuthorization() throws IOException {
|
||||
String response = makeServiceRequest(DIRECTORY_AUTH_PATH, "GET", null);
|
||||
ContactDiscoveryCredentials token = JsonUtil.fromJson(response, ContactDiscoveryCredentials.class);
|
||||
return Credentials.basic(token.getUsername(), token.getPassword());
|
||||
}
|
||||
|
||||
public Pair<RemoteAttestationResponse, List<String>> getContactDiscoveryRemoteAttestation(String authorization, RemoteAttestationRequest request, String mrenclave)
|
||||
throws IOException
|
||||
{
|
||||
Response response = makeContactDiscoveryRequest(authorization, new LinkedList<String>(), "/v1/attestation/" + mrenclave, "PUT", JsonUtil.toJson(request));
|
||||
ResponseBody body = response.body();
|
||||
List<String> rawCookies = response.headers("Set-Cookie");
|
||||
List<String> cookies = new LinkedList<String>();
|
||||
|
||||
for (String cookie : rawCookies) {
|
||||
cookies.add(cookie.split(";")[0]);
|
||||
}
|
||||
|
||||
if (body != null) {
|
||||
return new Pair<RemoteAttestationResponse, List<String>>(JsonUtil.fromJson(body.string(), RemoteAttestationResponse.class), cookies);
|
||||
} else {
|
||||
throw new NonSuccessfulResponseCodeException("Empty response!");
|
||||
}
|
||||
}
|
||||
|
||||
public DiscoveryResponse getContactDiscoveryRegisteredUsers(String authorizationToken, DiscoveryRequest request, List<String> cookies, String mrenclave)
|
||||
throws IOException
|
||||
{
|
||||
ResponseBody body = makeContactDiscoveryRequest(authorizationToken, cookies, "/v1/discovery/" + mrenclave, "PUT", JsonUtil.toJson(request)).body();
|
||||
|
||||
if (body != null) {
|
||||
return JsonUtil.fromJson(body.string(), DiscoveryResponse.class);
|
||||
} else {
|
||||
throw new NonSuccessfulResponseCodeException("Empty response!");
|
||||
}
|
||||
}
|
||||
|
||||
public void reportContactDiscoveryServiceMatch() throws IOException {
|
||||
makeServiceRequest(String.format(DIRECTORY_FEEDBACK_PATH, "ok"), "PUT", "");
|
||||
}
|
||||
|
||||
public void reportContactDiscoveryServiceMismatch() throws IOException {
|
||||
makeServiceRequest(String.format(DIRECTORY_FEEDBACK_PATH, "mismatch"), "PUT", "");
|
||||
}
|
||||
|
||||
public void reportContactDiscoveryServiceAttestationError(String reason) throws IOException {
|
||||
ContactDiscoveryFailureReason failureReason = new ContactDiscoveryFailureReason(reason);
|
||||
makeServiceRequest(String.format(DIRECTORY_FEEDBACK_PATH, "attestation-error"), "PUT", JsonUtil.toJson(failureReason));
|
||||
}
|
||||
|
||||
public void reportContactDiscoveryServiceUnexpectedError(String reason) throws IOException {
|
||||
ContactDiscoveryFailureReason failureReason = new ContactDiscoveryFailureReason(reason);
|
||||
makeServiceRequest(String.format(DIRECTORY_FEEDBACK_PATH, "unexpected-error"), "PUT", JsonUtil.toJson(failureReason));
|
||||
}
|
||||
|
||||
public TurnServerInfo getTurnServerInfo() throws IOException {
|
||||
String response = makeServiceRequest(TURN_SERVER_INFO, "GET", null);
|
||||
return JsonUtil.fromJson(response, TurnServerInfo.class);
|
||||
}
|
||||
|
||||
public void setSoTimeoutMillis(long soTimeoutMillis) {
|
||||
this.soTimeoutMillis = soTimeoutMillis;
|
||||
}
|
||||
|
||||
public void cancelInFlightRequests() {
|
||||
synchronized (connections) {
|
||||
Log.w(TAG, "Canceling: " + connections.size());
|
||||
for (Call connection : connections) {
|
||||
Log.w(TAG, "Canceling: " + connection);
|
||||
connection.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AttachmentUploadAttributes getAttachmentUploadAttributes() throws NonSuccessfulResponseCodeException, PushNetworkException {
|
||||
String response = makeServiceRequest(ATTACHMENT_PATH, "GET", null);
|
||||
try {
|
||||
return JsonUtil.fromJson(response, AttachmentUploadAttributes.class);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new NonSuccessfulResponseCodeException("Unable to parse entity");
|
||||
}
|
||||
}
|
||||
|
||||
public Pair<Long, byte[]> uploadAttachment(PushAttachmentData attachment, AttachmentUploadAttributes uploadAttributes)
|
||||
throws PushNetworkException, NonSuccessfulResponseCodeException
|
||||
{
|
||||
long id = Long.parseLong(uploadAttributes.getAttachmentId());
|
||||
byte[] digest = uploadToCdn(ATTACHMENT_UPLOAD_PATH, uploadAttributes.getAcl(), uploadAttributes.getKey(),
|
||||
uploadAttributes.getPolicy(), uploadAttributes.getAlgorithm(),
|
||||
uploadAttributes.getCredential(), uploadAttributes.getDate(),
|
||||
uploadAttributes.getSignature(), attachment.getData(),
|
||||
"application/octet-stream", attachment.getDataSize(),
|
||||
attachment.getOutputStreamFactory(), attachment.getListener());
|
||||
|
||||
return new Pair<Long, byte[]>(id, digest);
|
||||
}
|
||||
|
||||
private void downloadFromCdn(File destination, String path, int maxSizeBytes, ProgressListener listener)
|
||||
throws PushNetworkException, NonSuccessfulResponseCodeException
|
||||
{
|
||||
try {
|
||||
FileOutputStream outputStream = new FileOutputStream(destination);
|
||||
downloadFromCdn(outputStream, path, maxSizeBytes, listener);
|
||||
} catch (IOException e) {
|
||||
throw new PushNetworkException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadFromCdn(OutputStream outputStream, String path, int maxSizeBytes, ProgressListener listener)
|
||||
throws PushNetworkException, NonSuccessfulResponseCodeException
|
||||
{
|
||||
@ -683,66 +256,6 @@ public class PushServiceSocket {
|
||||
throw new NonSuccessfulResponseCodeException("Response: " + response);
|
||||
}
|
||||
|
||||
private byte[] uploadToCdn(String path, String acl, String key, String policy, String algorithm,
|
||||
String credential, String date, String signature,
|
||||
InputStream data, String contentType, long length,
|
||||
OutputStreamFactory outputStreamFactory, ProgressListener progressListener)
|
||||
throws PushNetworkException, NonSuccessfulResponseCodeException
|
||||
{
|
||||
ConnectionHolder connectionHolder = getRandom(cdnClients, random);
|
||||
OkHttpClient okHttpClient = connectionHolder.getClient()
|
||||
.newBuilder()
|
||||
.connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
|
||||
.readTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
|
||||
.build();
|
||||
|
||||
DigestingRequestBody file = new DigestingRequestBody(data, outputStreamFactory, contentType, length, progressListener);
|
||||
|
||||
RequestBody requestBody = new MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
.addFormDataPart("acl", acl)
|
||||
.addFormDataPart("key", key)
|
||||
.addFormDataPart("policy", policy)
|
||||
.addFormDataPart("Content-Type", contentType)
|
||||
.addFormDataPart("x-amz-algorithm", algorithm)
|
||||
.addFormDataPart("x-amz-credential", credential)
|
||||
.addFormDataPart("x-amz-date", date)
|
||||
.addFormDataPart("x-amz-signature", signature)
|
||||
.addFormDataPart("file", "file", file)
|
||||
.build();
|
||||
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(connectionHolder.getUrl() + "/" + path)
|
||||
.post(requestBody);
|
||||
|
||||
if (connectionHolder.getHostHeader().isPresent()) {
|
||||
request.addHeader("Host", connectionHolder.getHostHeader().get());
|
||||
}
|
||||
|
||||
Call call = okHttpClient.newCall(request.build());
|
||||
|
||||
synchronized (connections) {
|
||||
connections.add(call);
|
||||
}
|
||||
|
||||
try {
|
||||
Response response;
|
||||
|
||||
try {
|
||||
response = call.execute();
|
||||
} catch (IOException e) {
|
||||
throw new PushNetworkException(e);
|
||||
}
|
||||
|
||||
if (response.isSuccessful()) return file.getTransmittedDigest();
|
||||
else throw new NonSuccessfulResponseCodeException("Response: " + response);
|
||||
} finally {
|
||||
synchronized (connections) {
|
||||
connections.remove(call);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String makeServiceRequest(String urlFragment, String method, String body)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||
{
|
||||
|
@ -1,9 +0,0 @@
|
||||
package org.session.libsignal.service.loki.database
|
||||
|
||||
import org.session.libsignal.libsignal.state.PreKeyBundle
|
||||
|
||||
interface LokiPreKeyBundleDatabaseProtocol {
|
||||
|
||||
fun getPreKeyBundle(publicKey: String): PreKeyBundle?
|
||||
fun removePreKeyBundle(publicKey: String)
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package org.session.libsignal.service.loki.database
|
||||
|
||||
import org.session.libsignal.libsignal.state.PreKeyRecord
|
||||
|
||||
interface LokiPreKeyRecordDatabaseProtocol {
|
||||
|
||||
fun getPreKeyRecord(publicKey: String): PreKeyRecord?
|
||||
}
|
Loading…
Reference in New Issue
Block a user