WIP: clean up signal protocol storage

This commit is contained in:
Ryan ZHAO 2021-02-19 15:11:30 +11:00
parent 1e1b3e02e1
commit d8104c0d5c
69 changed files with 6 additions and 17407 deletions

View File

@ -120,10 +120,7 @@ object FullBackupExporter {
} }
private inline fun shouldExportTable(table: String): Boolean { private inline fun shouldExportTable(table: String): Boolean {
return table != SignedPreKeyDatabase.TABLE_NAME && return table != PushDatabase.TABLE_NAME &&
table != OneTimePreKeyDatabase.TABLE_NAME &&
table != SessionDatabase.TABLE_NAME &&
table != PushDatabase.TABLE_NAME &&
table != LokiBackupFilesDatabase.TABLE_NAME && table != LokiBackupFilesDatabase.TABLE_NAME &&
table != LokiAPIDatabase.openGroupProfilePictureTable && table != LokiAPIDatabase.openGroupProfilePictureTable &&

View File

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

View File

@ -2,150 +2,20 @@ package org.thoughtcrime.securesms.crypto.storage;
import android.content.Context; import android.content.Context;
import org.session.libsignal.libsignal.IdentityKey;
import org.session.libsignal.libsignal.IdentityKeyPair; 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.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 org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import java.util.List; public class SignalProtocolStoreImpl implements IdentityKeyStore {
public class SignalProtocolStoreImpl implements SignalProtocolStore {
private final Context context; private final Context context;
// private final PreKeyStore preKeyStore;
// private final SignedPreKeyStore signedPreKeyStore;
// private final IdentityKeyStore identityKeyStore;
private final SessionStore sessionStore;
public SignalProtocolStoreImpl(Context context) { 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; this.context = context;
} }
@Override @Override
public IdentityKeyPair getIdentityKeyPair() { public IdentityKeyPair getIdentityKeyPair() {
return IdentityKeyUtil.getIdentityKeyPair(context); 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);
} }
} }

View File

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

View File

@ -53,9 +53,6 @@ public class DatabaseFactory {
private final GroupDatabase groupDatabase; private final GroupDatabase groupDatabase;
private final RecipientDatabase recipientDatabase; private final RecipientDatabase recipientDatabase;
private final GroupReceiptDatabase groupReceiptDatabase; private final GroupReceiptDatabase groupReceiptDatabase;
private final OneTimePreKeyDatabase preKeyDatabase;
private final SignedPreKeyDatabase signedPreKeyDatabase;
private final SessionDatabase sessionDatabase;
private final SearchDatabase searchDatabase; private final SearchDatabase searchDatabase;
private final JobDatabase jobDatabase; private final JobDatabase jobDatabase;
private final StickerDatabase stickerDatabase; private final StickerDatabase stickerDatabase;
@ -125,18 +122,6 @@ public class DatabaseFactory {
return getInstance(context).groupReceiptDatabase; return getInstance(context).groupReceiptDatabase;
} }
public static OneTimePreKeyDatabase getPreKeyDatabase(Context context) {
return getInstance(context).preKeyDatabase;
}
public static SignedPreKeyDatabase getSignedPreKeyDatabase(Context context) {
return getInstance(context).signedPreKeyDatabase;
}
public static SessionDatabase getSessionDatabase(Context context) {
return getInstance(context).sessionDatabase;
}
public static SearchDatabase getSearchDatabase(Context context) { public static SearchDatabase getSearchDatabase(Context context) {
return getInstance(context).searchDatabase; return getInstance(context).searchDatabase;
} }
@ -212,9 +197,6 @@ public class DatabaseFactory {
this.groupDatabase = new GroupDatabase(context, databaseHelper); this.groupDatabase = new GroupDatabase(context, databaseHelper);
this.recipientDatabase = new RecipientDatabase(context, databaseHelper); this.recipientDatabase = new RecipientDatabase(context, databaseHelper);
this.groupReceiptDatabase = new GroupReceiptDatabase(context, databaseHelper); this.groupReceiptDatabase = new GroupReceiptDatabase(context, databaseHelper);
this.preKeyDatabase = new OneTimePreKeyDatabase(context, databaseHelper);
this.signedPreKeyDatabase = new SignedPreKeyDatabase(context, databaseHelper);
this.sessionDatabase = new SessionDatabase(context, databaseHelper);
this.searchDatabase = new SearchDatabase(context, databaseHelper); this.searchDatabase = new SearchDatabase(context, databaseHelper);
this.jobDatabase = new JobDatabase(context, databaseHelper); this.jobDatabase = new JobDatabase(context, databaseHelper);
this.stickerDatabase = new StickerDatabase(context, databaseHelper, attachmentSecret); this.stickerDatabase = new StickerDatabase(context, databaseHelper, attachmentSecret);

View File

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

View File

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

View File

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

View File

@ -17,12 +17,9 @@ import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupReceiptDatabase; import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
import org.thoughtcrime.securesms.database.JobDatabase; import org.thoughtcrime.securesms.database.JobDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
import org.thoughtcrime.securesms.database.PushDatabase; import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.SearchDatabase; 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.SmsDatabase;
import org.thoughtcrime.securesms.database.StickerDatabase; import org.thoughtcrime.securesms.database.StickerDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
@ -94,9 +91,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(GroupDatabase.CREATE_TABLE); db.execSQL(GroupDatabase.CREATE_TABLE);
db.execSQL(RecipientDatabase.CREATE_TABLE); db.execSQL(RecipientDatabase.CREATE_TABLE);
db.execSQL(GroupReceiptDatabase.CREATE_TABLE); db.execSQL(GroupReceiptDatabase.CREATE_TABLE);
db.execSQL(OneTimePreKeyDatabase.CREATE_TABLE);
db.execSQL(SignedPreKeyDatabase.CREATE_TABLE);
db.execSQL(SessionDatabase.CREATE_TABLE);
for (String sql : SearchDatabase.CREATE_TABLE) { for (String sql : SearchDatabase.CREATE_TABLE) {
db.execSQL(sql); db.execSQL(sql);
} }

View File

@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.dependencies;
import android.content.Context; import android.content.Context;
import org.session.libsignal.libsignal.util.guava.Optional; 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.SignalServiceMessageReceiver;
import org.session.libsignal.service.api.SignalServiceMessageSender; import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.util.CredentialsProvider; import org.session.libsignal.service.api.util.CredentialsProvider;
@ -74,7 +73,6 @@ public class SignalCommunicationModule {
private final Context context; private final Context context;
private final SignalServiceNetworkAccess networkAccess; private final SignalServiceNetworkAccess networkAccess;
private SignalServiceAccountManager accountManager;
private SignalServiceMessageSender messageSender; private SignalServiceMessageSender messageSender;
private SignalServiceMessageReceiver messageReceiver; private SignalServiceMessageReceiver messageReceiver;

View File

@ -15,7 +15,6 @@ import com.annimon.stream.Collectors;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import org.session.libsession.messaging.jobs.Data; import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.utilities.MediaTypes;
import org.session.libsignal.metadata.InvalidMetadataMessageException; import org.session.libsignal.metadata.InvalidMetadataMessageException;
import org.session.libsignal.metadata.ProtocolInvalidMessageException; import org.session.libsignal.metadata.ProtocolInvalidMessageException;
import org.session.libsignal.service.api.crypto.SignalServiceCipher; 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.contactshare.ContactModelMapper;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; 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.NoSuchMessageException;
import org.thoughtcrime.securesms.database.PushDatabase; import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.StickerDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.groups.GroupMessageProcessor; import org.thoughtcrime.securesms.groups.GroupMessageProcessor;
import org.thoughtcrime.securesms.jobmanager.Job; 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.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.StickerSlide;
import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage; import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; 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.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceMessageSender; import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.messages.SignalServiceContent; 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.SignalServiceReceiptMessage;
import org.session.libsignal.service.api.messages.SignalServiceTypingMessage; import org.session.libsignal.service.api.messages.SignalServiceTypingMessage;
import org.session.libsignal.service.api.messages.shared.SharedContact; 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.mentions.MentionsManager;
import org.session.libsignal.service.loki.utilities.PublicKeyValidation; import org.session.libsignal.service.loki.utilities.PublicKeyValidation;
import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;

View File

@ -290,7 +290,6 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
.asExpirationUpdate(message.isExpirationUpdate()) .asExpirationUpdate(message.isExpirationUpdate())
.withProfileKey(profileKey.orNull()) .withProfileKey(profileKey.orNull())
.withQuote(quote.orNull()) .withQuote(quote.orNull())
.withSticker(sticker.orNull())
.withSharedContacts(sharedContacts) .withSharedContacts(sharedContacts)
.withPreviews(previews); .withPreviews(previews);
} }

View File

@ -263,7 +263,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
.withExpiration((int)(message.getExpiresIn() / 1000)) .withExpiration((int)(message.getExpiresIn() / 1000))
.withProfileKey(profileKey.orNull()) .withProfileKey(profileKey.orNull())
.withQuote(quote.orNull()) .withQuote(quote.orNull())
.withSticker(sticker.orNull())
.withSharedContacts(sharedContacts) .withSharedContacts(sharedContacts)
.withPreviews(previews) .withPreviews(previews)
.asExpirationUpdate(message.isExpirationUpdate()) .asExpirationUpdate(message.isExpirationUpdate())
@ -277,7 +276,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
.withExpiration((int)(message.getExpiresIn() / 1000)) .withExpiration((int)(message.getExpiresIn() / 1000))
.withProfileKey(profileKey.orNull()) .withProfileKey(profileKey.orNull())
.withQuote(quote.orNull()) .withQuote(quote.orNull())
.withSticker(sticker.orNull())
.withSharedContacts(sharedContacts) .withSharedContacts(sharedContacts)
.withPreviews(previews) .withPreviews(previews)
.asExpirationUpdate(message.isExpirationUpdate()) .asExpirationUpdate(message.isExpirationUpdate())

View File

@ -210,7 +210,6 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
.withBody(message.getBody()) .withBody(message.getBody())
.withExpiration((int)(message.getExpiresIn() / 1000)) .withExpiration((int)(message.getExpiresIn() / 1000))
.withProfileKey(profileKey.orNull()) .withProfileKey(profileKey.orNull())
.asEndSessionMessage(message.isEndSession())
.build(); .build();
SignalServiceDataMessage textSecureSelfSendMessage = SignalServiceDataMessage.newBuilder() SignalServiceDataMessage textSecureSelfSendMessage = SignalServiceDataMessage.newBuilder()
@ -219,7 +218,6 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
.withSyncTarget(destination.serialize()) .withSyncTarget(destination.serialize())
.withExpiration((int)(message.getExpiresIn() / 1000)) .withExpiration((int)(message.getExpiresIn() / 1000))
.withProfileKey(profileKey.orNull()) .withProfileKey(profileKey.orNull())
.asEndSessionMessage(message.isEndSession())
.build(); .build();
if (userPublicKey.equals(address.getNumber())) { if (userPublicKey.equals(address.getNumber())) {

View File

@ -5,14 +5,8 @@
*/ */
package org.session.libsignal.libsignal; 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 org.session.libsignal.libsignal.ecc.ECPrivateKey;
import static org.session.libsignal.libsignal.state.StorageProtos.IdentityKeyPairStructure;
/** /**
* Holder for public and private identity key pair. * Holder for public and private identity key pair.
* *
@ -28,16 +22,6 @@ public class IdentityKeyPair {
this.privateKey = privateKey; 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() { public IdentityKey getPublicKey() {
return publicKey; return publicKey;
} }
@ -45,11 +29,4 @@ public class IdentityKeyPair {
public ECPrivateKey getPrivateKey() { public ECPrivateKey getPrivateKey() {
return privateKey; return privateKey;
} }
public byte[] serialize() {
return IdentityKeyPairStructure.newBuilder()
.setPublicKey(ByteString.copyFrom(publicKey.serialize()))
.setPrivateKey(ByteString.copyFrom(privateKey.serialize()))
.build().toByteArray();
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,56 +27,4 @@ public interface IdentityKeyStore {
*/ */
public IdentityKeyPair getIdentityKeyPair(); 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);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,13 +10,9 @@ import org.session.libsignal.libsignal.IdentityKeyPair;
import org.session.libsignal.libsignal.InvalidKeyException; import org.session.libsignal.libsignal.InvalidKeyException;
import org.session.libsignal.libsignal.ecc.Curve; import org.session.libsignal.libsignal.ecc.Curve;
import org.session.libsignal.libsignal.ecc.ECKeyPair; 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.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.LinkedList;
import java.util.List;
/** /**
* Helper class for generating keys of different types. * Helper class for generating keys of different types.
@ -26,19 +22,6 @@ import java.util.List;
public class KeyHelper { public class KeyHelper {
private 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, * Generate a registration ID. Clients should only do this once,
* at install time. * at install time.
@ -59,79 +42,4 @@ public class KeyHelper {
throw new AssertionError(e); 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);
}
}
} }

View File

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

View File

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

View File

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

View File

@ -108,12 +108,6 @@ public class SignalServiceMessageReceiver {
return retrieveAttachment(pointer, destination, maxSizeBytes, null); 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) public InputStream retrieveProfileAvatar(String path, File destination, byte[] profileKey, int maxSizeBytes)
throws IOException throws IOException
{ {
@ -209,10 +203,6 @@ public class SignalServiceMessageReceiver {
return new SignalServiceMessagePipe(webSocket, Optional.of(credentialsProvider)); return new SignalServiceMessagePipe(webSocket, Optional.of(credentialsProvider));
} }
public List<SignalServiceEnvelope> retrieveMessages() throws IOException {
return retrieveMessages(new NullMessageReceivedCallback());
}
public List<SignalServiceEnvelope> retrieveMessages(MessageReceivedCallback callback) public List<SignalServiceEnvelope> retrieveMessages(MessageReceivedCallback callback)
throws IOException throws IOException
{ {

View File

@ -9,8 +9,8 @@ import com.google.protobuf.ByteString;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.session.libsignal.libsignal.ecc.ECKeyPair; 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.utilities.logging.Log;
import org.session.libsignal.libsignal.state.SignalProtocolStore;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.crypto.AttachmentCipherOutputStream; import org.session.libsignal.service.api.crypto.AttachmentCipherOutputStream;
import org.session.libsignal.service.api.crypto.UnidentifiedAccess; 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.PushNetworkException;
import org.session.libsignal.service.api.push.exceptions.UnregisteredUserException; import org.session.libsignal.service.api.push.exceptions.UnregisteredUserException;
import org.session.libsignal.service.api.util.CredentialsProvider; 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.crypto.PaddingInputStream;
import org.session.libsignal.service.internal.push.OutgoingPushMessage; import org.session.libsignal.service.internal.push.OutgoingPushMessage;
import org.session.libsignal.service.internal.push.OutgoingPushMessageList; import org.session.libsignal.service.internal.push.OutgoingPushMessageList;
import org.session.libsignal.service.internal.push.PushAttachmentData; 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.PushTransportDetails;
import org.session.libsignal.service.internal.push.SignalServiceProtos; import org.session.libsignal.service.internal.push.SignalServiceProtos;
import org.session.libsignal.service.internal.push.SignalServiceProtos.AttachmentPointer; 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.LokiAPIDatabaseProtocol;
import org.session.libsignal.service.loki.database.LokiMessageDatabaseProtocol; import org.session.libsignal.service.loki.database.LokiMessageDatabaseProtocol;
import org.session.libsignal.service.loki.database.LokiOpenGroupDatabaseProtocol; 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.LokiThreadDatabaseProtocol;
import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol; import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol;
import org.session.libsignal.service.loki.utilities.TTLUtilities; import org.session.libsignal.service.loki.utilities.TTLUtilities;
@ -94,7 +91,7 @@ public class SignalServiceMessageSender {
private static final String TAG = SignalServiceMessageSender.class.getSimpleName(); private static final String TAG = SignalServiceMessageSender.class.getSimpleName();
private final SignalProtocolStore store; private final IdentityKeyStore store;
private final SignalServiceAddress localAddress; private final SignalServiceAddress localAddress;
private final AtomicReference<Optional<SignalServiceMessagePipe>> pipe; private final AtomicReference<Optional<SignalServiceMessagePipe>> pipe;
@ -118,7 +115,7 @@ public class SignalServiceMessageSender {
* @param store The SignalProtocolStore. * @param store The SignalProtocolStore.
*/ */
public SignalServiceMessageSender(String user, String password, public SignalServiceMessageSender(String user, String password,
SignalProtocolStore store, IdentityKeyStore store,
Optional<SignalServiceMessagePipe> pipe, Optional<SignalServiceMessagePipe> pipe,
Optional<SignalServiceMessagePipe> unidentifiedPipe, Optional<SignalServiceMessagePipe> unidentifiedPipe,
String userPublicKey, String userPublicKey,
@ -134,7 +131,7 @@ public class SignalServiceMessageSender {
} }
public SignalServiceMessageSender(CredentialsProvider credentialsProvider, public SignalServiceMessageSender(CredentialsProvider credentialsProvider,
SignalProtocolStore store, IdentityKeyStore store,
Optional<SignalServiceMessagePipe> pipe, Optional<SignalServiceMessagePipe> pipe,
Optional<SignalServiceMessagePipe> unidentifiedPipe, Optional<SignalServiceMessagePipe> unidentifiedPipe,
String userPublicKey, String userPublicKey,

View File

@ -6,7 +6,6 @@
package org.session.libsignal.service.api.messages; 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.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.shared.SharedContact; import org.session.libsignal.service.api.messages.shared.SharedContact;
import org.session.libsignal.service.api.push.SignalServiceAddress; import org.session.libsignal.service.api.push.SignalServiceAddress;
@ -259,14 +258,10 @@ public class SignalServiceDataMessage {
private long timestamp; private long timestamp;
private SignalServiceGroup group; private SignalServiceGroup group;
private String body; private String body;
private boolean endSession;
private int expiresInSeconds; private int expiresInSeconds;
private boolean expirationUpdate; private boolean expirationUpdate;
private byte[] profileKey; private byte[] profileKey;
private boolean profileKeyUpdate;
private Quote quote; private Quote quote;
private Sticker sticker;
private PreKeyBundle preKeyBundle;
private String syncTarget; private String syncTarget;
private Builder() {} private Builder() {}
@ -301,15 +296,6 @@ public class SignalServiceDataMessage {
return this; return this;
} }
public Builder asEndSessionMessage() {
return asEndSessionMessage(true);
}
public Builder asEndSessionMessage(boolean endSession) {
this.endSession = endSession;
return this;
}
public Builder asExpirationUpdate() { public Builder asExpirationUpdate() {
return asExpirationUpdate(true); return asExpirationUpdate(true);
} }
@ -329,11 +315,6 @@ public class SignalServiceDataMessage {
return this; return this;
} }
public Builder asProfileKeyUpdate(boolean profileKeyUpdate) {
this.profileKeyUpdate = profileKeyUpdate;
return this;
}
public Builder withQuote(Quote quote) { public Builder withQuote(Quote quote) {
this.quote = quote; this.quote = quote;
return this; return this;
@ -354,16 +335,6 @@ public class SignalServiceDataMessage {
return this; 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() { public SignalServiceDataMessage build() {
if (timestamp == 0) timestamp = System.currentTimeMillis(); if (timestamp == 0) timestamp = System.currentTimeMillis();
// closedGroupUpdate is always null because we don't use SignalServiceDataMessage to send them (we use ClosedGroupUpdateMessageSendJob) // closedGroupUpdate is always null because we don't use SignalServiceDataMessage to send them (we use ClosedGroupUpdateMessageSendJob)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,14 +12,10 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import org.session.libsignal.libsignal.IdentityKey; import org.session.libsignal.libsignal.IdentityKey;
import org.session.libsignal.libsignal.ecc.ECPublicKey; import org.session.libsignal.libsignal.ecc.ECPublicKey;
import org.session.libsignal.utilities.logging.Log; 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.Pair;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.crypto.UnidentifiedAccess; import org.session.libsignal.service.api.crypto.UnidentifiedAccess;
import org.session.libsignal.service.api.messages.SignalServiceAttachment.ProgressListener; 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.profiles.SignalServiceProfile;
import org.session.libsignal.service.api.push.ContactTokenDetails; import org.session.libsignal.service.api.push.ContactTokenDetails;
import org.session.libsignal.service.api.push.SignalServiceAddress; import org.session.libsignal.service.api.push.SignalServiceAddress;
@ -157,110 +153,6 @@ public class PushServiceSocket {
this.random = new SecureRandom(); 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 { public List<SignalServiceEnvelopeEntity> getMessages() throws IOException {
String responseText = makeServiceRequest(String.format(MESSAGE_PATH, ""), "GET", null); String responseText = makeServiceRequest(String.format(MESSAGE_PATH, ""), "GET", null);
return JsonUtil.fromJson(responseText, SignalServiceEnvelopeEntityList.class).getMessages(); 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); 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) public byte[] retrieveSticker(byte[] packId, int stickerId)
throws NonSuccessfulResponseCodeException, PushNetworkException 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) private void downloadFromCdn(OutputStream outputStream, String path, int maxSizeBytes, ProgressListener listener)
throws PushNetworkException, NonSuccessfulResponseCodeException throws PushNetworkException, NonSuccessfulResponseCodeException
{ {
@ -683,66 +256,6 @@ public class PushServiceSocket {
throw new NonSuccessfulResponseCodeException("Response: " + response); 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) private String makeServiceRequest(String urlFragment, String method, String body)
throws NonSuccessfulResponseCodeException, PushNetworkException throws NonSuccessfulResponseCodeException, PushNetworkException
{ {

View File

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

View File

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