diff --git a/res/values/strings.xml b/res/values/strings.xml index 0df6e3941e..3474bc0f32 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -467,7 +467,7 @@ %1$d messages in %2$d conversations Most recent from: %1$s - Encrypted message... + Locked message... Media message: %s (No subject) Message delivery failed. @@ -779,7 +779,7 @@ Change my passphrase Enable passphrase Passphrase %s - Enable local encryption of messages and keys + Enable lock screen for messages Screen security Screen security %s Block screenshots in the recents list and inside the app diff --git a/res/xml/preferences_app_protection.xml b/res/xml/preferences_app_protection.xml index 5ec51ecd6f..6a263f80ce 100644 --- a/res/xml/preferences_app_protection.xml +++ b/res/xml/preferences_app_protection.xml @@ -5,7 +5,7 @@ android:key="pref_enable_passphrase_temporary" android:defaultValue="true" android:title="@string/preferences__enable_passphrase" - android:summary="@string/preferences__enable_local_encryption_of_messages_and_keys"/> + android:summary="@string/preferences__enable_lock_screen_for_messages"/> UPGRADE_VERSIONS = new TreeSet() {{ add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION); @@ -70,6 +65,7 @@ public class DatabaseUpgradeActivity extends BaseActivity { add(SIGNED_PREKEY_VERSION); add(NO_DECRYPT_QUEUE_VERSION); add(PUSH_DECRYPT_SERIAL_ID_VERSION); + add(MIGRATE_SESSION_PLAINTEXT); }}; private MasterSecret masterSecret; @@ -90,10 +86,6 @@ public class DatabaseUpgradeActivity extends BaseActivity { .execute(VersionTracker.getLastSeenVersion(this)); } else { VersionTracker.updateLastSeenVersion(this); - ApplicationContext.getInstance(this) - .getJobManager() - .setEncryptionKeys(new EncryptionKeys(ParcelUtil.serialize(masterSecret))); -// DecryptingQueue.schedulePendingDecrypts(DatabaseUpgradeActivity.this, masterSecret); MessageNotifier.updateNotification(DatabaseUpgradeActivity.this, masterSecret); startActivity((Intent)getIntent().getParcelableExtra("next_intent")); finish(); @@ -154,9 +146,7 @@ public class DatabaseUpgradeActivity extends BaseActivity { .onApplicationLevelUpgrade(context, masterSecret, params[0], this); if (params[0] < CURVE25519_VERSION) { - if (!IdentityKeyUtil.hasCurve25519IdentityKeys(context)) { - IdentityKeyUtil.generateCurve25519IdentityKeys(context, masterSecret); - } + IdentityKeyUtil.migrateIdentityKeys(context, masterSecret); } if (params[0] < NO_V1_VERSION) { @@ -178,70 +168,48 @@ public class DatabaseUpgradeActivity extends BaseActivity { if (params[0] < SIGNED_PREKEY_VERSION) { ApplicationContext.getInstance(getApplicationContext()) .getJobManager() - .add(new CreateSignedPreKeyJob(context, masterSecret)); + .add(new CreateSignedPreKeyJob(context)); } if (params[0] < NO_DECRYPT_QUEUE_VERSION) { - EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(getApplicationContext()); - PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(getApplicationContext()); - - SmsDatabase.Reader smsReader = null; - Cursor pushReader = null; - - SmsMessageRecord record; - - try { - smsReader = smsDatabase.getDecryptInProgressMessages(masterSecret); - - while ((record = smsReader.getNext()) != null) { - ApplicationContext.getInstance(getApplicationContext()) - .getJobManager() - .add(new SmsDecryptJob(getApplicationContext(), record.getId())); - } - } finally { - if (smsReader != null) - smsReader.close(); - } - - try { - pushReader = pushDatabase.getPending(); - - while ((pushReader != null && pushReader.moveToNext())) { - ApplicationContext.getInstance(getApplicationContext()) - .getJobManager() - .add(new PushDecryptJob(getApplicationContext(), - pushReader.getLong(pushReader.getColumnIndexOrThrow(PushDatabase.ID)), - pushReader.getString(pushReader.getColumnIndexOrThrow(PushDatabase.SOURCE)))); - } - } finally { - if (pushReader != null) - pushReader.close(); - } + scheduleMessagesInPushDatabase(context); } if (params[0] < PUSH_DECRYPT_SERIAL_ID_VERSION) { - PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context); - Cursor pushReader = null; + scheduleMessagesInPushDatabase(context); + } - try { - pushReader = pushDatabase.getPending(); + if (params[0] < MIGRATE_SESSION_PLAINTEXT) { + new TextSecureSessionStore(context, masterSecret).migrateSessions(); + new TextSecurePreKeyStore(context, masterSecret).migrateRecords(); - while (pushReader != null && pushReader.moveToNext()) { - ApplicationContext.getInstance(getApplicationContext()) - .getJobManager() - .add(new PushDecryptJob(getApplicationContext(), - pushReader.getLong(pushReader.getColumnIndexOrThrow(PushDatabase.ID)), - pushReader.getString(pushReader.getColumnIndexOrThrow(PushDatabase.SOURCE)))); - } - } finally { - if (pushReader != null) - pushReader.close(); - } + IdentityKeyUtil.migrateIdentityKeys(context, masterSecret); + scheduleMessagesInPushDatabase(context);; } return null; } + private void scheduleMessagesInPushDatabase(Context context) { + PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context); + Cursor pushReader = null; + + try { + pushReader = pushDatabase.getPending(); + + while (pushReader != null && pushReader.moveToNext()) { + ApplicationContext.getInstance(getApplicationContext()) + .getJobManager() + .add(new PushDecryptJob(getApplicationContext(), + pushReader.getLong(pushReader.getColumnIndexOrThrow(PushDatabase.ID)), + pushReader.getString(pushReader.getColumnIndexOrThrow(PushDatabase.SOURCE)))); + } + } finally { + if (pushReader != null) + pushReader.close(); + } + } + @Override protected void onProgressUpdate(Double... update) { indeterminateProgress.setVisibility(View.GONE); @@ -254,11 +222,6 @@ public class DatabaseUpgradeActivity extends BaseActivity { @Override protected void onPostExecute(Void result) { VersionTracker.updateLastSeenVersion(DatabaseUpgradeActivity.this); -// DecryptingQueue.schedulePendingDecrypts(DatabaseUpgradeActivity.this, masterSecret); - ApplicationContext.getInstance(DatabaseUpgradeActivity.this) - .getJobManager() - .setEncryptionKeys(new EncryptionKeys(ParcelUtil.serialize(masterSecret))); - MessageNotifier.updateNotification(DatabaseUpgradeActivity.this, masterSecret); startActivity((Intent)getIntent().getParcelableExtra("next_intent")); diff --git a/src/org/thoughtcrime/securesms/DeviceProvisioningActivity.java b/src/org/thoughtcrime/securesms/DeviceProvisioningActivity.java index 4f6149a3f5..39e0ea33ac 100644 --- a/src/org/thoughtcrime/securesms/DeviceProvisioningActivity.java +++ b/src/org/thoughtcrime/securesms/DeviceProvisioningActivity.java @@ -108,7 +108,7 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActionBarActiv String ephemeralId = uri.getQueryParameter("uuid"); String publicKeyEncoded = uri.getQueryParameter("pub_key"); ECPublicKey publicKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0); - IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context); accountManager.addDevice(ephemeralId, publicKey, identityKeyPair, verificationCode); return SUCCESS; diff --git a/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java b/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java index 79e0149113..ac636a8d39 100644 --- a/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java +++ b/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java @@ -66,7 +66,7 @@ public class PassphraseCreateActivity extends PassphraseActivity { passphrase); MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret); - IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this, masterSecret); + IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this); VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this); TextSecurePreferences.setPasswordDisabled(PassphraseCreateActivity.this, true); diff --git a/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterCipher.java b/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterCipher.java index 0563b0ad73..23255c5206 100644 --- a/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterCipher.java +++ b/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterCipher.java @@ -61,39 +61,45 @@ public class AsymmetricMasterCipher { this.asymmetricMasterSecret = asymmetricMasterSecret; } - public String decryptBody(String body) throws IOException, InvalidMessageException { + public byte[] encryptBytes(byte[] body) { + try { + ECPublicKey theirPublic = asymmetricMasterSecret.getDjbPublicKey(); + ECKeyPair ourKeyPair = Curve.generateKeyPair(); + byte[] secret = Curve.calculateAgreement(theirPublic, ourKeyPair.getPrivateKey()); + MasterCipher masterCipher = getMasterCipherForSecret(secret); + byte[] encryptedBodyBytes = masterCipher.encryptBytes(body); + + PublicKey ourPublicKey = new PublicKey(31337, ourKeyPair.getPublicKey()); + byte[] publicKeyBytes = ourPublicKey.serialize(); + + return Util.combine(publicKeyBytes, encryptedBodyBytes); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } + } + + public byte[] decryptBytes(byte[] combined) throws IOException, InvalidMessageException { try { - byte[] combined = Base64.decode(body); byte[][] parts = Util.split(combined, PublicKey.KEY_SIZE, combined.length - PublicKey.KEY_SIZE); PublicKey theirPublicKey = new PublicKey(parts[0], 0); ECPrivateKey ourPrivateKey = asymmetricMasterSecret.getPrivateKey(); byte[] secret = Curve.calculateAgreement(theirPublicKey.getKey(), ourPrivateKey); MasterCipher masterCipher = getMasterCipherForSecret(secret); - byte[] decryptedBody = masterCipher.decryptBytes(parts[1]); - return new String(decryptedBody); - } catch (InvalidKeyException | InvalidMessageException ike) { - throw new InvalidMessageException(ike); + return masterCipher.decryptBytes(parts[1]); + } catch (InvalidKeyException e) { + throw new InvalidMessageException(e); } } + public String decryptBody(String body) throws IOException, InvalidMessageException { + byte[] combined = Base64.decode(body); + return new String(decryptBytes(combined)); + } + public String encryptBody(String body) { - try { - ECPublicKey theirPublic = asymmetricMasterSecret.getDjbPublicKey(); - ECKeyPair ourKeyPair = Curve.generateKeyPair(); - byte[] secret = Curve.calculateAgreement(theirPublic, ourKeyPair.getPrivateKey()); - MasterCipher masterCipher = getMasterCipherForSecret(secret); - byte[] encryptedBodyBytes = masterCipher.encryptBytes(body.getBytes()); - - PublicKey ourPublicKey = new PublicKey(31337, ourKeyPair.getPublicKey()); - byte[] publicKeyBytes = ourPublicKey.serialize(); - byte[] combined = Util.combine(publicKeyBytes, encryptedBodyBytes); - - return Base64.encodeBytes(combined); - } catch (InvalidKeyException e) { - throw new AssertionError(e); - } + return Base64.encodeBytes(encryptBytes(body.getBytes())); } private MasterCipher getMasterCipherForSecret(byte[] secretBytes) { diff --git a/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java b/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java index dca0d09a3e..f0a4690fa0 100644 --- a/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java @@ -20,7 +20,7 @@ package org.thoughtcrime.securesms.crypto; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; -import android.util.Log; +import android.support.annotation.NonNull; import org.thoughtcrime.securesms.util.Base64; import org.whispersystems.libaxolotl.IdentityKey; @@ -40,86 +40,109 @@ import java.io.IOException; public class IdentityKeyUtil { - private static final String IDENTITY_PUBLIC_KEY_DJB_PREF = "pref_identity_public_curve25519"; - private static final String IDENTITY_PRIVATE_KEY_DJB_PREF = "pref_identity_private_curve25519"; + private static final String TAG = IdentityKeyUtil.class.getSimpleName(); + + private static final String IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF = "pref_identity_public_curve25519"; + private static final String IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF = "pref_identity_private_curve25519"; + + private static final String IDENTITY_PUBLIC_KEY_PREF = "pref_identity_public_v3"; + private static final String IDENTITY_PRIVATE_KEY_PREF = "pref_identity_private_v3"; public static boolean hasIdentityKey(Context context) { SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0); return - preferences.contains(IDENTITY_PUBLIC_KEY_DJB_PREF) && - preferences.contains(IDENTITY_PRIVATE_KEY_DJB_PREF); + preferences.contains(IDENTITY_PUBLIC_KEY_PREF) && + preferences.contains(IDENTITY_PRIVATE_KEY_PREF); } - public static IdentityKey getIdentityKey(Context context) { - if (!hasIdentityKey(context)) return null; + public static @NonNull IdentityKey getIdentityKey(@NonNull Context context) { + if (!hasIdentityKey(context)) throw new AssertionError("There isn't one!"); try { - byte[] publicKeyBytes = Base64.decode(retrieve(context, IDENTITY_PUBLIC_KEY_DJB_PREF)); + byte[] publicKeyBytes = Base64.decode(retrieve(context, IDENTITY_PUBLIC_KEY_PREF)); return new IdentityKey(publicKeyBytes, 0); - } catch (IOException ioe) { - Log.w("IdentityKeyUtil", ioe); - return null; - } catch (InvalidKeyException e) { - Log.w("IdentityKeyUtil", e); - return null; - } - } - - public static IdentityKeyPair getIdentityKeyPair(Context context, - MasterSecret masterSecret) - { - if (!hasIdentityKey(context)) - return null; - - try { - MasterCipher masterCipher = new MasterCipher(masterSecret); - IdentityKey publicKey = getIdentityKey(context); - ECPrivateKey privateKey = masterCipher.decryptKey(Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_DJB_PREF))); - - return new IdentityKeyPair(publicKey, privateKey); } catch (IOException | InvalidKeyException e) { throw new AssertionError(e); } } - public static void generateIdentityKeys(Context context, MasterSecret masterSecret) { + public static @NonNull IdentityKeyPair getIdentityKeyPair(@NonNull Context context) { + if (!hasIdentityKey(context)) throw new AssertionError("There isn't one!"); + + try { + IdentityKey publicKey = getIdentityKey(context); + ECPrivateKey privateKey = Curve.decodePrivatePoint(Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_PREF))); + + return new IdentityKeyPair(publicKey, privateKey); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + public static void generateIdentityKeys(Context context) { ECKeyPair djbKeyPair = Curve.generateKeyPair(); - - MasterCipher masterCipher = new MasterCipher(masterSecret); IdentityKey djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey()); - byte[] djbPrivateKey = masterCipher.encryptKey(djbKeyPair.getPrivateKey()); + ECPrivateKey djbPrivateKey = djbKeyPair.getPrivateKey(); - save(context, IDENTITY_PUBLIC_KEY_DJB_PREF, Base64.encodeBytes(djbIdentityKey.serialize())); - save(context, IDENTITY_PRIVATE_KEY_DJB_PREF, Base64.encodeBytes(djbPrivateKey)); + save(context, IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(djbIdentityKey.serialize())); + save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(djbPrivateKey.serialize())); } - public static boolean hasCurve25519IdentityKeys(Context context) { + public static void migrateIdentityKeys(@NonNull Context context, + @NonNull MasterSecret masterSecret) + { + if (!hasIdentityKey(context)) { + if (hasLegacyIdentityKeys(context)) { + IdentityKeyPair legacyPair = getLegacyIdentityKeyPair(context, masterSecret); + + save(context, IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(legacyPair.serialize())); + save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(legacyPair.serialize())); + + delete(context, IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF); + delete(context, IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF); + } else { + generateIdentityKeys(context); + } + } + } + + private static boolean hasLegacyIdentityKeys(Context context) { return - retrieve(context, IDENTITY_PUBLIC_KEY_DJB_PREF) != null && - retrieve(context, IDENTITY_PRIVATE_KEY_DJB_PREF) != null; + retrieve(context, IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF) != null && + retrieve(context, IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF) != null; } - public static void generateCurve25519IdentityKeys(Context context, MasterSecret masterSecret) { - MasterCipher masterCipher = new MasterCipher(masterSecret); - ECKeyPair djbKeyPair = Curve.generateKeyPair(); - IdentityKey djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey()); - byte[] djbPrivateKey = masterCipher.encryptKey(djbKeyPair.getPrivateKey()); + private static IdentityKeyPair getLegacyIdentityKeyPair(@NonNull Context context, + @NonNull MasterSecret masterSecret) + { + try { + MasterCipher masterCipher = new MasterCipher(masterSecret); + byte[] publicKeyBytes = Base64.decode(retrieve(context, IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF)); + IdentityKey identityKey = new IdentityKey(publicKeyBytes, 0); + ECPrivateKey privateKey = masterCipher.decryptKey(Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF))); - save(context, IDENTITY_PUBLIC_KEY_DJB_PREF, Base64.encodeBytes(djbIdentityKey.serialize())); - save(context, IDENTITY_PRIVATE_KEY_DJB_PREF, Base64.encodeBytes(djbPrivateKey)); + return new IdentityKeyPair(identityKey, privateKey); + } catch (IOException | InvalidKeyException e) { + throw new AssertionError(e); + } } - public static String retrieve(Context context, String key) { + private static String retrieve(Context context, String key) { SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0); return preferences.getString(key, null); } - public static void save(Context context, String key, String value) { + private static void save(Context context, String key, String value) { SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0); Editor preferencesEditor = preferences.edit(); preferencesEditor.putString(key, value); if (!preferencesEditor.commit()) throw new AssertionError("failed to save identity key/value to shared preferences"); } + + private static void delete(Context context, String key) { + context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0).edit().remove(key).commit(); + } + } diff --git a/src/org/thoughtcrime/securesms/crypto/MasterSecretUnion.java b/src/org/thoughtcrime/securesms/crypto/MasterSecretUnion.java new file mode 100644 index 0000000000..121562630f --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/MasterSecretUnion.java @@ -0,0 +1,29 @@ +package org.thoughtcrime.securesms.crypto; + +import android.support.annotation.NonNull; + +import org.whispersystems.libaxolotl.util.guava.Optional; + +public class MasterSecretUnion { + + private final Optional masterSecret; + private final Optional asymmetricMasterSecret; + + public MasterSecretUnion(@NonNull MasterSecret masterSecret) { + this.masterSecret = Optional.of(masterSecret); + this.asymmetricMasterSecret = Optional.absent(); + } + + public MasterSecretUnion(@NonNull AsymmetricMasterSecret asymmetricMasterSecret) { + this.masterSecret = Optional.absent(); + this.asymmetricMasterSecret = Optional.of(asymmetricMasterSecret); + } + + public Optional getMasterSecret() { + return masterSecret; + } + + public Optional getAsymmetricMasterSecret() { + return asymmetricMasterSecret; + } +} diff --git a/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java b/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java index 45f00ac538..598fdfc28a 100644 --- a/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java @@ -19,6 +19,8 @@ package org.thoughtcrime.securesms.crypto; import android.content.Context; import android.content.SharedPreferences; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; @@ -121,8 +123,8 @@ public class MasterSecretUtil { } } - public static AsymmetricMasterSecret getAsymmetricMasterSecret(Context context, - MasterSecret masterSecret) + public static AsymmetricMasterSecret getAsymmetricMasterSecret(@NonNull Context context, + @Nullable MasterSecret masterSecret) { try { byte[] djbPublicBytes = retrieve(context, ASYMMETRIC_LOCAL_PUBLIC_DJB); diff --git a/src/org/thoughtcrime/securesms/crypto/MediaKey.java b/src/org/thoughtcrime/securesms/crypto/MediaKey.java new file mode 100644 index 0000000000..058b5448fe --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/MediaKey.java @@ -0,0 +1,31 @@ +package org.thoughtcrime.securesms.crypto; + +import android.support.annotation.NonNull; + +import org.thoughtcrime.securesms.util.Base64; +import org.whispersystems.libaxolotl.InvalidMessageException; + +import java.io.IOException; + +public class MediaKey { + + public static String getEncrypted(@NonNull MasterSecretUnion masterSecret, @NonNull byte[] key) { + if (masterSecret.getMasterSecret().isPresent()) { + return Base64.encodeBytes(new MasterCipher(masterSecret.getMasterSecret().get()).encryptBytes(key)); + } else { + return "?ASYNC-" + Base64.encodeBytes(new AsymmetricMasterCipher(masterSecret.getAsymmetricMasterSecret().get()).encryptBytes(key)); + } + } + + public static byte[] getDecrypted(@NonNull MasterSecret masterSecret, + @NonNull AsymmetricMasterSecret asymmetricMasterSecret, + @NonNull String encodedKey) + throws IOException, InvalidMessageException + { + if (encodedKey.startsWith("?ASYNC-")) { + return new AsymmetricMasterCipher(asymmetricMasterSecret).decryptBytes(Base64.decode(encodedKey.substring("?ASYNC-".length()))); + } else { + return new MasterCipher(masterSecret).decryptBytes(Base64.decode(encodedKey)); + } + } +} diff --git a/src/org/thoughtcrime/securesms/crypto/PreKeyUtil.java b/src/org/thoughtcrime/securesms/crypto/PreKeyUtil.java index 691bb85d42..b857946886 100644 --- a/src/org/thoughtcrime/securesms/crypto/PreKeyUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/PreKeyUtil.java @@ -48,8 +48,8 @@ public class PreKeyUtil { public static final int BATCH_SIZE = 100; - public static List generatePreKeys(Context context, MasterSecret masterSecret) { - PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret); + public static List generatePreKeys(Context context) { + PreKeyStore preKeyStore = new TextSecurePreKeyStore(context); List records = new LinkedList<>(); int preKeyIdOffset = getNextPreKeyId(context); @@ -66,11 +66,10 @@ public class PreKeyUtil { return records; } - public static SignedPreKeyRecord generateSignedPreKey(Context context, MasterSecret masterSecret, - IdentityKeyPair identityKeyPair) + public static SignedPreKeyRecord generateSignedPreKey(Context context, IdentityKeyPair identityKeyPair) { try { - SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret); + SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context); int signedPreKeyId = getNextSignedPreKeyId(context); ECKeyPair keyPair = Curve.generateKeyPair(); byte[] signature = Curve.calculateSignature(identityKeyPair.getPrivateKey(), keyPair.getPublicKey().serialize()); @@ -85,8 +84,8 @@ public class PreKeyUtil { } } - public static PreKeyRecord generateLastResortKey(Context context, MasterSecret masterSecret) { - PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret); + public static PreKeyRecord generateLastResortKey(Context context) { + PreKeyStore preKeyStore = new TextSecurePreKeyStore(context); if (preKeyStore.containsPreKey(Medium.MAX_VALUE)) { try { diff --git a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureAxolotlStore.java b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureAxolotlStore.java index 8da6dcf1a7..51d35aeed2 100644 --- a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureAxolotlStore.java +++ b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureAxolotlStore.java @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.crypto.storage; import android.content.Context; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.whispersystems.libaxolotl.AxolotlAddress; import org.whispersystems.libaxolotl.IdentityKey; import org.whispersystems.libaxolotl.IdentityKeyPair; @@ -25,11 +24,11 @@ public class TextSecureAxolotlStore implements AxolotlStore { private final IdentityKeyStore identityKeyStore; private final SessionStore sessionStore; - public TextSecureAxolotlStore(Context context, MasterSecret masterSecret) { - this.preKeyStore = new TextSecurePreKeyStore(context, masterSecret); - this.signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret); - this.identityKeyStore = new TextSecureIdentityKeyStore(context, masterSecret); - this.sessionStore = new TextSecureSessionStore(context, masterSecret); + public TextSecureAxolotlStore(Context context) { + this.preKeyStore = new TextSecurePreKeyStore(context); + this.signedPreKeyStore = new TextSecurePreKeyStore(context); + this.identityKeyStore = new TextSecureIdentityKeyStore(context); + this.sessionStore = new TextSecureSessionStore(context); } @Override diff --git a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java index 48150934c8..f3a6de3d7c 100644 --- a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java +++ b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.crypto.storage; import android.content.Context; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -13,17 +12,15 @@ import org.whispersystems.libaxolotl.state.IdentityKeyStore; public class TextSecureIdentityKeyStore implements IdentityKeyStore { - private final Context context; - private final MasterSecret masterSecret; + private final Context context; - public TextSecureIdentityKeyStore(Context context, MasterSecret masterSecret) { - this.context = context; - this.masterSecret = masterSecret; + public TextSecureIdentityKeyStore(Context context) { + this.context = context; } @Override public IdentityKeyPair getIdentityKeyPair() { - return IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + return IdentityKeyUtil.getIdentityKeyPair(context); } @Override @@ -34,13 +31,13 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore { @Override public void saveIdentity(String name, IdentityKey identityKey) { long recipientId = RecipientFactory.getRecipientsFromString(context, name, true).getPrimaryRecipient().getRecipientId(); - DatabaseFactory.getIdentityDatabase(context).saveIdentity(masterSecret, recipientId, identityKey); + DatabaseFactory.getIdentityDatabase(context).saveIdentity(recipientId, identityKey); } @Override public boolean isTrustedIdentity(String name, IdentityKey identityKey) { long recipientId = RecipientFactory.getRecipientsFromString(context, name, true).getPrimaryRecipient().getRecipientId(); return DatabaseFactory.getIdentityDatabase(context) - .isValidIdentity(masterSecret, recipientId, identityKey); + .isValidIdentity(recipientId, identityKey); } } diff --git a/src/org/thoughtcrime/securesms/crypto/storage/TextSecurePreKeyStore.java b/src/org/thoughtcrime/securesms/crypto/storage/TextSecurePreKeyStore.java index effc2c893a..46858fdf5f 100644 --- a/src/org/thoughtcrime/securesms/crypto/storage/TextSecurePreKeyStore.java +++ b/src/org/thoughtcrime/securesms/crypto/storage/TextSecurePreKeyStore.java @@ -1,6 +1,8 @@ package org.thoughtcrime.securesms.crypto.storage; import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.Log; import org.thoughtcrime.securesms.crypto.MasterCipher; @@ -28,14 +30,19 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore { public static final String SIGNED_PREKEY_DIRECTORY = "signed_prekeys"; - private static final int CURRENT_VERSION_MARKER = 1; + private static final int PLAINTEXT_VERSION = 2; + private static final int CURRENT_VERSION_MARKER = 2; private static final Object FILE_LOCK = new Object(); private static final String TAG = TextSecurePreKeyStore.class.getSimpleName(); - private final Context context; - private final MasterSecret masterSecret; + @NonNull private final Context context; + @Nullable private final MasterSecret masterSecret; - public TextSecurePreKeyStore(Context context, MasterSecret masterSecret) { + public TextSecurePreKeyStore(@NonNull Context context) { + this(context, null); + } + + public TextSecurePreKeyStore(@NonNull Context context, @Nullable MasterSecret masterSecret) { this.context = context; this.masterSecret = masterSecret; } @@ -129,28 +136,65 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore { record.delete(); } + public void migrateRecords() { + synchronized (FILE_LOCK) { + File preKeyRecords = getPreKeyDirectory(); + + for (File preKeyRecord : preKeyRecords.listFiles()) { + try { + int preKeyId = Integer.parseInt(preKeyRecord.getName()); + PreKeyRecord record = loadPreKey(preKeyId); + + storePreKey(preKeyId, record); + } catch (InvalidKeyIdException | NumberFormatException e) { + Log.w(TAG, e); + } + } + + File signedPreKeyRecords = getSignedPreKeyDirectory(); + + for (File signedPreKeyRecord : signedPreKeyRecords.listFiles()) { + try { + int signedPreKeyId = Integer.parseInt(signedPreKeyRecord.getName()); + SignedPreKeyRecord record = loadSignedPreKey(signedPreKeyId); + + storeSignedPreKey(signedPreKeyId, record); + } catch (InvalidKeyIdException | NumberFormatException e) { + Log.w(TAG, e); + } + } + } + } + private byte[] loadSerializedRecord(File recordFile) throws IOException, InvalidMessageException { - MasterCipher masterCipher = new MasterCipher(masterSecret); FileInputStream fin = new FileInputStream(recordFile); int recordVersion = readInteger(fin); - if (recordVersion != CURRENT_VERSION_MARKER) { + if (recordVersion > CURRENT_VERSION_MARKER) { throw new AssertionError("Invalid version: " + recordVersion); } - return masterCipher.decryptBytes(readBlob(fin)); + byte[] serializedRecord = readBlob(fin); + + if (recordVersion < PLAINTEXT_VERSION && masterSecret != null) { + MasterCipher masterCipher = new MasterCipher(masterSecret); + serializedRecord = masterCipher.decryptBytes(serializedRecord); + } else if (recordVersion < PLAINTEXT_VERSION) { + throw new AssertionError("Migration didn't happen!"); + } + + return serializedRecord; } private void storeSerializedRecord(File file, byte[] serialized) throws IOException { - MasterCipher masterCipher = new MasterCipher(masterSecret); - RandomAccessFile recordFile = new RandomAccessFile(file, "rw"); - FileChannel out = recordFile.getChannel(); + RandomAccessFile recordFile = new RandomAccessFile(file, "rw"); + FileChannel out = recordFile.getChannel(); out.position(0); writeInteger(CURRENT_VERSION_MARKER, out); - writeBlob(masterCipher.encryptBytes(serialized), out); + writeBlob(serialized, out); out.truncate(out.position()); recordFile.close(); } diff --git a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java index 30e62e5310..05b36c65d0 100644 --- a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java +++ b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java @@ -1,6 +1,8 @@ package org.thoughtcrime.securesms.crypto.storage; import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.Log; import org.thoughtcrime.securesms.crypto.MasterCipher; @@ -34,37 +36,46 @@ public class TextSecureSessionStore implements SessionStore { private static final int SINGLE_STATE_VERSION = 1; private static final int ARCHIVE_STATES_VERSION = 2; - private static final int CURRENT_VERSION = 2; + private static final int PLAINTEXT_VERSION = 3; + private static final int CURRENT_VERSION = 3; - private final Context context; - private final MasterSecret masterSecret; + @NonNull private final Context context; + @Nullable private final MasterSecret masterSecret; - public TextSecureSessionStore(Context context, MasterSecret masterSecret) { + public TextSecureSessionStore(@NonNull Context context) { + this(context, null); + } + + public TextSecureSessionStore(@NonNull Context context, @Nullable MasterSecret masterSecret) { this.context = context.getApplicationContext(); this.masterSecret = masterSecret; } @Override - public SessionRecord loadSession(AxolotlAddress address) { + public SessionRecord loadSession(@NonNull AxolotlAddress address) { synchronized (FILE_LOCK) { try { - MasterCipher cipher = new MasterCipher(masterSecret); - FileInputStream in = new FileInputStream(getSessionFile(address)); - - int versionMarker = readInteger(in); + FileInputStream in = new FileInputStream(getSessionFile(address)); + int versionMarker = readInteger(in); if (versionMarker > CURRENT_VERSION) { throw new AssertionError("Unknown version: " + versionMarker); } - byte[] serialized = cipher.decryptBytes(readBlob(in)); + byte[] serialized = readBlob(in); in.close(); + if (versionMarker < PLAINTEXT_VERSION && masterSecret != null) { + serialized = new MasterCipher(masterSecret).decryptBytes(serialized); + } else if (versionMarker < PLAINTEXT_VERSION) { + throw new AssertionError("Session didn't get migrated: (" + versionMarker + "," + address + ")"); + } + if (versionMarker == SINGLE_STATE_VERSION) { SessionStructure sessionStructure = SessionStructure.parseFrom(serialized); SessionState sessionState = new SessionState(sessionStructure); return new SessionRecord(sessionState); - } else if (versionMarker == ARCHIVE_STATES_VERSION) { + } else if (versionMarker >= ARCHIVE_STATES_VERSION) { return new SessionRecord(serialized); } else { throw new AssertionError("Unknown version: " + versionMarker); @@ -77,16 +88,15 @@ public class TextSecureSessionStore implements SessionStore { } @Override - public void storeSession(AxolotlAddress address, SessionRecord record) { + public void storeSession(@NonNull AxolotlAddress address, @NonNull SessionRecord record) { synchronized (FILE_LOCK) { try { - MasterCipher masterCipher = new MasterCipher(masterSecret); RandomAccessFile sessionFile = new RandomAccessFile(getSessionFile(address), "rw"); FileChannel out = sessionFile.getChannel(); out.position(0); writeInteger(CURRENT_VERSION, out); - writeBlob(masterCipher.encryptBytes(record.serialize()), out); + writeBlob(record.serialize(), out); out.truncate(out.position()); sessionFile.close(); @@ -143,6 +153,23 @@ public class TextSecureSessionStore implements SessionStore { return results; } + public void migrateSessions() { + synchronized (FILE_LOCK) { + File directory = getSessionDirectory(); + + for (File session : directory.listFiles()) { + if (session.isFile()) { + AxolotlAddress address = getAddressName(session); + + if (address != null) { + SessionRecord sessionRecord = loadSession(address); + storeSession(address, sessionRecord); + } + } + } + } + } + private File getSessionFile(AxolotlAddress address) { return new File(getSessionDirectory(), getSessionName(address)); } @@ -168,6 +195,23 @@ public class TextSecureSessionStore implements SessionStore { return recipientId + (deviceId == TextSecureAddress.DEFAULT_DEVICE_ID ? "" : "." + deviceId); } + private @Nullable AxolotlAddress getAddressName(File sessionFile) { + try { + String[] parts = sessionFile.getName().split("[.]"); + Recipient recipient = RecipientFactory.getRecipientForId(context, Integer.valueOf(parts[0]), true); + + int deviceId; + + if (parts.length > 1) deviceId = Integer.parseInt(parts[1]); + else deviceId = TextSecureAddress.DEFAULT_DEVICE_ID; + + return new AxolotlAddress(recipient.getNumber(), deviceId); + } catch (NumberFormatException e) { + Log.w(TAG, e); + return null; + } + } + private byte[] readBlob(FileInputStream in) throws IOException { int length = readInteger(in); byte[] blobBytes = new byte[length]; diff --git a/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java b/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java index c8878edf79..0d8acec5c2 100644 --- a/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java @@ -19,12 +19,16 @@ package org.thoughtcrime.securesms.database; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteOpenHelper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.Log; import android.util.Pair; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher; import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecretUnion; +import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.database.model.DisplayRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.sms.IncomingTextMessage; @@ -60,57 +64,80 @@ public class EncryptingSmsDatabase extends SmsDatabase { return ciphertext; } - public long insertMessageOutbox(MasterSecret masterSecret, long threadId, + public long insertMessageOutbox(MasterSecretUnion masterSecret, long threadId, OutgoingTextMessage message, boolean forceSms, long timestamp) { long type = Types.BASE_OUTBOX_TYPE; - message = message.withBody(getEncryptedBody(masterSecret, message.getMessageBody())); - type |= Types.ENCRYPTION_SYMMETRIC_BIT; + + if (masterSecret.getMasterSecret().isPresent()) { + message = message.withBody(getEncryptedBody(masterSecret.getMasterSecret().get(), message.getMessageBody())); + type |= Types.ENCRYPTION_SYMMETRIC_BIT; + } else { + message = message.withBody(getAsymmetricEncryptedBody(masterSecret.getAsymmetricMasterSecret().get(), message.getMessageBody())); + type |= Types.ENCRYPTION_ASYMMETRIC_BIT; + } return insertMessageOutbox(threadId, message, type, forceSms, timestamp); } - public Pair insertMessageInbox(MasterSecret masterSecret, - IncomingTextMessage message) + public Pair insertMessageInbox(@NonNull MasterSecretUnion masterSecret, + @NonNull IncomingTextMessage message) { - long type = Types.BASE_INBOX_TYPE; - - if (masterSecret == null && message.isSecureMessage()) { - type |= Types.ENCRYPTION_REMOTE_BIT; + if (masterSecret.getMasterSecret().isPresent()) { + return insertMessageInbox(masterSecret.getMasterSecret().get(), message); } else { - type |= Types.ENCRYPTION_SYMMETRIC_BIT; - message = message.withMessageBody(getEncryptedBody(masterSecret, message.getMessageBody())); + return insertMessageInbox(masterSecret.getAsymmetricMasterSecret().get(), message); } + } + + private Pair insertMessageInbox(@NonNull MasterSecret masterSecret, + @NonNull IncomingTextMessage message) + { + long type = Types.BASE_INBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT; + + message = message.withMessageBody(getEncryptedBody(masterSecret, message.getMessageBody())); return insertMessageInbox(message, type); } - public Pair insertMessageInbox(AsymmetricMasterSecret masterSecret, - IncomingTextMessage message) + private Pair insertMessageInbox(@NonNull AsymmetricMasterSecret masterSecret, + @NonNull IncomingTextMessage message) { - long type = Types.BASE_INBOX_TYPE; + long type = Types.BASE_INBOX_TYPE | Types.ENCRYPTION_ASYMMETRIC_BIT; - if (message.isSecureMessage()) { - type |= Types.ENCRYPTION_REMOTE_BIT; - } else { - message = message.withMessageBody(getAsymmetricEncryptedBody(masterSecret, message.getMessageBody())); - type |= Types.ENCRYPTION_ASYMMETRIC_BIT; - } + message = message.withMessageBody(getAsymmetricEncryptedBody(masterSecret, message.getMessageBody())); return insertMessageInbox(message, type); } - public Pair updateBundleMessageBody(MasterSecret masterSecret, long messageId, String body) { - String encryptedBody = getEncryptedBody(masterSecret, body); - return updateMessageBodyAndType(messageId, encryptedBody, Types.TOTAL_MASK, - Types.BASE_INBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT | Types.SECURE_MESSAGE_BIT); + public Pair updateBundleMessageBody(MasterSecretUnion masterSecret, long messageId, String body) { + long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT; + String encryptedBody; + + if (masterSecret.getMasterSecret().isPresent()) { + encryptedBody = getEncryptedBody(masterSecret.getMasterSecret().get(), body); + type |= Types.ENCRYPTION_SYMMETRIC_BIT; + } else { + encryptedBody = getAsymmetricEncryptedBody(masterSecret.getAsymmetricMasterSecret().get(), body); + type |= Types.ENCRYPTION_ASYMMETRIC_BIT; + } + + return updateMessageBodyAndType(messageId, encryptedBody, Types.TOTAL_MASK, type); } - public void updateMessageBody(MasterSecret masterSecret, long messageId, String body) { - String encryptedBody = getEncryptedBody(masterSecret, body); - updateMessageBodyAndType(messageId, encryptedBody, Types.ENCRYPTION_MASK, - Types.ENCRYPTION_SYMMETRIC_BIT); + public void updateMessageBody(MasterSecretUnion masterSecret, long messageId, String body) { + long type; + + if (masterSecret.getMasterSecret().isPresent()) { + body = getEncryptedBody(masterSecret.getMasterSecret().get(), body); + type = Types.ENCRYPTION_SYMMETRIC_BIT; + } else { + body = getAsymmetricEncryptedBody(masterSecret.getAsymmetricMasterSecret().get(), body); + type = Types.ENCRYPTION_ASYMMETRIC_BIT; + } + + updateMessageBodyAndType(messageId, body, Types.ENCRYPTION_MASK, type); } public Reader getMessages(MasterSecret masterSecret, int skip, int limit) { diff --git a/src/org/thoughtcrime/securesms/database/IdentityDatabase.java b/src/org/thoughtcrime/securesms/database/IdentityDatabase.java index ea039a9c87..111cf5bb01 100644 --- a/src/org/thoughtcrime/securesms/database/IdentityDatabase.java +++ b/src/org/thoughtcrime/securesms/database/IdentityDatabase.java @@ -29,8 +29,6 @@ import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.Base64; import org.whispersystems.libaxolotl.IdentityKey; import org.whispersystems.libaxolotl.InvalidKeyException; -import org.thoughtcrime.securesms.crypto.MasterCipher; -import org.thoughtcrime.securesms.crypto.MasterSecret; import java.io.IOException; @@ -42,13 +40,11 @@ public class IdentityDatabase extends Database { private static final String ID = "_id"; public static final String RECIPIENT = "recipient"; public static final String IDENTITY_KEY = "key"; - public static final String MAC = "mac"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + RECIPIENT + " INTEGER UNIQUE, " + - IDENTITY_KEY + " TEXT, " + - MAC + " TEXT);"; + IDENTITY_KEY + " TEXT);"; public IdentityDatabase(Context context, SQLiteOpenHelper databaseHelper) { super(context, databaseHelper); @@ -64,28 +60,19 @@ public class IdentityDatabase extends Database { return cursor; } - public boolean isValidIdentity(MasterSecret masterSecret, - long recipientId, + public boolean isValidIdentity(long recipientId, IdentityKey theirIdentity) { - SQLiteDatabase database = databaseHelper.getReadableDatabase(); - MasterCipher masterCipher = new MasterCipher(masterSecret); - Cursor cursor = null; + SQLiteDatabase database = databaseHelper.getReadableDatabase(); + Cursor cursor = null; try { cursor = database.query(TABLE_NAME, null, RECIPIENT + " = ?", new String[] {recipientId+""}, null, null,null); if (cursor != null && cursor.moveToFirst()) { - String serializedIdentity = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY)); - String mac = cursor.getString(cursor.getColumnIndexOrThrow(MAC)); - - if (!masterCipher.verifyMacFor(recipientId + serializedIdentity, Base64.decode(mac))) { - Log.w("IdentityDatabase", "MAC failed"); - return false; - } - - IdentityKey ourIdentity = new IdentityKey(Base64.decode(serializedIdentity), 0); + String serializedIdentity = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY)); + IdentityKey ourIdentity = new IdentityKey(Base64.decode(serializedIdentity), 0); return ourIdentity.equals(theirIdentity); } else { @@ -104,18 +91,14 @@ public class IdentityDatabase extends Database { } } - public void saveIdentity(MasterSecret masterSecret, long recipientId, IdentityKey identityKey) + public void saveIdentity(long recipientId, IdentityKey identityKey) { - SQLiteDatabase database = databaseHelper.getWritableDatabase(); - MasterCipher masterCipher = new MasterCipher(masterSecret); - String identityKeyString = Base64.encodeBytes(identityKey.serialize()); - String macString = Base64.encodeBytes(masterCipher.getMacFor(recipientId + - identityKeyString)); + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + String identityKeyString = Base64.encodeBytes(identityKey.serialize()); ContentValues contentValues = new ContentValues(); contentValues.put(RECIPIENT, recipientId); contentValues.put(IDENTITY_KEY, identityKeyString); - contentValues.put(MAC, macString); database.replace(TABLE_NAME, null, contentValues); @@ -129,17 +112,15 @@ public class IdentityDatabase extends Database { context.getContentResolver().notifyChange(CHANGE_URI, null); } - public Reader readerFor(MasterSecret masterSecret, Cursor cursor) { - return new Reader(masterSecret, cursor); + public Reader readerFor(Cursor cursor) { + return new Reader(cursor); } public class Reader { private final Cursor cursor; - private final MasterCipher cipher; - public Reader(MasterSecret masterSecret, Cursor cursor) { + public Reader(Cursor cursor) { this.cursor = cursor; - this.cipher = new MasterCipher(masterSecret); } public Identity getCurrent() { @@ -147,14 +128,9 @@ public class IdentityDatabase extends Database { Recipients recipients = RecipientFactory.getRecipientsForIds(context, new long[]{recipientId}, true); try { - String identityKeyString = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY)); - String mac = cursor.getString(cursor.getColumnIndexOrThrow(MAC)); + String identityKeyString = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY)); + IdentityKey identityKey = new IdentityKey(Base64.decode(identityKeyString), 0); - if (!cipher.verifyMacFor(recipientId + identityKeyString, Base64.decode(mac))) { - return new Identity(recipients, null); - } - - IdentityKey identityKey = new IdentityKey(Base64.decode(identityKeyString), 0); return new Identity(recipients, identityKey); } catch (IOException e) { Log.w("IdentityDatabase", e); diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 8ec2419a63..6c434bf1d8 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -32,8 +32,10 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher; import org.thoughtcrime.securesms.crypto.MasterCipher; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecretUnion; import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.database.documents.NetworkFailureList; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; @@ -357,12 +359,11 @@ public class MmsDatabase extends MessagingDatabase { return cursor; } - public void updateResponseStatus(long messageId, int status) { - SQLiteDatabase database = databaseHelper.getWritableDatabase(); - ContentValues contentValues = new ContentValues(); - contentValues.put(RESPONSE_STATUS, status); + public Reader getDecryptInProgressMessages(MasterSecret masterSecret) { + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + String where = MESSAGE_BOX + " & " + (Types.ENCRYPTION_ASYMMETRIC_BIT) + " != 0"; - database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {messageId + ""}); + return readerFor(masterSecret, db.query(TABLE_NAME, MMS_PROJECTION, where, null, null, null, null)); } private void updateMailboxBitmask(long id, long maskOff, long maskOn) { @@ -417,15 +418,6 @@ public class MmsDatabase extends MessagingDatabase { notifyConversationListeners(getThreadIdForMessage(messageId)); } - public void markDeliveryStatus(long messageId, int status) { - SQLiteDatabase database = databaseHelper.getWritableDatabase(); - ContentValues contentValues = new ContentValues(); - contentValues.put(STATUS, status); - - database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {messageId + ""}); - notifyConversationListeners(getThreadIdForMessage(messageId)); - } - public void markAsNoSession(long messageId, long threadId) { updateMailboxBitmask(messageId, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_NO_SESSION_BIT); notifyConversationListeners(threadId); @@ -474,6 +466,36 @@ public class MmsDatabase extends MessagingDatabase { database.update(TABLE_NAME, contentValues, null, null); } + public void updateMessageBody(MasterSecretUnion masterSecret, long messageId, String body) { + body = getEncryptedBody(masterSecret, body); + + long type; + + if (masterSecret.getMasterSecret().isPresent()) { + type = Types.ENCRYPTION_SYMMETRIC_BIT; + } else { + type = Types.ENCRYPTION_ASYMMETRIC_BIT; + } + + updateMessageBodyAndType(messageId, body, Types.ENCRYPTION_MASK, type); + } + + private Pair updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + db.execSQL("UPDATE " + TABLE_NAME + " SET " + BODY + " = ?, " + + MESSAGE_BOX + " = (" + MESSAGE_BOX + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + ") " + + "WHERE " + ID + " = ?", + new String[] {body, messageId + ""}); + + long threadId = getThreadIdForMessage(messageId); + + DatabaseFactory.getThreadDatabase(context).update(threadId); + notifyConversationListeners(threadId); + notifyConversationListListeners(); + + return new Pair<>(messageId, threadId); + } + public Optional getNotification(long messageId) { SQLiteDatabase db = databaseHelper.getReadableDatabase(); MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context); @@ -544,15 +566,6 @@ public class MmsDatabase extends MessagingDatabase { } } - public Reader getNotificationsWithDownloadState(MasterSecret masterSecret, long state) { - SQLiteDatabase database = databaseHelper.getReadableDatabase(); - String selection = STATUS + " = ?"; - String[] selectionArgs = new String[]{state + ""}; - - Cursor cursor = database.query(TABLE_NAME, MMS_PROJECTION, selection, selectionArgs, null, null, null); - return new Reader(masterSecret, cursor); - } - public long copyMessageInbox(MasterSecret masterSecret, long messageId) throws MmsException { try { SendReq request = getOutgoingMessage(masterSecret, messageId); @@ -563,15 +576,17 @@ public class MmsDatabase extends MessagingDatabase { contentValues.put(READ, 1); contentValues.put(DATE_RECEIVED, contentValues.getAsLong(DATE_SENT)); - return insertMediaMessage(masterSecret, request.getPduHeaders(), + return insertMediaMessage(new MasterSecretUnion(masterSecret), request.getPduHeaders(), request.getBody(), contentValues); } catch (NoSuchMessageException e) { throw new MmsException(e); } } - private Pair insertMessageInbox(MasterSecret masterSecret, IncomingMediaMessage retrieved, - String contentLocation, long threadId, long mailbox) + private Pair insertMessageInbox(MasterSecretUnion masterSecret, + IncomingMediaMessage retrieved, + String contentLocation, + long threadId, long mailbox) throws MmsException { PduHeaders headers = retrieved.getPduHeaders(); @@ -614,35 +629,44 @@ public class MmsDatabase extends MessagingDatabase { return new Pair<>(messageId, threadId); } - public Pair insertMessageInbox(MasterSecret masterSecret, + public Pair insertMessageInbox(MasterSecretUnion masterSecret, IncomingMediaMessage retrieved, String contentLocation, long threadId) throws MmsException { - return insertMessageInbox(masterSecret, retrieved, contentLocation, threadId, - Types.BASE_INBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT | - (retrieved.isPushMessage() ? Types.PUSH_MESSAGE_BIT : 0)); + long type = Types.BASE_INBOX_TYPE; + + if (masterSecret.getMasterSecret().isPresent()) { + type |= Types.ENCRYPTION_SYMMETRIC_BIT; + } else { + type |= Types.ENCRYPTION_ASYMMETRIC_BIT; + } + + if (retrieved.isPushMessage()) { + type |= Types.PUSH_MESSAGE_BIT; + } + + return insertMessageInbox(masterSecret, retrieved, contentLocation, threadId, type); } - public Pair insertSecureMessageInbox(MasterSecret masterSecret, - IncomingMediaMessage retrieved, - String contentLocation, long threadId) - throws MmsException - { - return insertMessageInbox(masterSecret, retrieved, contentLocation, threadId, - Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT | - Types.ENCRYPTION_REMOTE_BIT); - } - - public Pair insertSecureDecryptedMessageInbox(MasterSecret masterSecret, + public Pair insertSecureDecryptedMessageInbox(MasterSecretUnion masterSecret, IncomingMediaMessage retrieved, long threadId) throws MmsException { - return insertMessageInbox(masterSecret, retrieved, "", threadId, - Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT | - Types.ENCRYPTION_SYMMETRIC_BIT | - (retrieved.isPushMessage() ? Types.PUSH_MESSAGE_BIT : 0)); + long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT; + + if (masterSecret.getMasterSecret().isPresent()) { + type |= Types.ENCRYPTION_SYMMETRIC_BIT; + } else { + type |= Types.ENCRYPTION_ASYMMETRIC_BIT; + } + + if (retrieved.isPushMessage()) { + type |= Types.PUSH_MESSAGE_BIT; + } + + return insertMessageInbox(masterSecret, retrieved, "", threadId, type); } public Pair insertMessageInbox(@NonNull NotificationInd notification) { @@ -680,11 +704,15 @@ public class MmsDatabase extends MessagingDatabase { jobManager.add(new TrimThreadJob(context, threadId)); } - public long insertMessageOutbox(MasterSecret masterSecret, OutgoingMediaMessage message, + public long insertMessageOutbox(@NonNull MasterSecretUnion masterSecret, + @NonNull OutgoingMediaMessage message, long threadId, boolean forceSms, long timestamp) throws MmsException { - long type = Types.BASE_OUTBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT; + long type = Types.BASE_OUTBOX_TYPE; + + if (masterSecret.getMasterSecret().isPresent()) type |= Types.ENCRYPTION_SYMMETRIC_BIT; + else type |= Types.ENCRYPTION_ASYMMETRIC_BIT; if (message.isSecure()) type |= Types.SECURE_MESSAGE_BIT; if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT; @@ -727,14 +755,23 @@ public class MmsDatabase extends MessagingDatabase { } } - long messageId = insertMediaMessage(masterSecret, sendRequest.getPduHeaders(), + long messageId = insertMediaMessage(masterSecret, + sendRequest.getPduHeaders(), sendRequest.getBody(), contentValues); jobManager.add(new TrimThreadJob(context, threadId)); return messageId; } - private long insertMediaMessage(MasterSecret masterSecret, + private String getEncryptedBody(MasterSecretUnion masterSecret, String body) { + if (masterSecret.getMasterSecret().isPresent()) { + return new MasterCipher(masterSecret.getMasterSecret().get()).encryptBody(body); + } else { + return new AsymmetricMasterCipher(masterSecret.getAsymmetricMasterSecret().get()).encryptBody(body); + } + } + + private long insertMediaMessage(MasterSecretUnion masterSecret, PduHeaders headers, PduBody body, ContentValues contentValues) @@ -744,12 +781,14 @@ public class MmsDatabase extends MessagingDatabase { PartDatabase partsDatabase = DatabaseFactory.getPartDatabase(context); MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context); - if (Types.isSymmetricEncryption(contentValues.getAsLong(MESSAGE_BOX))) { + if (Types.isSymmetricEncryption(contentValues.getAsLong(MESSAGE_BOX)) || + Types.isAsymmetricEncryption(contentValues.getAsLong(MESSAGE_BOX))) + { String messageText = PartParser.getMessageText(body); body = PartParser.getSupportedMediaParts(body); if (!TextUtils.isEmpty(messageText)) { - contentValues.put(BODY, new MasterCipher(masterSecret).encryptBody(messageText)); + contentValues.put(BODY, getEncryptedBody(masterSecret, messageText)); } } @@ -1105,6 +1144,8 @@ public class MmsDatabase extends MessagingDatabase { return new DisplayRecord.Body(masterCipher.decryptBody(body), true); } else if (!TextUtils.isEmpty(body) && masterCipher == null && Types.isSymmetricEncryption(box)) { return new DisplayRecord.Body(body, false); + } else if (!TextUtils.isEmpty(body) && Types.isAsymmetricEncryption(box)) { + return new DisplayRecord.Body(body, false); } else { return new DisplayRecord.Body(body == null ? "" : body, true); } diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 882b05b7ce..9eaceb0bac 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -175,9 +175,7 @@ public interface MmsSmsColumns { } public static boolean isDecryptInProgressType(long type) { - return - (type & ENCRYPTION_REMOTE_BIT) != 0 || - (type & ENCRYPTION_ASYMMETRIC_BIT) != 0; + return (type & ENCRYPTION_ASYMMETRIC_BIT) != 0; } public static boolean isNoRemoteSessionType(long type) { @@ -185,7 +183,8 @@ public interface MmsSmsColumns { } public static boolean isLegacyType(long type) { - return (type & ENCRYPTION_REMOTE_LEGACY_BIT) != 0; + return (type & ENCRYPTION_REMOTE_LEGACY_BIT) != 0 || + (type & ENCRYPTION_REMOTE_BIT) != 0; } public static long translateFromSystemBaseType(long theirType) { diff --git a/src/org/thoughtcrime/securesms/database/PartDatabase.java b/src/org/thoughtcrime/securesms/database/PartDatabase.java index 5207ed8eb2..f87fb6cec0 100644 --- a/src/org/thoughtcrime/securesms/database/PartDatabase.java +++ b/src/org/thoughtcrime/securesms/database/PartDatabase.java @@ -30,6 +30,7 @@ import android.util.Pair; import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecretUnion; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.util.BitmapDecodingException; import org.thoughtcrime.securesms.util.MediaUtil; @@ -222,7 +223,7 @@ public class PartDatabase extends Database { } } - void insertParts(MasterSecret masterSecret, long mmsId, PduBody body) throws MmsException { + void insertParts(MasterSecretUnion masterSecret, long mmsId, PduBody body) throws MmsException { for (int i=0;i partData = null; - if (part.getData() != null || part.getDataUri() != null) { - partData = writePartData(masterSecret, part); + if ((part.getData() != null || part.getDataUri() != null) && masterSecret.getMasterSecret().isPresent()) { + partData = writePartData(masterSecret.getMasterSecret().get(), part); Log.w(TAG, "Wrote part to file: " + partData.first.getAbsolutePath()); } @@ -453,12 +454,12 @@ public class PartDatabase extends Database { long partRowId = database.insert(TABLE_NAME, null, contentValues); PartId partId = new PartId(partRowId, part.getUniqueId()); - if (thumbnail != null) { + if (thumbnail != null && masterSecret.getMasterSecret().isPresent()) { Log.w(TAG, "inserting pre-generated thumbnail"); ThumbnailData data = new ThumbnailData(thumbnail); - updatePartThumbnail(masterSecret, partId, part, data.toDataStream(), data.getAspectRatio()); + updatePartThumbnail(masterSecret.getMasterSecret().get(), partId, part, data.toDataStream(), data.getAspectRatio()); } else if (!part.isInProgress()) { - thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, partId)); + thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret.getMasterSecret().get(), partId)); } return partId; diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index eef506f11b..716b38cd88 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -213,10 +213,6 @@ public class SmsDatabase extends MessagingDatabase { updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_NO_SESSION_BIT); } - public void markAsDecrypting(long id) { - updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_BIT); - } - public void markAsLegacyVersion(long id) { updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_LEGACY_BIT); } @@ -461,7 +457,7 @@ public class SmsDatabase extends MessagingDatabase { } public Cursor getDecryptInProgressMessages() { - String where = TYPE + " & " + (Types.ENCRYPTION_REMOTE_BIT | Types.ENCRYPTION_ASYMMETRIC_BIT) + " != 0"; + String where = TYPE + " & " + (Types.ENCRYPTION_ASYMMETRIC_BIT) + " != 0"; SQLiteDatabase db = databaseHelper.getReadableDatabase(); return db.query(TABLE_NAME, MESSAGE_PROJECTION, where, null, null, null, null); } diff --git a/src/org/thoughtcrime/securesms/dependencies/AxolotlStorageModule.java b/src/org/thoughtcrime/securesms/dependencies/AxolotlStorageModule.java index 63bc496f3b..5e5d51301a 100644 --- a/src/org/thoughtcrime/securesms/dependencies/AxolotlStorageModule.java +++ b/src/org/thoughtcrime/securesms/dependencies/AxolotlStorageModule.java @@ -22,13 +22,13 @@ public class AxolotlStorageModule { @Provides SignedPreKeyStoreFactory provideSignedPreKeyStoreFactory() { return new SignedPreKeyStoreFactory() { @Override - public SignedPreKeyStore create(MasterSecret masterSecret) { - return new TextSecureAxolotlStore(context, masterSecret); + public SignedPreKeyStore create() { + return new TextSecureAxolotlStore(context); } }; } public static interface SignedPreKeyStoreFactory { - public SignedPreKeyStore create(MasterSecret masterSecret); + public SignedPreKeyStore create(); } } diff --git a/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java index f333229eec..7845aea745 100644 --- a/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java +++ b/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java @@ -4,7 +4,6 @@ import android.content.Context; import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.DeviceListActivity; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore; import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob; import org.thoughtcrime.securesms.jobs.CleanPreKeysJob; @@ -61,12 +60,12 @@ public class TextSecureCommunicationModule { @Provides TextSecureMessageSenderFactory provideTextSecureMessageSenderFactory() { return new TextSecureMessageSenderFactory() { @Override - public TextSecureMessageSender create(MasterSecret masterSecret) { + public TextSecureMessageSender create() { return new TextSecureMessageSender(BuildConfig.PUSH_URL, new TextSecurePushTrustStore(context), TextSecurePreferences.getLocalNumber(context), TextSecurePreferences.getPushServerPassword(context), - new TextSecureAxolotlStore(context, masterSecret), + new TextSecureAxolotlStore(context), Optional.of((TextSecureMessageSender.EventListener) new SecurityEventListener(context))); } @@ -80,7 +79,7 @@ public class TextSecureCommunicationModule { } public static interface TextSecureMessageSenderFactory { - public TextSecureMessageSender create(MasterSecret masterSecret); + public TextSecureMessageSender create(); } private static class DynamicCredentialsProvider implements CredentialsProvider { diff --git a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java index 41a0d59d3c..f36191ed11 100644 --- a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java +++ b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java @@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.groups; import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.Log; import android.util.Pair; @@ -9,6 +11,8 @@ import com.google.protobuf.ByteString; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecretUnion; +import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.GroupDatabase; @@ -36,10 +40,10 @@ public class GroupMessageProcessor { private static final String TAG = GroupMessageProcessor.class.getSimpleName(); - public static void process(Context context, - MasterSecret masterSecret, - TextSecureEnvelope envelope, - TextSecureDataMessage message) + public static void process(@NonNull Context context, + @NonNull MasterSecretUnion masterSecret, + @NonNull TextSecureEnvelope envelope, + @NonNull TextSecureDataMessage message) { if (!message.getGroupInfo().isPresent() || message.getGroupInfo().get().getGroupId() == null) { Log.w(TAG, "Received group message with no id! Ignoring..."); @@ -62,10 +66,10 @@ public class GroupMessageProcessor { } } - private static void handleGroupCreate(Context context, - MasterSecret masterSecret, - TextSecureEnvelope envelope, - TextSecureGroup group) + private static void handleGroupCreate(@NonNull Context context, + @NonNull MasterSecretUnion masterSecret, + @NonNull TextSecureEnvelope envelope, + @NonNull TextSecureGroup group) { GroupDatabase database = DatabaseFactory.getGroupDatabase(context); byte[] id = group.getGroupId(); @@ -81,11 +85,11 @@ public class GroupMessageProcessor { storeMessage(context, masterSecret, envelope, group, builder.build()); } - private static void handleGroupUpdate(Context context, - MasterSecret masterSecret, - TextSecureEnvelope envelope, - TextSecureGroup group, - GroupRecord groupRecord) + private static void handleGroupUpdate(@NonNull Context context, + @NonNull MasterSecretUnion masterSecret, + @NonNull TextSecureEnvelope envelope, + @NonNull TextSecureGroup group, + @NonNull GroupRecord groupRecord) { GroupDatabase database = DatabaseFactory.getGroupDatabase(context); @@ -131,11 +135,11 @@ public class GroupMessageProcessor { storeMessage(context, masterSecret, envelope, group, builder.build()); } - private static void handleGroupLeave(Context context, - MasterSecret masterSecret, - TextSecureEnvelope envelope, - TextSecureGroup group, - GroupRecord record) + private static void handleGroupLeave(@NonNull Context context, + @NonNull MasterSecretUnion masterSecret, + @NonNull TextSecureEnvelope envelope, + @NonNull TextSecureGroup group, + @NonNull GroupRecord record) { GroupDatabase database = DatabaseFactory.getGroupDatabase(context); byte[] id = group.getGroupId(); @@ -152,9 +156,11 @@ public class GroupMessageProcessor { } - private static void storeMessage(Context context, MasterSecret masterSecret, - TextSecureEnvelope envelope, TextSecureGroup group, - GroupContext storage) + private static void storeMessage(@NonNull Context context, + @NonNull MasterSecretUnion masterSecret, + @NonNull TextSecureEnvelope envelope, + @NonNull TextSecureGroup group, + @NonNull GroupContext storage) { if (group.getAvatar().isPresent()) { ApplicationContext.getInstance(context).getJobManager() @@ -167,7 +173,7 @@ public class GroupMessageProcessor { IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body); Pair messageAndThreadId = smsDatabase.insertMessageInbox(masterSecret, groupMessage); - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second); } private static GroupContext.Builder createGroupContext(TextSecureGroup group) { diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java index 85bce1f142..dc02db32b2 100644 --- a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java @@ -3,15 +3,15 @@ package org.thoughtcrime.securesms.jobs; import android.content.Context; import android.util.Log; - -import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecretUtil; +import org.thoughtcrime.securesms.crypto.MediaKey; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.PartDatabase; import org.thoughtcrime.securesms.database.PartDatabase.PartId; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; -import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.requirements.NetworkRequirement; @@ -114,10 +114,10 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable throws InvalidPartException { try { - MasterCipher masterCipher = new MasterCipher(masterSecret); - long id = Long.parseLong(Util.toIsoString(part.getContentLocation())); - byte[] key = masterCipher.decryptBytes(Base64.decode(Util.toIsoString(part.getContentDisposition()))); - String relay = null; + AsymmetricMasterSecret asymmetricMasterSecret = MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret); + long id = Long.parseLong(Util.toIsoString(part.getContentLocation())); + byte[] key = MediaKey.getDecrypted(masterSecret, asymmetricMasterSecret, Util.toIsoString(part.getContentDisposition())); + String relay = null; if (part.getName() != null) { relay = Util.toIsoString(part.getName()); diff --git a/src/org/thoughtcrime/securesms/jobs/CleanPreKeysJob.java b/src/org/thoughtcrime/securesms/jobs/CleanPreKeysJob.java index d3229b6ed1..a00e1a7117 100644 --- a/src/org/thoughtcrime/securesms/jobs/CleanPreKeysJob.java +++ b/src/org/thoughtcrime/securesms/jobs/CleanPreKeysJob.java @@ -51,7 +51,7 @@ public class CleanPreKeysJob extends MasterSecretJob implements InjectableType { @Override public void onRun(MasterSecret masterSecret) throws IOException { try { - SignedPreKeyStore signedPreKeyStore = signedPreKeyStoreFactory.create(masterSecret); + SignedPreKeyStore signedPreKeyStore = signedPreKeyStoreFactory.create(); SignedPreKeyEntity currentSignedPreKey = accountManager.getSignedPreKey(); if (currentSignedPreKey == null) return; diff --git a/src/org/thoughtcrime/securesms/jobs/CreateSignedPreKeyJob.java b/src/org/thoughtcrime/securesms/jobs/CreateSignedPreKeyJob.java index c596ded6c9..b0828ef9fe 100644 --- a/src/org/thoughtcrime/securesms/jobs/CreateSignedPreKeyJob.java +++ b/src/org/thoughtcrime/securesms/jobs/CreateSignedPreKeyJob.java @@ -7,9 +7,8 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.PreKeyUtil; import org.thoughtcrime.securesms.dependencies.InjectableType; -import org.thoughtcrime.securesms.util.ParcelUtil; +import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.jobqueue.EncryptionKeys; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.libaxolotl.IdentityKeyPair; @@ -21,17 +20,19 @@ import java.io.IOException; import javax.inject.Inject; -public class CreateSignedPreKeyJob extends ContextJob implements InjectableType { +public class CreateSignedPreKeyJob extends MasterSecretJob implements InjectableType { + + private static final long serialVersionUID = 1L; private static final String TAG = CreateSignedPreKeyJob.class.getSimpleName(); @Inject transient TextSecureAccountManager accountManager; - public CreateSignedPreKeyJob(Context context, MasterSecret masterSecret) { + public CreateSignedPreKeyJob(Context context) { super(context, JobParameters.newBuilder() .withPersistence() .withRequirement(new NetworkRequirement(context)) - .withEncryption(new EncryptionKeys(ParcelUtil.serialize(masterSecret))) + .withRequirement(new MasterSecretRequirement(context)) .withGroupId(CreateSignedPreKeyJob.class.getSimpleName()) .create()); } @@ -40,16 +41,14 @@ public class CreateSignedPreKeyJob extends ContextJob implements InjectableType public void onAdded() {} @Override - public void onRun() throws IOException { - MasterSecret masterSecret = ParcelUtil.deserialize(getEncryptionKeys().getEncoded(), MasterSecret.CREATOR); - + public void onRun(MasterSecret masterSecret) throws IOException { if (TextSecurePreferences.isSignedPreKeyRegistered(context)) { Log.w(TAG, "Signed prekey already registered..."); return; } - IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); - SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, masterSecret, identityKeyPair); + IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context); + SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKeyPair); accountManager.setSignedPreKey(signedPreKeyRecord); TextSecurePreferences.setSignedPreKeyRegistered(context, true); @@ -59,7 +58,7 @@ public class CreateSignedPreKeyJob extends ContextJob implements InjectableType public void onCanceled() {} @Override - public boolean onShouldRetry(Exception exception) { + public boolean onShouldRetryThrowable(Exception exception) { if (exception instanceof PushNetworkException) return true; return false; } diff --git a/src/org/thoughtcrime/securesms/jobs/DeliveryReceiptJob.java b/src/org/thoughtcrime/securesms/jobs/DeliveryReceiptJob.java index b9c260c989..4e4f04ac85 100644 --- a/src/org/thoughtcrime/securesms/jobs/DeliveryReceiptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/DeliveryReceiptJob.java @@ -47,7 +47,7 @@ public class DeliveryReceiptJob extends ContextJob implements InjectableType { @Override public void onRun() throws IOException { Log.w("DeliveryReceiptJob", "Sending delivery receipt..."); - TextSecureMessageSender messageSender = messageSenderFactory.create(null); + TextSecureMessageSender messageSender = messageSenderFactory.create(); TextSecureAddress textSecureAddress = new TextSecureAddress(destination, Optional.fromNullable(relay)); messageSender.sendDeliveryReceipt(textSecureAddress, timestamp); diff --git a/src/org/thoughtcrime/securesms/jobs/MasterSecretDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/MasterSecretDecryptJob.java new file mode 100644 index 0000000000..161a82711d --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/MasterSecretDecryptJob.java @@ -0,0 +1,103 @@ +package org.thoughtcrime.securesms.jobs; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; + +import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher; +import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecretUnion; +import org.thoughtcrime.securesms.crypto.MasterSecretUtil; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; +import org.thoughtcrime.securesms.database.MmsDatabase; +import org.thoughtcrime.securesms.database.SmsDatabase; +import org.thoughtcrime.securesms.database.model.MessageRecord; +import org.thoughtcrime.securesms.database.model.SmsMessageRecord; +import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; +import org.thoughtcrime.securesms.notifications.MessageNotifier; +import org.whispersystems.jobqueue.JobParameters; +import org.whispersystems.libaxolotl.InvalidMessageException; + +import java.io.IOException; + +public class MasterSecretDecryptJob extends MasterSecretJob { + + private static final long serialVersionUID = 1L; + private static final String TAG = MasterSecretDecryptJob.class.getSimpleName(); + + public MasterSecretDecryptJob(Context context) { + super(context, JobParameters.newBuilder() + .withRequirement(new MasterSecretRequirement(context)) + .create()); + } + + @Override + public void onRun(MasterSecret masterSecret) { + EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); + SmsDatabase.Reader smsReader = smsDatabase.getDecryptInProgressMessages(masterSecret); + + SmsMessageRecord smsRecord; + + while ((smsRecord = smsReader.getNext()) != null) { + try { + String body = getAsymmetricDecryptedBody(masterSecret, smsRecord.getBody().getBody()); + smsDatabase.updateMessageBody(new MasterSecretUnion(masterSecret), smsRecord.getId(), body); + } catch (InvalidMessageException e) { + Log.w(TAG, e); + } + } + + MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context); + MmsDatabase.Reader mmsReader = mmsDatabase.getDecryptInProgressMessages(masterSecret); + + MessageRecord mmsRecord; + + while ((mmsRecord = mmsReader.getNext()) != null) { + try { + String body = getAsymmetricDecryptedBody(masterSecret, mmsRecord.getBody().getBody()); + mmsDatabase.updateMessageBody(new MasterSecretUnion(masterSecret), mmsRecord.getId(), body); + } catch (InvalidMessageException e) { + Log.w(TAG, e); + } + } + + smsReader.close(); + mmsReader.close(); + + MessageNotifier.updateNotification(context, masterSecret); + } + + @Override + public boolean onShouldRetryThrowable(Exception exception) { + return false; + } + + @Override + public void onAdded() { + + } + + @Override + public void onCanceled() { + + } + + private String getAsymmetricDecryptedBody(MasterSecret masterSecret, String body) + throws InvalidMessageException + { + try { + AsymmetricMasterSecret asymmetricMasterSecret = MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret); + AsymmetricMasterCipher asymmetricMasterCipher = new AsymmetricMasterCipher(asymmetricMasterSecret); + + if (TextUtils.isEmpty(body)) return ""; + else return asymmetricMasterCipher.decryptBody(body); + + } catch (IOException e) { + throw new InvalidMessageException(e); + } + } + + +} diff --git a/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java index f5d9b397a2..53922d86b4 100644 --- a/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java @@ -2,21 +2,17 @@ package org.thoughtcrime.securesms.jobs; import android.content.Context; import android.net.Uri; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.util.Log; import android.util.Pair; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecretUnion; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.mms.ApnUnavailableException; import org.thoughtcrime.securesms.mms.CompatMmsConnection; -import org.thoughtcrime.securesms.mms.IncomingLollipopMmsConnection; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; -import org.thoughtcrime.securesms.mms.IncomingLegacyMmsConnection; -import org.thoughtcrime.securesms.mms.IncomingMmsConnection; import org.thoughtcrime.securesms.mms.MmsRadioException; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.protocol.WirePrefix; @@ -154,8 +150,8 @@ public class MmsDownloadJob extends MasterSecretJob { database.markAsLegacyVersion(messageId, threadId); messageAndThreadId = new Pair<>(messageId, threadId); } else { - messageAndThreadId = database.insertMessageInbox(masterSecret, message, - contentLocation, threadId); + messageAndThreadId = database.insertMessageInbox(new MasterSecretUnion(masterSecret), + message, contentLocation, threadId); database.delete(messageId); } diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index 23358a64bf..35011f7879 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -59,7 +59,7 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje public void onRun(MasterSecret masterSecret) throws IOException, UntrustedIdentityException, NetworkException { - TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret); + TextSecureMessageSender messageSender = messageSenderFactory.create(); File contactDataFile = createTempFile("multidevice-contact-update"); try { diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java index 60c937f4fe..388be9dcda 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java @@ -47,7 +47,7 @@ public class MultiDeviceGroupUpdateJob extends MasterSecretJob implements Inject @Override public void onRun(MasterSecret masterSecret) throws Exception { - TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret); + TextSecureMessageSender messageSender = messageSenderFactory.create(); File contactDataFile = createTempFile("multidevice-contact-update"); GroupDatabase.Reader reader = null; diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 3da69d071a..17993d0c4b 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -1,11 +1,15 @@ package org.thoughtcrime.securesms.jobs; import android.content.Context; +import android.support.annotation.NonNull; import android.util.Log; import android.util.Pair; import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecretUnion; +import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore; import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; @@ -15,7 +19,6 @@ import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.database.PushDatabase; import org.thoughtcrime.securesms.groups.GroupMessageProcessor; -import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage; @@ -47,9 +50,9 @@ import org.whispersystems.libaxolotl.state.SessionStore; import org.whispersystems.libaxolotl.util.guava.Optional; import org.whispersystems.textsecure.api.crypto.TextSecureCipher; import org.whispersystems.textsecure.api.messages.TextSecureContent; +import org.whispersystems.textsecure.api.messages.TextSecureDataMessage; import org.whispersystems.textsecure.api.messages.TextSecureEnvelope; import org.whispersystems.textsecure.api.messages.TextSecureGroup; -import org.whispersystems.textsecure.api.messages.TextSecureDataMessage; import org.whispersystems.textsecure.api.messages.multidevice.RequestMessage; import org.whispersystems.textsecure.api.messages.multidevice.SentTranscriptMessage; import org.whispersystems.textsecure.api.messages.multidevice.TextSecureSyncMessage; @@ -59,9 +62,9 @@ import java.util.concurrent.TimeUnit; import ws.com.google.android.mms.MmsException; -public class PushDecryptJob extends MasterSecretJob { +public class PushDecryptJob extends ContextJob { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; public static final String TAG = PushDecryptJob.class.getSimpleName(); @@ -75,7 +78,6 @@ public class PushDecryptJob extends MasterSecretJob { public PushDecryptJob(Context context, long pushMessageId, long smsMessageId, String sender) { super(context, JobParameters.newBuilder() .withPersistence() - .withRequirement(new MasterSecretRequirement(context)) .withGroupId(sender) .withWakeLock(true, 5, TimeUnit.SECONDS) .create()); @@ -91,18 +93,29 @@ public class PushDecryptJob extends MasterSecretJob { } @Override - public void onRun(MasterSecret masterSecret) throws NoSuchMessageException { + public void onRun() throws NoSuchMessageException { + if (!IdentityKeyUtil.hasIdentityKey(context)) { + Log.w(TAG, "Skipping job, waiting for migration..."); + return; + } + + MasterSecret masterSecret = KeyCachingService.getMasterSecret(context); PushDatabase database = DatabaseFactory.getPushDatabase(context); TextSecureEnvelope envelope = database.get(messageId); Optional optionalSmsMessageId = smsMessageId > 0 ? Optional.of(smsMessageId) : Optional.absent(); - handleMessage(masterSecret, envelope, optionalSmsMessageId); + MasterSecretUnion masterSecretUnion; + + if (masterSecret == null) masterSecretUnion = new MasterSecretUnion(MasterSecretUtil.getAsymmetricMasterSecret(context, null)); + else masterSecretUnion = new MasterSecretUnion(masterSecret); + + handleMessage(masterSecretUnion, envelope, optionalSmsMessageId); database.delete(messageId); } @Override - public boolean onShouldRetryThrowable(Exception exception) { + public boolean onShouldRetry(Exception exception) { return false; } @@ -111,9 +124,9 @@ public class PushDecryptJob extends MasterSecretJob { } - private void handleMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional smsMessageId) { + private void handleMessage(MasterSecretUnion masterSecret, TextSecureEnvelope envelope, Optional smsMessageId) { try { - AxolotlStore axolotlStore = new TextSecureAxolotlStore(context, masterSecret); + AxolotlStore axolotlStore = new TextSecureAxolotlStore(context); TextSecureAddress localAddress = new TextSecureAddress(TextSecurePreferences.getLocalNumber(context)); TextSecureCipher cipher = new TextSecureCipher(localAddress, axolotlStore); @@ -157,8 +170,10 @@ public class PushDecryptJob extends MasterSecretJob { } } - private void handleEndSessionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, - TextSecureDataMessage message, Optional smsMessageId) + private void handleEndSessionMessage(@NonNull MasterSecretUnion masterSecret, + @NonNull TextSecureEnvelope envelope, + @NonNull TextSecureDataMessage message, + @NonNull Optional smsMessageId) { EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); IncomingTextMessage incomingTextMessage = new IncomingTextMessage(envelope.getSource(), @@ -171,20 +186,25 @@ public class PushDecryptJob extends MasterSecretJob { if (!smsMessageId.isPresent()) { IncomingEndSessionMessage incomingEndSessionMessage = new IncomingEndSessionMessage(incomingTextMessage); Pair messageAndThreadId = smsDatabase.insertMessageInbox(masterSecret, incomingEndSessionMessage); + threadId = messageAndThreadId.second; } else { smsDatabase.markAsEndSession(smsMessageId.get()); threadId = smsDatabase.getThreadIdForMessage(smsMessageId.get()); } - SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); + SessionStore sessionStore = new TextSecureSessionStore(context); sessionStore.deleteAllSessions(envelope.getSource()); SecurityEvent.broadcastSecurityUpdateEvent(context, threadId); - MessageNotifier.updateNotification(context, masterSecret, threadId); + MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), threadId); } - private void handleGroupMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureDataMessage message, Optional smsMessageId) { + private void handleGroupMessage(@NonNull MasterSecretUnion masterSecret, + @NonNull TextSecureEnvelope envelope, + @NonNull TextSecureDataMessage message, + @NonNull Optional smsMessageId) + { GroupMessageProcessor.process(context, masterSecret, envelope, message); if (smsMessageId.isPresent()) { @@ -192,7 +212,9 @@ public class PushDecryptJob extends MasterSecretJob { } } - private void handleSynchronizeSentMessage(MasterSecret masterSecret, SentTranscriptMessage message, Optional smsMessageId) + private void handleSynchronizeSentMessage(@NonNull MasterSecretUnion masterSecret, + @NonNull SentTranscriptMessage message, + @NonNull Optional smsMessageId) throws MmsException { if (message.getMessage().getAttachments().isPresent()) { @@ -202,7 +224,9 @@ public class PushDecryptJob extends MasterSecretJob { } } - private void handleSynchronizeRequestMessage(MasterSecret masterSecret, RequestMessage message) { + private void handleSynchronizeRequestMessage(@NonNull MasterSecretUnion masterSecret, + @NonNull RequestMessage message) + { if (message.isContactsRequest()) { ApplicationContext.getInstance(context) .getJobManager() @@ -216,8 +240,10 @@ public class PushDecryptJob extends MasterSecretJob { } } - private void handleMediaMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, - TextSecureDataMessage message, Optional smsMessageId) + private void handleMediaMessage(@NonNull MasterSecretUnion masterSecret, + @NonNull TextSecureEnvelope envelope, + @NonNull TextSecureDataMessage message, + @NonNull Optional smsMessageId) throws MmsException { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); @@ -239,12 +265,12 @@ public class PushDecryptJob extends MasterSecretJob { DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); } - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second); } - private void handleSynchronizeSentMediaMessage(MasterSecret masterSecret, - SentTranscriptMessage message, - Optional smsMessageId) + private void handleSynchronizeSentMediaMessage(@NonNull MasterSecretUnion masterSecret, + @NonNull SentTranscriptMessage message, + @NonNull Optional smsMessageId) throws MmsException { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); @@ -270,8 +296,10 @@ public class PushDecryptJob extends MasterSecretJob { } } - private void handleTextMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, - TextSecureDataMessage message, Optional smsMessageId) + private void handleTextMessage(@NonNull MasterSecretUnion masterSecret, + @NonNull TextSecureEnvelope envelope, + @NonNull TextSecureDataMessage message, + @NonNull Optional smsMessageId) { EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); String body = message.getBody().isPresent() ? message.getBody().get() : ""; @@ -287,16 +315,15 @@ public class PushDecryptJob extends MasterSecretJob { message.getGroupInfo()); textMessage = new IncomingEncryptedMessage(textMessage, body); - messageAndThreadId = database.insertMessageInbox(masterSecret, textMessage); } - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second); } - private void handleSynchronizeSentTextMessage(MasterSecret masterSecret, - SentTranscriptMessage message, - Optional smsMessageId) + private void handleSynchronizeSentTextMessage(@NonNull MasterSecretUnion masterSecret, + @NonNull SentTranscriptMessage message, + @NonNull Optional smsMessageId) { EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); Recipients recipients = getSyncMessageDestination(message); @@ -315,55 +342,70 @@ public class PushDecryptJob extends MasterSecretJob { } } - private void handleInvalidVersionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional smsMessageId) { + private void handleInvalidVersionMessage(@NonNull MasterSecretUnion masterSecret, + @NonNull TextSecureEnvelope envelope, + @NonNull Optional smsMessageId) + { EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); if (!smsMessageId.isPresent()) { - Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope); + Pair messageAndThreadId = insertPlaceholder(envelope); smsDatabase.markAsInvalidVersionKeyExchange(messageAndThreadId.first); - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second); } else { smsDatabase.markAsInvalidVersionKeyExchange(smsMessageId.get()); } } - private void handleCorruptMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional smsMessageId) { + private void handleCorruptMessage(@NonNull MasterSecretUnion masterSecret, + @NonNull TextSecureEnvelope envelope, + @NonNull Optional smsMessageId) + { EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); if (!smsMessageId.isPresent()) { - Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope); + Pair messageAndThreadId = insertPlaceholder(envelope); smsDatabase.markAsDecryptFailed(messageAndThreadId.first); - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second); } else { smsDatabase.markAsDecryptFailed(smsMessageId.get()); } } - private void handleNoSessionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional smsMessageId) { + private void handleNoSessionMessage(@NonNull MasterSecretUnion masterSecret, + @NonNull TextSecureEnvelope envelope, + @NonNull Optional smsMessageId) + { EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); if (!smsMessageId.isPresent()) { - Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope); + Pair messageAndThreadId = insertPlaceholder(envelope); smsDatabase.markAsNoSession(messageAndThreadId.first); - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second); } else { smsDatabase.markAsNoSession(smsMessageId.get()); } } - private void handleLegacyMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional smsMessageId) { + private void handleLegacyMessage(@NonNull MasterSecretUnion masterSecret, + @NonNull TextSecureEnvelope envelope, + @NonNull Optional smsMessageId) + { EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); if (!smsMessageId.isPresent()) { - Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope); + Pair messageAndThreadId = insertPlaceholder(envelope); smsDatabase.markAsLegacyVersion(messageAndThreadId.first); - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second); } else { smsDatabase.markAsLegacyVersion(smsMessageId.get()); } } - private void handleDuplicateMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional smsMessageId) { + private void handleDuplicateMessage(@NonNull MasterSecretUnion masterSecret, + @NonNull TextSecureEnvelope envelope, + @NonNull Optional smsMessageId) + { // Let's start ignoring these now // SmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); // @@ -376,7 +418,10 @@ public class PushDecryptJob extends MasterSecretJob { // } } - private void handleUntrustedIdentityMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional smsMessageId) { + private void handleUntrustedIdentityMessage(@NonNull MasterSecretUnion masterSecret, + @NonNull TextSecureEnvelope envelope, + @NonNull Optional smsMessageId) + { try { EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); Recipients recipients = RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false); @@ -393,7 +438,7 @@ public class PushDecryptJob extends MasterSecretJob { Pair messageAndThreadId = database.insertMessageInbox(masterSecret, bundleMessage); database.setMismatchedIdentity(messageAndThreadId.first, recipientId, identityKey); - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second); } else { database.updateMessageBody(masterSecret, smsMessageId.get(), encoded); database.markAsPreKeyBundle(smsMessageId.get()); @@ -404,16 +449,14 @@ public class PushDecryptJob extends MasterSecretJob { } } - private Pair insertPlaceholder(MasterSecret masterSecret, TextSecureEnvelope envelope) { - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - - IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(), - envelope.getTimestamp(), "", - Optional.absent()); + private Pair insertPlaceholder(@NonNull TextSecureEnvelope envelope) { + EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(), + envelope.getTimestamp(), "", + Optional.absent()); textMessage = new IncomingEncryptedMessage(textMessage, ""); - - return database.insertMessageInbox(masterSecret, textMessage); + return database.insertMessageInbox(textMessage); } private Recipients getSyncMessageDestination(SentTranscriptMessage message) { diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java index 4362e2f307..2be79eba48 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java @@ -135,7 +135,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { { message = getResolvedMessage(masterSecret, message, MediaConstraints.PUSH_CONSTRAINTS, false); - TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret); + TextSecureMessageSender messageSender = messageSenderFactory.create(); byte[] groupId = GroupUtil.getDecodedId(message.getTo()[0].getString()); Recipients recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, false); List attachments = getAttachments(masterSecret, message); diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index 6f7c9ec8d9..5630a593ff 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -104,7 +104,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException, UndeliverableMessageException { - TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret); + TextSecureMessageSender messageSender = messageSenderFactory.create(); String destination = message.getTo()[0].getString(); try { diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index 391e9d766b..2d3730c5a7 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -59,7 +59,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { try { Log.w(TAG, "Sending message: " + messageId); - deliver(masterSecret, record); + deliver(record); database.markAsPush(messageId); database.markAsSecure(messageId); database.markAsSent(messageId); @@ -99,12 +99,12 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { } } - private void deliver(MasterSecret masterSecret, SmsMessageRecord message) + private void deliver(SmsMessageRecord message) throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException { try { TextSecureAddress address = getPushAddress(message.getIndividualRecipient().getNumber()); - TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret); + TextSecureMessageSender messageSender = messageSenderFactory.create(); TextSecureDataMessage textSecureMessage = TextSecureDataMessage.newBuilder() .withTimestamp(message.getDateSent()) .withBody(message.getBody().getBody()) diff --git a/src/org/thoughtcrime/securesms/jobs/RefreshPreKeysJob.java b/src/org/thoughtcrime/securesms/jobs/RefreshPreKeysJob.java index 4ea11a0e8f..aac7109884 100644 --- a/src/org/thoughtcrime/securesms/jobs/RefreshPreKeysJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RefreshPreKeysJob.java @@ -57,10 +57,10 @@ public class RefreshPreKeysJob extends MasterSecretJob implements InjectableType return; } - List preKeyRecords = PreKeyUtil.generatePreKeys(context, masterSecret); - PreKeyRecord lastResortKeyRecord = PreKeyUtil.generateLastResortKey(context, masterSecret); - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); - SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, masterSecret, identityKey); + List preKeyRecords = PreKeyUtil.generatePreKeys(context); + PreKeyRecord lastResortKeyRecord = PreKeyUtil.generateLastResortKey(context); + IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context); + SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKey); Log.w(TAG, "Registering new prekeys..."); diff --git a/src/org/thoughtcrime/securesms/jobs/SmsDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/SmsDecryptJob.java deleted file mode 100644 index d2d7680f9c..0000000000 --- a/src/org/thoughtcrime/securesms/jobs/SmsDecryptJob.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import android.content.Context; -import android.util.Log; - -import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher; -import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret; -import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.crypto.MasterSecretUtil; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; -import org.thoughtcrime.securesms.database.NoSuchMessageException; -import org.thoughtcrime.securesms.database.model.SmsMessageRecord; -import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; -import org.thoughtcrime.securesms.notifications.MessageNotifier; -import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; -import org.thoughtcrime.securesms.sms.IncomingTextMessage; -import org.whispersystems.jobqueue.JobParameters; -import org.whispersystems.libaxolotl.InvalidMessageException; -import org.whispersystems.libaxolotl.util.guava.Optional; -import org.whispersystems.textsecure.api.messages.TextSecureGroup; - -import java.io.IOException; - -public class SmsDecryptJob extends MasterSecretJob { - - private static final String TAG = SmsDecryptJob.class.getSimpleName(); - - private final long messageId; - - public SmsDecryptJob(Context context, long messageId) { - super(context, JobParameters.newBuilder() - .withPersistence() - .withRequirement(new MasterSecretRequirement(context)) - .create()); - - this.messageId = messageId; - } - - @Override - public void onAdded() {} - - @Override - public void onRun(MasterSecret masterSecret) throws NoSuchMessageException { - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - - try { - SmsMessageRecord record = database.getMessage(masterSecret, messageId); - IncomingTextMessage message = createIncomingTextMessage(masterSecret, record); - long messageId = record.getId(); - - if (message.isSecureMessage()) { - database.markAsLegacyVersion(messageId); - } else { - database.updateMessageBody(masterSecret, messageId, message.getMessageBody()); - } - - MessageNotifier.updateNotification(context, masterSecret); - } catch (InvalidMessageException e) { - Log.w(TAG, e); - database.markAsDecryptFailed(messageId); - } - } - - @Override - public boolean onShouldRetryThrowable(Exception exception) { - return false; - } - - @Override - public void onCanceled() { - // TODO - } - - private String getAsymmetricDecryptedBody(MasterSecret masterSecret, String body) - throws InvalidMessageException - { - try { - AsymmetricMasterSecret asymmetricMasterSecret = MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret); - AsymmetricMasterCipher asymmetricMasterCipher = new AsymmetricMasterCipher(asymmetricMasterSecret); - - return asymmetricMasterCipher.decryptBody(body); - } catch (IOException e) { - throw new InvalidMessageException(e); - } - } - - private IncomingTextMessage createIncomingTextMessage(MasterSecret masterSecret, SmsMessageRecord record) - throws InvalidMessageException - { - IncomingTextMessage message = new IncomingTextMessage(record.getRecipients().getPrimaryRecipient().getNumber(), - record.getRecipientDeviceId(), - record.getDateSent(), - record.getBody().getBody(), - Optional.absent()); - - if (record.isAsymmetricEncryption()) { - String plaintextBody = getAsymmetricDecryptedBody(masterSecret, record.getBody().getBody()); - return new IncomingTextMessage(message, plaintextBody); - } else { - return new IncomingEncryptedMessage(message, message.getMessageBody()); - } - } -} diff --git a/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java b/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java index e3c17c3c06..27a208cf16 100644 --- a/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java +++ b/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java @@ -5,8 +5,8 @@ import android.telephony.SmsMessage; import android.util.Log; import android.util.Pair; -import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecretUnion; import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; @@ -43,11 +43,20 @@ public class SmsReceiveJob extends ContextJob { @Override public void onRun() { - Optional message = assembleMessageFragments(pdus); + Optional message = assembleMessageFragments(pdus); + MasterSecret masterSecret = KeyCachingService.getMasterSecret(context); + + MasterSecretUnion masterSecretUnion; + + if (masterSecret == null) { + masterSecretUnion = new MasterSecretUnion(MasterSecretUtil.getAsymmetricMasterSecret(context, null)); + } else { + masterSecretUnion = new MasterSecretUnion(masterSecret); + } if (message.isPresent() && !isBlocked(message.get())) { - Pair messageAndThreadId = storeMessage(message.get()); - MessageNotifier.updateNotification(context, KeyCachingService.getMasterSecret(context), messageAndThreadId.second); + Pair messageAndThreadId = storeMessage(masterSecretUnion, message.get()); + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } else if (message.isPresent()) { Log.w(TAG, "*** Received blocked SMS, ignoring..."); } @@ -72,21 +81,15 @@ public class SmsReceiveJob extends ContextJob { return false; } - private Pair storeMessage(IncomingTextMessage message) { - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - MasterSecret masterSecret = KeyCachingService.getMasterSecret(context); + private Pair storeMessage(MasterSecretUnion masterSecret, IncomingTextMessage message) { + EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); Pair messageAndThreadId; if (message.isSecureMessage()) { - messageAndThreadId = database.insertMessageInbox((MasterSecret)null, message); + IncomingTextMessage placeholder = new IncomingTextMessage(message, ""); + messageAndThreadId = database.insertMessageInbox(placeholder); database.markAsLegacyVersion(messageAndThreadId.first); - } else if (masterSecret == null) { - messageAndThreadId = database.insertMessageInbox(MasterSecretUtil.getAsymmetricMasterSecret(context, null), message); - - ApplicationContext.getInstance(context) - .getJobManager() - .add(new SmsDecryptJob(context, messageAndThreadId.first)); } else { messageAndThreadId = database.insertMessageInbox(masterSecret, message); } diff --git a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java index a6980b5613..4659b647eb 100644 --- a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java @@ -2,8 +2,11 @@ package org.thoughtcrime.securesms.mms; import android.text.TextUtils; +import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher; import org.thoughtcrime.securesms.crypto.MasterCipher; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecretUnion; +import org.thoughtcrime.securesms.crypto.MediaKey; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Util; @@ -34,7 +37,7 @@ public class IncomingMediaMessage { this.push = false; } - public IncomingMediaMessage(MasterSecret masterSecret, + public IncomingMediaMessage(MasterSecretUnion masterSecret, String from, String to, long sentTimeMillis, @@ -70,11 +73,11 @@ public class IncomingMediaMessage { for (TextSecureAttachment attachment : attachments.get()) { if (attachment.isPointer()) { PduPart media = new PduPart(); - byte[] encryptedKey = new MasterCipher(masterSecret).encryptBytes(attachment.asPointer().getKey()); + String encryptedKey = MediaKey.getEncrypted(masterSecret, attachment.asPointer().getKey()); media.setContentType(Util.toIsoBytes(attachment.getContentType())); media.setContentLocation(Util.toIsoBytes(String.valueOf(attachment.asPointer().getId()))); - media.setContentDisposition(Util.toIsoBytes(Base64.encodeBytes(encryptedKey))); + media.setContentDisposition(Util.toIsoBytes(encryptedKey)); if (relay.isPresent()) { media.setName(Util.toIsoBytes(relay.get())); diff --git a/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java index 5892bb9bf1..374f9ab678 100644 --- a/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java @@ -3,11 +3,10 @@ package org.thoughtcrime.securesms.mms; import android.content.Context; import android.text.TextUtils; -import org.thoughtcrime.securesms.crypto.MasterCipher; -import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecretUnion; +import org.thoughtcrime.securesms.crypto.MediaKey; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.recipients.Recipients; -import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.textsecure.api.messages.TextSecureAttachment; @@ -40,7 +39,7 @@ public class OutgoingMediaMessage { this(context, recipients, slideDeck.toPduBody(), message, distributionType); } - public OutgoingMediaMessage(Context context, MasterSecret masterSecret, + public OutgoingMediaMessage(Context context, MasterSecretUnion masterSecret, Recipients recipients, List attachments, String message) { @@ -74,17 +73,17 @@ public class OutgoingMediaMessage { return false; } - private static PduBody pduBodyFor(MasterSecret masterSecret, List attachments) { + private static PduBody pduBodyFor(MasterSecretUnion masterSecret, List attachments) { PduBody body = new PduBody(); for (TextSecureAttachment attachment : attachments) { if (attachment.isPointer()) { PduPart media = new PduPart(); - byte[] encryptedKey = new MasterCipher(masterSecret).encryptBytes(attachment.asPointer().getKey()); + String encryptedKey = MediaKey.getEncrypted(masterSecret, attachment.asPointer().getKey()); media.setContentType(Util.toIsoBytes(attachment.getContentType())); media.setContentLocation(Util.toIsoBytes(String.valueOf(attachment.asPointer().getId()))); - media.setContentDisposition(Util.toIsoBytes(Base64.encodeBytes(encryptedKey))); + media.setContentDisposition(Util.toIsoBytes(encryptedKey)); body.addPart(media); } diff --git a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java index 06ba1bb0b7..84c8a5f7f3 100644 --- a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java +++ b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java @@ -31,6 +31,7 @@ import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat.Action; @@ -118,7 +119,7 @@ public class MessageNotifier { } } - public static void updateNotification(Context context, MasterSecret masterSecret) { + public static void updateNotification(@NonNull Context context, @Nullable MasterSecret masterSecret) { if (!TextSecurePreferences.isNotificationsEnabled(context)) { return; } @@ -126,7 +127,10 @@ public class MessageNotifier { updateNotification(context, masterSecret, false, 0); } - public static void updateNotification(Context context, MasterSecret masterSecret, long threadId) { + public static void updateNotification(@NonNull Context context, + @Nullable MasterSecret masterSecret, + long threadId) + { Recipients recipients = DatabaseFactory.getThreadDatabase(context) .getRecipientsForThreadId(threadId); @@ -146,7 +150,10 @@ public class MessageNotifier { } } - private static void updateNotification(Context context, MasterSecret masterSecret, boolean signal, int reminderCount) { + private static void updateNotification(@NonNull Context context, + @Nullable MasterSecret masterSecret, + boolean signal, int reminderCount) + { Cursor telcoCursor = null; Cursor pushCursor = null; @@ -182,9 +189,9 @@ public class MessageNotifier { } } - private static void sendSingleThreadNotification(Context context, - MasterSecret masterSecret, - NotificationState notificationState, + private static void sendSingleThreadNotification(@NonNull Context context, + @Nullable MasterSecret masterSecret, + @NonNull NotificationState notificationState, boolean signal) { if (notificationState.getNotifications().isEmpty()) { @@ -266,9 +273,9 @@ public class MessageNotifier { .notify(NOTIFICATION_ID, builder.build()); } - private static void sendMultipleThreadNotification(Context context, - MasterSecret masterSecret, - NotificationState notificationState, + private static void sendMultipleThreadNotification(@NonNull Context context, + @Nullable MasterSecret masterSecret, + @NonNull NotificationState notificationState, boolean signal) { List notifications = notificationState.getNotifications(); @@ -375,10 +382,10 @@ public class MessageNotifier { } } - private static void appendPushNotificationState(Context context, - MasterSecret masterSecret, - NotificationState notificationState, - Cursor cursor) + private static void appendPushNotificationState(@NonNull Context context, + @Nullable MasterSecret masterSecret, + @NonNull NotificationState notificationState, + @NonNull Cursor cursor) { if (masterSecret != null) return; @@ -392,7 +399,7 @@ public class MessageNotifier { Recipients recipients = RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false); Recipient recipient = recipients.getPrimaryRecipient(); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); - SpannableString body = new SpannableString(context.getString(R.string.MessageNotifier_encrypted_message)); + SpannableString body = new SpannableString(context.getString(R.string.MessageNotifier_locked_message)); body.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); if (!recipients.isMuted()) { @@ -405,9 +412,9 @@ public class MessageNotifier { } } - private static NotificationState constructNotificationState(Context context, - MasterSecret masterSecret, - Cursor cursor) + private static NotificationState constructNotificationState(@NonNull Context context, + @Nullable MasterSecret masterSecret, + @NonNull Cursor cursor) { NotificationState notificationState = new NotificationState(); MessageRecord record; @@ -433,7 +440,7 @@ public class MessageNotifier { } if (SmsDatabase.Types.isDecryptInProgressType(record.getType()) || !record.getBody().isPlaintext()) { - body = SpanUtil.italic(context.getString(R.string.MessageNotifier_encrypted_message)); + body = SpanUtil.italic(context.getString(R.string.MessageNotifier_locked_message)); } else if (record.isMms() && TextUtils.isEmpty(body)) { body = SpanUtil.italic(context.getString(R.string.MessageNotifier_media_message)); } else if (record.isMms() && !record.isMmsNotification()) { diff --git a/src/org/thoughtcrime/securesms/service/KeyCachingService.java b/src/org/thoughtcrime/securesms/service/KeyCachingService.java index 40e50aefb3..1f4998d4f7 100644 --- a/src/org/thoughtcrime/securesms/service/KeyCachingService.java +++ b/src/org/thoughtcrime/securesms/service/KeyCachingService.java @@ -28,6 +28,7 @@ import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.os.SystemClock; +import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.util.Log; import android.widget.RemoteViews; @@ -40,6 +41,7 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.InvalidPassphraseException; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecretUtil; +import org.thoughtcrime.securesms.jobs.MasterSecretDecryptJob; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.ParcelUtil; @@ -78,7 +80,7 @@ public class KeyCachingService extends Service { public KeyCachingService() {} - public static synchronized MasterSecret getMasterSecret(Context context) { + public static synchronized @Nullable MasterSecret getMasterSecret(Context context) { if (masterSecret == null && TextSecurePreferences.isPasswordDisabled(context)) { try { MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE); @@ -103,13 +105,14 @@ public class KeyCachingService extends Service { broadcastNewSecret(); startTimeoutIfAppropriate(); + if (!TextSecurePreferences.isPasswordDisabled(this)) { + ApplicationContext.getInstance(this).getJobManager().add(new MasterSecretDecryptJob(this)); + } + new AsyncTask() { @Override protected Void doInBackground(Void... params) { if (!DatabaseUpgradeActivity.isUpdate(KeyCachingService.this)) { - ApplicationContext.getInstance(KeyCachingService.this) - .getJobManager() - .setEncryptionKeys(new EncryptionKeys(ParcelUtil.serialize(masterSecret))); MessageNotifier.updateNotification(KeyCachingService.this, masterSecret); } return null; diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java index 3d0d3451a8..432ab95afa 100644 --- a/src/org/thoughtcrime/securesms/service/RegistrationService.java +++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java @@ -14,7 +14,6 @@ import com.google.android.gms.gcm.GoogleCloudMessaging; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.PreKeyUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.jobs.GcmRefreshJob; @@ -153,15 +152,14 @@ public class RegistrationService extends Service { private void handleVoiceRegistrationIntent(Intent intent) { markAsVerifying(true); - String number = intent.getStringExtra("e164number"); - String password = intent.getStringExtra("password" ); - String signalingKey = intent.getStringExtra("signaling_key"); - MasterSecret masterSecret = intent.getParcelableExtra("master_secret"); + String number = intent.getStringExtra("e164number"); + String password = intent.getStringExtra("password"); + String signalingKey = intent.getStringExtra("signaling_key"); try { TextSecureAccountManager accountManager = TextSecureCommunicationFactory.createManager(this, number, password); - handleCommonRegistration(masterSecret, accountManager, number); + handleCommonRegistration(accountManager, number); markAsVerified(number, password, signalingKey); @@ -181,9 +179,8 @@ public class RegistrationService extends Service { private void handleSmsRegistrationIntent(Intent intent) { markAsVerifying(true); - String number = intent.getStringExtra("e164number"); - MasterSecret masterSecret = intent.getParcelableExtra("master_secret"); - int registrationId = TextSecurePreferences.getLocalRegistrationId(this); + String number = intent.getStringExtra("e164number"); + int registrationId = TextSecurePreferences.getLocalRegistrationId(this); if (registrationId == 0) { registrationId = KeyHelper.generateRegistrationId(false); @@ -204,7 +201,7 @@ public class RegistrationService extends Service { String challenge = waitForChallenge(); accountManager.verifyAccount(challenge, signalingKey, true, registrationId); - handleCommonRegistration(masterSecret, accountManager, number); + handleCommonRegistration(accountManager, number); markAsVerified(number, password, signalingKey); setState(new RegistrationState(RegistrationState.STATE_COMPLETE, number)); @@ -230,15 +227,15 @@ public class RegistrationService extends Service { } } - private void handleCommonRegistration(MasterSecret masterSecret, TextSecureAccountManager accountManager, String number) + private void handleCommonRegistration(TextSecureAccountManager accountManager, String number) throws IOException { setState(new RegistrationState(RegistrationState.STATE_GENERATING_KEYS, number)); Recipient self = RecipientFactory.getRecipientsFromString(this, number, false).getPrimaryRecipient(); - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(this, masterSecret); - List records = PreKeyUtil.generatePreKeys(this, masterSecret); - PreKeyRecord lastResort = PreKeyUtil.generateLastResortKey(this, masterSecret); - SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(this, masterSecret, identityKey); + IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(this); + List records = PreKeyUtil.generatePreKeys(this); + PreKeyRecord lastResort = PreKeyUtil.generateLastResortKey(this); + SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(this, identityKey); accountManager.setPreKeys(identityKey.getPublicKey(),lastResort, signedPreKey, records); setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number)); @@ -249,7 +246,7 @@ public class RegistrationService extends Service { TextSecurePreferences.setGcmRegistrationId(this, gcmRegistrationId); TextSecurePreferences.setWebsocketRegistered(this, true); - DatabaseFactory.getIdentityDatabase(this).saveIdentity(masterSecret, self.getRecipientId(), identityKey.getPublicKey()); + DatabaseFactory.getIdentityDatabase(this).saveIdentity(self.getRecipientId(), identityKey.getPublicKey()); DirectoryHelper.refreshDirectory(this, accountManager, number); DirectoryRefreshListener.schedule(this); diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index eaf7bd3b47..a44ec759dd 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -22,6 +22,7 @@ import android.util.Pair; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecretUnion; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase; @@ -73,7 +74,7 @@ public class MessageSender { allocatedThreadId = threadId; } - long messageId = database.insertMessageOutbox(masterSecret, allocatedThreadId, message, forceSms, System.currentTimeMillis()); + long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), allocatedThreadId, message, forceSms, System.currentTimeMillis()); sendTextMessage(context, recipients, forceSms, keyExchange, messageId); @@ -99,7 +100,7 @@ public class MessageSender { } Recipients recipients = message.getRecipients(); - long messageId = database.insertMessageOutbox(masterSecret, message, allocatedThreadId, forceSms, System.currentTimeMillis()); + long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), message, allocatedThreadId, forceSms, System.currentTimeMillis()); sendMediaMessage(context, masterSecret, recipients, forceSms, messageId);