From ace18557974a5400e5c4e3c6356802029ec09963 Mon Sep 17 00:00:00 2001 From: Alan Evans Date: Wed, 15 Jan 2020 16:11:41 -0500 Subject: [PATCH] Test various Argon2 parameters. --- app/build.gradle | 1 + .../securesms/jobs/Argon2TestJob.java | 246 ++++++++++++++++++ .../securesms/jobs/JobManagerFactories.java | 3 + .../migrations/ApplicationMigrations.java | 8 +- .../migrations/Argon2TestMigrationJob.java | 51 ++++ .../securesms/util/TextSecurePreferences.java | 10 + app/witness-verifications.gradle | 3 + 7 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/Argon2TestJob.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/migrations/Argon2TestMigrationJob.java diff --git a/app/build.gradle b/app/build.gradle index 0833b6a0b..26bb7dfcf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -117,6 +117,7 @@ dependencies { implementation project(':libsignal-service') + implementation 'org.signal:argon2:13.0@aar' implementation 'org.signal:ringrtc-android:0.3.1' implementation "me.leolin:ShortcutBadger:1.1.16" diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/Argon2TestJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/Argon2TestJob.java new file mode 100644 index 000000000..1b558c95b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/Argon2TestJob.java @@ -0,0 +1,246 @@ +package org.thoughtcrime.securesms.jobs; + +import android.app.ActivityManager; + +import androidx.annotation.NonNull; + +import org.signal.argon2.Argon2; +import org.signal.argon2.Argon2Exception; +import org.signal.argon2.MemoryCost; +import org.signal.argon2.Type; +import org.signal.argon2.Version; +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.util.ServiceUtil; +import org.thoughtcrime.securesms.util.TextSecurePreferences; + +import java.nio.charset.StandardCharsets; +import java.text.NumberFormat; +import java.util.Locale; + +public final class Argon2TestJob extends BaseJob { + + public static final String KEY = "Argon2TestJob"; + + private static final String TAG = Log.tag(Argon2TestJob.class); + + private Argon2TestJob(@NonNull Parameters parameters) { + super(parameters); + } + + public Argon2TestJob() { + this(new Parameters.Builder() + .setMaxAttempts(1) + .build()); + } + + @Override + protected void onRun() throws InterruptedException { + if (TextSecurePreferences.isArgon2Tested(context)) { + Log.w(TAG, "Skipping Argon2 test, it has been run before"); + return; + } + + TextSecurePreferences.setArgon2Tested(context,true); + + Log.i(TAG, "Starting Argon2 test"); + + Argon2.Builder argon2Builder = new Argon2.Builder(Version.V13) + .type(Type.Argon2id) + .parallelism(1); + + MemoryCost memoryCost = MemoryCost.MiB(4); + try { + checkRam(memoryCost); + } catch (Exception e) { + throw new Argon2NotEnoughRam4(e.getMessage()); + } + test(argon2Builder.memoryCost(memoryCost) + .iterations(8) + .build(), + "c0286e8dfd91b633f41d9dc13dc99b3705b62e23349dd399ff68be5fc7720c41", + "$argon2id$v=19$m=4096,t=8,p=1$c29tZXNhbHQ$wChujf2RtjP0HZ3BPcmbNwW2LiM0ndOZ/2i+X8dyDEE"); + + Thread.sleep(3000); + + memoryCost = MemoryCost.MiB(8); + try { + checkRam(memoryCost); + } catch (Exception e) { + throw new Argon2NotEnoughRam8(e.getMessage()); + } + test(argon2Builder.memoryCost(memoryCost) + .iterations(3) + .build(), + "c52fdf6c6dc5e4e82b826b00d795781540d6d50458a43f0ccc44a7d701830318", + "$argon2id$v=19$m=8192,t=3,p=1$c29tZXNhbHQ$xS/fbG3F5OgrgmsA15V4FUDW1QRYpD8MzESn1wGDAxg"); + + Thread.sleep(3000); + + memoryCost = MemoryCost.MiB(16); + try { + checkRam(memoryCost); + } catch (Exception e) { + throw new Argon2NotEnoughRam16(e.getMessage()); + } + test(argon2Builder.memoryCost(memoryCost) + .iterations(3) + .build(), + "d41922d454814e3dbf2828108bb43a5b6319b22fc9f169528c20d1a2846e06c6", + "$argon2id$v=19$m=16384,t=3,p=1$c29tZXNhbHQ$1Bki1FSBTj2/KCgQi7Q6W2MZsi/J8WlSjCDRooRuBsY"); + + Thread.sleep(3000); + + memoryCost = MemoryCost.MiB(32); + try { + checkRam(memoryCost); + } catch (Exception e) { + throw new Argon2NotEnoughRam32(e.getMessage()); + } + test(argon2Builder.memoryCost(memoryCost) + .iterations(2) + .build(), + "8365c0271b505e7fa397982790802de7b62f71f4e11e05f7a4e6b2ad4f75fec0", + "$argon2id$v=19$m=32768,t=2,p=1$c29tZXNhbHQ$g2XAJxtQXn+jl5gnkIAt57YvcfThHgX3pOayrU91/sA"); + + Log.i(TAG, "Argon2 test complete"); + } + + private void test(Argon2 argon2, String expectedHashHex, String expectedEncoded) { + long startTime = System.currentTimeMillis(); + + Argon2.Result hash; + try { + hash = argon2.hash("signal".getBytes(StandardCharsets.UTF_8), + "somesalt".getBytes(StandardCharsets.UTF_8)); + } catch (Argon2Exception e) { + throw new Argon2TestJobRuntimeException(e); + } + + long endTime = System.currentTimeMillis(); + + Log.i(TAG, String.format(Locale.US, "Argon2 hash complete:%n%s%.3f seconds", hash, (endTime - startTime) / 1000f)); + + if (!hash.getHashHex().equals(expectedHashHex)) { + throw new Argon2HashIncorrectRuntimeException("Hash was not correct"); + } + + if (!hash.getEncoded().equals(expectedEncoded)) { + throw new Argon2EncodedHashIncorrectRuntimeException("Encoded Hash was not correct"); + } + } + + private void checkRam(MemoryCost memoryCost) throws Exception { + NumberFormat numberFormat = NumberFormat.getInstance(Locale.US); + ActivityManager activityManager = ServiceUtil.getActivityManager(context); + ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); + + activityManager.getMemoryInfo(memoryInfo); + + long remainingRam = memoryInfo.availMem - memoryInfo.threshold - memoryCost.toBytes(); + + if (remainingRam <= 0) { + throw new Exception(String.format("Not enough RAM available without taking the system into a low memory state.%n" + + "Available: %s%n" + + "Low memory threshold: %s%n" + + "Requested: %s%n" + + "Shortfall: %s", + numberFormat.format(memoryInfo.availMem), + numberFormat.format(memoryInfo.threshold), + numberFormat.format(memoryCost.toBytes()), + numberFormat.format(-remainingRam) + )); + } else { + Log.i(TAG, String.format("Enough RAM available without taking the system into a low memory state.%n" + + "Available: %s%n" + + "Low memory threshold: %s%n" + + "Requested: %s%n" + + "Surplus: %s", + numberFormat.format(memoryInfo.availMem), + numberFormat.format(memoryInfo.threshold), + numberFormat.format(memoryCost.toBytes()), + numberFormat.format(remainingRam) + )); + } + } + + @Override + protected boolean onShouldRetry(@NonNull Exception e) { + return false; + } + + @Override + public @NonNull Data serialize() { + return Data.EMPTY; + } + + @Override + public @NonNull String getFactoryKey() { + return KEY; + } + + @Override + public void onCanceled() { + + } + + private static abstract class Argon2RuntimeException extends RuntimeException { + private Argon2RuntimeException(String message) { + super(message); + } + + private Argon2RuntimeException(Throwable inner) { + super(inner); + } + } + + private static class Argon2HashIncorrectRuntimeException extends Argon2RuntimeException { + private Argon2HashIncorrectRuntimeException(String message) { + super(message); + } + } + + private static class Argon2EncodedHashIncorrectRuntimeException extends Argon2RuntimeException { + private Argon2EncodedHashIncorrectRuntimeException(String message) { + super(message); + } + } + + private static class Argon2TestJobRuntimeException extends Argon2RuntimeException { + private Argon2TestJobRuntimeException(Throwable t) { + super(t); + } + } + + private static class Argon2NotEnoughRam4 extends Argon2RuntimeException { + private Argon2NotEnoughRam4(String message) { + super(message); + } + } + + private static class Argon2NotEnoughRam8 extends Argon2RuntimeException { + private Argon2NotEnoughRam8(String message) { + super(message); + } + } + + private static class Argon2NotEnoughRam16 extends Argon2RuntimeException { + private Argon2NotEnoughRam16(String message) { + super(message); + } + } + + private static class Argon2NotEnoughRam32 extends Argon2RuntimeException { + private Argon2NotEnoughRam32(String message) { + super(message); + } + } + + public static final class Factory implements Job.Factory { + @Override + public @NonNull Argon2TestJob create(@NonNull Parameters parameters, @NonNull Data data) { + return new Argon2TestJob(parameters); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 4f7fd59a2..2c1bd5caf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintOb import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMigration; import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMigration2; import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdJobMigration; +import org.thoughtcrime.securesms.migrations.Argon2TestMigrationJob; import org.thoughtcrime.securesms.migrations.AvatarMigrationJob; import org.thoughtcrime.securesms.migrations.CachedAttachmentsMigrationJob; import org.thoughtcrime.securesms.migrations.DatabaseMigrationJob; @@ -94,6 +95,7 @@ public final class JobManagerFactories { put(TypingSendJob.KEY, new TypingSendJob.Factory()); put(UpdateApkJob.KEY, new UpdateApkJob.Factory()); put(MarkerJob.KEY, new MarkerJob.Factory()); + put(Argon2TestJob.KEY, new Argon2TestJob.Factory()); // Migrations put(AvatarMigrationJob.KEY, new AvatarMigrationJob.Factory()); @@ -105,6 +107,7 @@ public final class JobManagerFactories { put(StickerLaunchMigrationJob.KEY, new StickerLaunchMigrationJob.Factory()); put(UuidMigrationJob.KEY, new UuidMigrationJob.Factory()); put(RegistrationPinV2MigrationJob.KEY, new RegistrationPinV2MigrationJob.Factory()); + put(Argon2TestMigrationJob.KEY, new Argon2TestMigrationJob.Factory()); // Dead jobs put("PushContentReceiveJob", new FailingJob.Factory()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java index 67d08d145..65dd9586d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java @@ -10,6 +10,7 @@ import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import org.thoughtcrime.securesms.jobmanager.JobManager; +import org.thoughtcrime.securesms.jobs.Argon2TestJob; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; @@ -38,7 +39,7 @@ public class ApplicationMigrations { private static final int LEGACY_CANONICAL_VERSION = 455; - public static final int CURRENT_VERSION = 8; + public static final int CURRENT_VERSION = 9; private static final class Version { static final int LEGACY = 1; @@ -49,6 +50,7 @@ public class ApplicationMigrations { static final int UUIDS = 6; static final int CACHED_ATTACHMENTS = 7; static final int STICKERS_LAUNCH = 8; + static final int TEST_ARGON2 = 9; } /** @@ -193,6 +195,10 @@ public class ApplicationMigrations { jobs.put(Version.STICKERS_LAUNCH, new StickerLaunchMigrationJob()); } + if (lastSeenVersion < Version.TEST_ARGON2) { + jobs.put(Version.TEST_ARGON2, new Argon2TestMigrationJob()); + } + return jobs; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/Argon2TestMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/Argon2TestMigrationJob.java new file mode 100644 index 000000000..bc619188a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/Argon2TestMigrationJob.java @@ -0,0 +1,51 @@ +package org.thoughtcrime.securesms.migrations; + +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.jobs.Argon2TestJob; + +/** + * Triggers a Argon2 Test, just once. + */ +public final class Argon2TestMigrationJob extends MigrationJob { + + public static final String KEY = "Argon2TestMigrationJob"; + + private Argon2TestMigrationJob(Parameters parameters) { + super(parameters); + } + + public Argon2TestMigrationJob() { + this(new Parameters.Builder().build()); + } + + @Override + boolean isUiBlocking() { + return false; + } + + @Override + void performMigration() { + ApplicationDependencies.getJobManager().add(new Argon2TestJob()); + } + + @Override + boolean shouldRetry(@NonNull Exception e) { + return false; + } + + @Override + public @NonNull String getFactoryKey() { + return KEY; + } + + public static final class Factory implements Job.Factory { + @Override + public @NonNull Argon2TestMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) { + return new Argon2TestMigrationJob(parameters); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 341081903..8bfe6ab05 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -216,6 +216,8 @@ public class TextSecurePreferences { private static final String STORAGE_MANIFEST_VERSION = "pref_storage_manifest_version"; + private static final String ARGON2_TESTED = "argon2_tested"; + public static boolean isScreenLockEnabled(@NonNull Context context) { return getBooleanPreference(context, SCREEN_LOCK, false); } @@ -1339,6 +1341,14 @@ public class TextSecurePreferences { setLongPreference(context, STORAGE_MANIFEST_VERSION, version); } + public static boolean isArgon2Tested(Context context) { + return getBooleanPreference(context, ARGON2_TESTED, false); + } + + public static void setArgon2Tested(Context context, boolean tested) { + setBooleanPreference(context, ARGON2_TESTED, tested); + } + public static void setBooleanPreference(Context context, String key, boolean value) { PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply(); } diff --git a/app/witness-verifications.gradle b/app/witness-verifications.gradle index ca7689599..661385ace 100644 --- a/app/witness-verifications.gradle +++ b/app/witness-verifications.gradle @@ -351,6 +351,9 @@ dependencyVerification { ['org.signal:android-database-sqlcipher:3.5.9-S3', '33d4063336893af00b9d68b418e7b290cace74c20ce8aacffddc0911010d3d73'], + ['org.signal:argon2:13.0', + '34709776041840533a15e79d8abc4ebce6d0aa99bf8d39bd4202ef5791296f72'], + ['org.signal:ringrtc-android:0.3.1', '785c422a2322f810141d85f63eab3874961b19face886daaf140ab011d39e166'],