Add the ability to migrate to new KBS enclaves.
parent
e22384b6b4
commit
474963dcf1
|
@ -132,9 +132,10 @@ android {
|
||||||
buildConfigField "int", "CONTENT_PROXY_PORT", "443"
|
buildConfigField "int", "CONTENT_PROXY_PORT", "443"
|
||||||
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
|
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
|
||||||
buildConfigField "String", "CDS_MRENCLAVE", "\"c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15\""
|
buildConfigField "String", "CDS_MRENCLAVE", "\"c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15\""
|
||||||
buildConfigField "String", "KBS_ENCLAVE_NAME", "\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\""
|
buildConfigField "KbsEnclave", "KBS_ENCLAVE", "new KbsEnclave(\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\"," +
|
||||||
buildConfigField "String", "KBS_SERVICE_ID", "\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\""
|
"\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\", " +
|
||||||
buildConfigField "String", "KBS_MRENCLAVE", "\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\""
|
"\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\")";
|
||||||
|
buildConfigField "KbsEnclave[]", "KBS_FALLBACKS", "new KbsEnclave[0]"
|
||||||
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
|
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
|
||||||
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X0=\""
|
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X0=\""
|
||||||
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
|
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
|
||||||
|
@ -214,8 +215,10 @@ android {
|
||||||
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api-staging.directory.signal.org\""
|
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api-staging.directory.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
|
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
|
||||||
buildConfigField "String", "CDS_MRENCLAVE", "\"bd123560b01c8fa92935bc5ae15cd2064e5c45215f23f0bd40364d521329d2ad\""
|
buildConfigField "String", "CDS_MRENCLAVE", "\"bd123560b01c8fa92935bc5ae15cd2064e5c45215f23f0bd40364d521329d2ad\""
|
||||||
buildConfigField "String", "KBS_ENCLAVE_NAME", "\"823a3b2c037ff0cbe305cc48928cfcc97c9ed4a8ca6d49af6f7d6981fb60a4e9\""
|
buildConfigField "KbsEnclave", "KBS_ENCLAVE", "new KbsEnclave(\"823a3b2c037ff0cbe305cc48928cfcc97c9ed4a8ca6d49af6f7d6981fb60a4e9\", " +
|
||||||
buildConfigField "String", "KBS_SERVICE_ID", "\"038c40bbbacdc873caa81ac793bb75afde6dfe436a99ab1f15e3f0cbb7434ced\""
|
"\"038c40bbbacdc873caa81ac793bb75afde6dfe436a99ab1f15e3f0cbb7434ced\", " +
|
||||||
|
"\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\")"
|
||||||
|
buildConfigField "KbsEnclave[]", "KBS_FALLBACKS", "new KbsEnclave[0]"
|
||||||
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
|
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
|
||||||
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdls=\""
|
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdls=\""
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in our {@link BuildConfig} to tie together the various attributes of a KBS instance. This
|
||||||
|
* is sitting in the root directory so it can be accessed by the build config.
|
||||||
|
*/
|
||||||
|
public final class KbsEnclave {
|
||||||
|
|
||||||
|
private final String enclaveName;
|
||||||
|
private final String serviceId;
|
||||||
|
private final String mrEnclave;
|
||||||
|
|
||||||
|
public KbsEnclave(@NonNull String enclaveName, @NonNull String serviceId, @NonNull String mrEnclave) {
|
||||||
|
this.enclaveName = enclaveName;
|
||||||
|
this.serviceId = serviceId;
|
||||||
|
this.mrEnclave = mrEnclave;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull String getMrEnclave() {
|
||||||
|
return mrEnclave;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull String getEnclaveName() {
|
||||||
|
return enclaveName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull String getServiceId() {
|
||||||
|
return serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
KbsEnclave enclave = (KbsEnclave) o;
|
||||||
|
return enclaveName.equals(enclave.enclaveName) &&
|
||||||
|
serviceId.equals(enclave.serviceId) &&
|
||||||
|
mrEnclave.equals(enclave.mrEnclave);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(enclaveName, serviceId, mrEnclave);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import androidx.annotation.MainThread;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.BuildConfig;
|
import org.thoughtcrime.securesms.BuildConfig;
|
||||||
|
import org.thoughtcrime.securesms.KbsEnclave;
|
||||||
import org.thoughtcrime.securesms.messages.IncomingMessageProcessor;
|
import org.thoughtcrime.securesms.messages.IncomingMessageProcessor;
|
||||||
import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever;
|
import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever;
|
||||||
import org.thoughtcrime.securesms.groups.GroupsV2AuthorizationMemoryValueCache;
|
import org.thoughtcrime.securesms.groups.GroupsV2AuthorizationMemoryValueCache;
|
||||||
|
@ -15,6 +16,7 @@ import org.thoughtcrime.securesms.keyvalue.KeyValueStore;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.megaphone.MegaphoneRepository;
|
import org.thoughtcrime.securesms.megaphone.MegaphoneRepository;
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||||
|
import org.thoughtcrime.securesms.pin.KbsEnclaves;
|
||||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||||
import org.thoughtcrime.securesms.recipients.LiveRecipientCache;
|
import org.thoughtcrime.securesms.recipients.LiveRecipientCache;
|
||||||
import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
|
import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
|
||||||
|
@ -111,11 +113,11 @@ public class ApplicationDependencies {
|
||||||
return groupsV2Operations;
|
return groupsV2Operations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized @NonNull KeyBackupService getKeyBackupService() {
|
public static synchronized @NonNull KeyBackupService getKeyBackupService(@NonNull KbsEnclave enclave) {
|
||||||
return getSignalServiceAccountManager().getKeyBackupService(IasKeyStore.getIasKeyStore(application),
|
return getSignalServiceAccountManager().getKeyBackupService(IasKeyStore.getIasKeyStore(application),
|
||||||
BuildConfig.KBS_ENCLAVE_NAME,
|
enclave.getEnclaveName(),
|
||||||
Hex.fromStringOrThrow(BuildConfig.KBS_SERVICE_ID),
|
Hex.fromStringOrThrow(enclave.getServiceId()),
|
||||||
BuildConfig.KBS_MRENCLAVE,
|
enclave.getMrEnclave(),
|
||||||
10);
|
10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
package org.thoughtcrime.securesms.jobs;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.KbsEnclave;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.pin.KbsEnclaves;
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||||
|
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears data from an old KBS enclave.
|
||||||
|
*/
|
||||||
|
public class ClearFallbackKbsEnclaveJob extends BaseJob {
|
||||||
|
|
||||||
|
public static final String KEY = "ClearFallbackKbsEnclaveJob";
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(ClearFallbackKbsEnclaveJob.class);
|
||||||
|
|
||||||
|
private static final String KEY_ENCLAVE_NAME = "enclaveName";
|
||||||
|
private static final String KEY_SERVICE_ID = "serviceId";
|
||||||
|
private static final String KEY_MR_ENCLAVE = "mrEnclave";
|
||||||
|
|
||||||
|
private final KbsEnclave enclave;
|
||||||
|
|
||||||
|
ClearFallbackKbsEnclaveJob(@NonNull KbsEnclave enclave) {
|
||||||
|
this(new Parameters.Builder()
|
||||||
|
.addConstraint(NetworkConstraint.KEY)
|
||||||
|
.setLifespan(TimeUnit.DAYS.toMillis(90))
|
||||||
|
.setMaxAttempts(Parameters.UNLIMITED)
|
||||||
|
.setQueue("ClearFallbackKbsEnclaveJob")
|
||||||
|
.build(),
|
||||||
|
enclave);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearAll() {
|
||||||
|
if (KbsEnclaves.fallbacks().isEmpty()) {
|
||||||
|
Log.i(TAG, "No fallbacks!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JobManager jobManager = ApplicationDependencies.getJobManager();
|
||||||
|
|
||||||
|
for (KbsEnclave enclave : KbsEnclaves.fallbacks()) {
|
||||||
|
jobManager.add(new ClearFallbackKbsEnclaveJob(enclave));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClearFallbackKbsEnclaveJob(@NonNull Parameters parameters, @NonNull KbsEnclave enclave) {
|
||||||
|
super(parameters);
|
||||||
|
this.enclave = enclave;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull String getFactoryKey() {
|
||||||
|
return KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Data serialize() {
|
||||||
|
return new Data.Builder().putString(KEY_ENCLAVE_NAME, enclave.getEnclaveName())
|
||||||
|
.putString(KEY_SERVICE_ID, enclave.getServiceId())
|
||||||
|
.putString(KEY_MR_ENCLAVE, enclave.getMrEnclave())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRun() throws IOException, UnauthenticatedResponseException {
|
||||||
|
Log.i(TAG, "Preparing to delete data from " + enclave.getEnclaveName());
|
||||||
|
ApplicationDependencies.getKeyBackupService(enclave).newPinChangeSession().removePin();
|
||||||
|
Log.i(TAG, "Successfully deleted the data from " + enclave.getEnclaveName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onShouldRetry(@NonNull Exception e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure() {
|
||||||
|
throw new AssertionError("This job should never fail. " + getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Factory implements Job.Factory<ClearFallbackKbsEnclaveJob> {
|
||||||
|
@Override
|
||||||
|
public @NonNull ClearFallbackKbsEnclaveJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||||
|
KbsEnclave enclave = new KbsEnclave(data.getString(KEY_ENCLAVE_NAME),
|
||||||
|
data.getString(KEY_SERVICE_ID),
|
||||||
|
data.getString(KEY_MR_ENCLAVE));
|
||||||
|
|
||||||
|
return new ClearFallbackKbsEnclaveJob(parameters, enclave);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.migrations.AvatarIdRemovalMigrationJob;
|
||||||
import org.thoughtcrime.securesms.migrations.AvatarMigrationJob;
|
import org.thoughtcrime.securesms.migrations.AvatarMigrationJob;
|
||||||
import org.thoughtcrime.securesms.migrations.CachedAttachmentsMigrationJob;
|
import org.thoughtcrime.securesms.migrations.CachedAttachmentsMigrationJob;
|
||||||
import org.thoughtcrime.securesms.migrations.DatabaseMigrationJob;
|
import org.thoughtcrime.securesms.migrations.DatabaseMigrationJob;
|
||||||
|
import org.thoughtcrime.securesms.migrations.KbsEnclaveMigrationJob;
|
||||||
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
|
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
|
||||||
import org.thoughtcrime.securesms.migrations.MigrationCompleteJob;
|
import org.thoughtcrime.securesms.migrations.MigrationCompleteJob;
|
||||||
import org.thoughtcrime.securesms.migrations.PassingMigrationJob;
|
import org.thoughtcrime.securesms.migrations.PassingMigrationJob;
|
||||||
|
@ -62,9 +63,11 @@ public final class JobManagerFactories {
|
||||||
put(AvatarGroupsV1DownloadJob.KEY, new AvatarGroupsV1DownloadJob.Factory());
|
put(AvatarGroupsV1DownloadJob.KEY, new AvatarGroupsV1DownloadJob.Factory());
|
||||||
put(AvatarGroupsV2DownloadJob.KEY, new AvatarGroupsV2DownloadJob.Factory());
|
put(AvatarGroupsV2DownloadJob.KEY, new AvatarGroupsV2DownloadJob.Factory());
|
||||||
put(CleanPreKeysJob.KEY, new CleanPreKeysJob.Factory());
|
put(CleanPreKeysJob.KEY, new CleanPreKeysJob.Factory());
|
||||||
|
put(ClearFallbackKbsEnclaveJob.KEY, new ClearFallbackKbsEnclaveJob.Factory());
|
||||||
put(CreateSignedPreKeyJob.KEY, new CreateSignedPreKeyJob.Factory());
|
put(CreateSignedPreKeyJob.KEY, new CreateSignedPreKeyJob.Factory());
|
||||||
put(DirectoryRefreshJob.KEY, new DirectoryRefreshJob.Factory());
|
put(DirectoryRefreshJob.KEY, new DirectoryRefreshJob.Factory());
|
||||||
put(FcmRefreshJob.KEY, new FcmRefreshJob.Factory());
|
put(FcmRefreshJob.KEY, new FcmRefreshJob.Factory());
|
||||||
|
put(KbsEnclaveMigrationWorkerJob.KEY, new KbsEnclaveMigrationWorkerJob.Factory());
|
||||||
put(LeaveGroupJob.KEY, new LeaveGroupJob.Factory());
|
put(LeaveGroupJob.KEY, new LeaveGroupJob.Factory());
|
||||||
put(LocalBackupJob.KEY, new LocalBackupJob.Factory());
|
put(LocalBackupJob.KEY, new LocalBackupJob.Factory());
|
||||||
put(MmsDownloadJob.KEY, new MmsDownloadJob.Factory());
|
put(MmsDownloadJob.KEY, new MmsDownloadJob.Factory());
|
||||||
|
@ -132,6 +135,7 @@ public final class JobManagerFactories {
|
||||||
put(AvatarMigrationJob.KEY, new AvatarMigrationJob.Factory());
|
put(AvatarMigrationJob.KEY, new AvatarMigrationJob.Factory());
|
||||||
put(CachedAttachmentsMigrationJob.KEY, new CachedAttachmentsMigrationJob.Factory());
|
put(CachedAttachmentsMigrationJob.KEY, new CachedAttachmentsMigrationJob.Factory());
|
||||||
put(DatabaseMigrationJob.KEY, new DatabaseMigrationJob.Factory());
|
put(DatabaseMigrationJob.KEY, new DatabaseMigrationJob.Factory());
|
||||||
|
put(KbsEnclaveMigrationJob.KEY, new KbsEnclaveMigrationJob.Factory());
|
||||||
put(LegacyMigrationJob.KEY, new LegacyMigrationJob.Factory());
|
put(LegacyMigrationJob.KEY, new LegacyMigrationJob.Factory());
|
||||||
put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory());
|
put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory());
|
||||||
put(PinOptOutMigration.KEY, new PinOptOutMigration.Factory());
|
put(PinOptOutMigration.KEY, new PinOptOutMigration.Factory());
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
package org.thoughtcrime.securesms.jobs;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.migrations.KbsEnclaveMigrationJob;
|
||||||
|
import org.thoughtcrime.securesms.pin.PinState;
|
||||||
|
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should only be enqueued by {@link KbsEnclaveMigrationJob}. Does the actual work of migrating KBS
|
||||||
|
* data to the new enclave and deleting it from the old enclave(s).
|
||||||
|
*/
|
||||||
|
public class KbsEnclaveMigrationWorkerJob extends BaseJob {
|
||||||
|
|
||||||
|
public static final String KEY = "KbsEnclaveMigrationWorkerJob";
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(KbsEnclaveMigrationWorkerJob.class);
|
||||||
|
|
||||||
|
public KbsEnclaveMigrationWorkerJob() {
|
||||||
|
this(new Parameters.Builder()
|
||||||
|
.addConstraint(NetworkConstraint.KEY)
|
||||||
|
.setLifespan(Parameters.IMMORTAL)
|
||||||
|
.setMaxAttempts(Parameters.UNLIMITED)
|
||||||
|
.setQueue("KbsEnclaveMigrationWorkerJob")
|
||||||
|
.setMaxInstances(1)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private KbsEnclaveMigrationWorkerJob(@NonNull Parameters parameters) {
|
||||||
|
super(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull String getFactoryKey() {
|
||||||
|
return KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Data serialize() {
|
||||||
|
return Data.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRun() throws IOException, UnauthenticatedResponseException {
|
||||||
|
String pin = SignalStore.kbsValues().getPin();
|
||||||
|
|
||||||
|
if (SignalStore.kbsValues().hasOptedOut()) {
|
||||||
|
Log.w(TAG, "Opted out of KBS! Nothing to migrate.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pin == null) {
|
||||||
|
Log.w(TAG, "No PIN available! Can't migrate!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PinState.onMigrateToNewEnclave(pin);
|
||||||
|
Log.i(TAG, "Migration successful!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onShouldRetry(@NonNull Exception e) {
|
||||||
|
return e instanceof IOException ||
|
||||||
|
e instanceof UnauthenticatedResponseException;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure() {
|
||||||
|
throw new AssertionError("This job should never fail. " + getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Factory implements Job.Factory<KbsEnclaveMigrationWorkerJob> {
|
||||||
|
@Override
|
||||||
|
public @NonNull KbsEnclaveMigrationWorkerJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||||
|
return new KbsEnclaveMigrationWorkerJob(parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -137,6 +137,10 @@ public final class KbsValues extends SignalStoreValues {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized @Nullable String getPin() {
|
||||||
|
return getString(PIN, null);
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized @Nullable String getLocalPinHash() {
|
public synchronized @Nullable String getLocalPinHash() {
|
||||||
return getString(LOCK_LOCAL_PIN_HASH, null);
|
return getString(LOCK_LOCAL_PIN_HASH, null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
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.KbsEnclaveMigrationWorkerJob;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A job to be run whenever we add a new KBS enclave. In order to prevent this moderately-expensive
|
||||||
|
* task from blocking the network for too long, this task simply enqueues another non-migration job,
|
||||||
|
* {@link KbsEnclaveMigrationWorkerJob}, to do the heavy lifting.
|
||||||
|
*/
|
||||||
|
public class KbsEnclaveMigrationJob extends MigrationJob {
|
||||||
|
|
||||||
|
public static final String KEY = "KbsEnclaveMigrationJob";
|
||||||
|
|
||||||
|
KbsEnclaveMigrationJob() {
|
||||||
|
this(new Parameters.Builder().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private KbsEnclaveMigrationJob(@NonNull Parameters parameters) {
|
||||||
|
super(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUiBlocking() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull String getFactoryKey() {
|
||||||
|
return KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void performMigration() {
|
||||||
|
ApplicationDependencies.getJobManager().add(new KbsEnclaveMigrationWorkerJob());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean shouldRetry(@NonNull Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Factory implements Job.Factory<KbsEnclaveMigrationJob> {
|
||||||
|
@Override
|
||||||
|
public @NonNull KbsEnclaveMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||||
|
return new KbsEnclaveMigrationJob(parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.thoughtcrime.securesms.pin;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.BuildConfig;
|
||||||
|
import org.thoughtcrime.securesms.KbsEnclave;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class KbsEnclaves {
|
||||||
|
|
||||||
|
public static @NonNull KbsEnclave current() {
|
||||||
|
return BuildConfig.KBS_ENCLAVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NonNull List<KbsEnclave> all() {
|
||||||
|
return Util.join(Collections.singletonList(BuildConfig.KBS_ENCLAVE), fallbacks());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NonNull List<KbsEnclave> fallbacks() {
|
||||||
|
return Arrays.asList(BuildConfig.KBS_FALLBACKS);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,15 @@
|
||||||
package org.thoughtcrime.securesms.pin;
|
package org.thoughtcrime.securesms.pin;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.KbsEnclave;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob;
|
import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob;
|
||||||
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
|
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.registration.service.KeyBackupSystemWrongPinException;
|
import org.thoughtcrime.securesms.registration.service.KeyBackupSystemWrongPinException;
|
||||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||||
|
@ -21,32 +25,58 @@ import java.util.Objects;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
class PinRestoreRepository {
|
public class PinRestoreRepository {
|
||||||
|
|
||||||
private static final String TAG = Log.tag(PinRestoreRepository.class);
|
private static final String TAG = Log.tag(PinRestoreRepository.class);
|
||||||
|
|
||||||
private final Executor executor = SignalExecutors.UNBOUNDED;
|
private final Executor executor = SignalExecutors.UNBOUNDED;
|
||||||
private final KeyBackupService kbs = ApplicationDependencies.getKeyBackupService();
|
|
||||||
|
|
||||||
void getToken(@NonNull Callback<Optional<TokenData>> callback) {
|
void getToken(@NonNull Callback<Optional<TokenData>> callback) {
|
||||||
executor.execute(() -> {
|
executor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
String authorization = kbs.getAuthorization();
|
callback.onComplete(Optional.fromNullable(getTokenSync(null)));
|
||||||
TokenResponse token = kbs.getToken(authorization);
|
|
||||||
TokenData tokenData = new TokenData(authorization, token);
|
|
||||||
callback.onComplete(Optional.of(tokenData));
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
callback.onComplete(Optional.absent());
|
callback.onComplete(Optional.absent());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param authorization If this is being called before the user is registered (i.e. as part of
|
||||||
|
* reglock), you must pass in an authorization token that can be used to
|
||||||
|
* retrieve a backup. Otherwise, pass in null and we'll fetch one.
|
||||||
|
*/
|
||||||
|
public @NonNull TokenData getTokenSync(@Nullable String authorization) throws IOException {
|
||||||
|
TokenData firstKnownTokenData = null;
|
||||||
|
|
||||||
|
for (KbsEnclave enclave : KbsEnclaves.all()) {
|
||||||
|
KeyBackupService kbs = ApplicationDependencies.getKeyBackupService(enclave);
|
||||||
|
|
||||||
|
authorization = authorization == null ? kbs.getAuthorization() : authorization;
|
||||||
|
|
||||||
|
TokenResponse token = kbs.getToken(authorization);
|
||||||
|
TokenData tokenData = new TokenData(enclave, authorization, token);
|
||||||
|
|
||||||
|
if (tokenData.getTriesRemaining() > 0) {
|
||||||
|
Log.i(TAG, "Found data! " + enclave.getEnclaveName());
|
||||||
|
return tokenData;
|
||||||
|
} else if (firstKnownTokenData == null) {
|
||||||
|
Log.i(TAG, "No data, but storing as the first response. " + enclave.getEnclaveName());
|
||||||
|
firstKnownTokenData = tokenData;
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "No data, and we already have a 'first response'. " + enclave.getEnclaveName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Objects.requireNonNull(firstKnownTokenData);
|
||||||
|
}
|
||||||
|
|
||||||
void submitPin(@NonNull String pin, @NonNull TokenData tokenData, @NonNull Callback<PinResultData> callback) {
|
void submitPin(@NonNull String pin, @NonNull TokenData tokenData, @NonNull Callback<PinResultData> callback) {
|
||||||
executor.execute(() -> {
|
executor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
Stopwatch stopwatch = new Stopwatch("PinSubmission");
|
Stopwatch stopwatch = new Stopwatch("PinSubmission");
|
||||||
|
|
||||||
KbsPinData kbsData = PinState.restoreMasterKey(pin, tokenData.basicAuth, tokenData.tokenResponse);
|
KbsPinData kbsData = PinState.restoreMasterKey(pin, tokenData.getEnclave(), tokenData.getBasicAuth(), tokenData.getTokenResponse());
|
||||||
PinState.onSignalPinRestore(ApplicationDependencies.getApplication(), Objects.requireNonNull(kbsData), pin);
|
PinState.onSignalPinRestore(ApplicationDependencies.getApplication(), Objects.requireNonNull(kbsData), pin);
|
||||||
stopwatch.split("MasterKey");
|
stopwatch.split("MasterKey");
|
||||||
|
|
||||||
|
@ -64,7 +94,7 @@ class PinRestoreRepository {
|
||||||
} catch (KeyBackupSystemNoDataException e) {
|
} catch (KeyBackupSystemNoDataException e) {
|
||||||
callback.onComplete(new PinResultData(PinResult.LOCKED, tokenData));
|
callback.onComplete(new PinResultData(PinResult.LOCKED, tokenData));
|
||||||
} catch (KeyBackupSystemWrongPinException e) {
|
} catch (KeyBackupSystemWrongPinException e) {
|
||||||
callback.onComplete(new PinResultData(PinResult.INCORRECT, new TokenData(tokenData.basicAuth, e.getTokenResponse())));
|
callback.onComplete(new PinResultData(PinResult.INCORRECT, TokenData.withResponse(tokenData, e.getTokenResponse())));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -73,18 +103,81 @@ class PinRestoreRepository {
|
||||||
void onComplete(@NonNull T value);
|
void onComplete(@NonNull T value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static class TokenData {
|
public static class TokenData implements Parcelable {
|
||||||
|
private final KbsEnclave enclave;
|
||||||
private final String basicAuth;
|
private final String basicAuth;
|
||||||
private final TokenResponse tokenResponse;
|
private final TokenResponse tokenResponse;
|
||||||
|
|
||||||
TokenData(@NonNull String basicAuth, @NonNull TokenResponse tokenResponse) {
|
TokenData(@NonNull KbsEnclave enclave, @NonNull String basicAuth, @NonNull TokenResponse tokenResponse) {
|
||||||
|
this.enclave = enclave;
|
||||||
this.basicAuth = basicAuth;
|
this.basicAuth = basicAuth;
|
||||||
this.tokenResponse = tokenResponse;
|
this.tokenResponse = tokenResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getTriesRemaining() {
|
private TokenData(Parcel in) {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
this.enclave = new KbsEnclave(in.readString(), in.readString(), in.readString());
|
||||||
|
this.basicAuth = in.readString();
|
||||||
|
|
||||||
|
byte[] backupId = new byte[0];
|
||||||
|
byte[] token = new byte[0];
|
||||||
|
|
||||||
|
in.readByteArray(backupId);
|
||||||
|
in.readByteArray(token);
|
||||||
|
|
||||||
|
this.tokenResponse = new TokenResponse(backupId, token, in.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NonNull TokenData withResponse(@NonNull TokenData data, @NonNull TokenResponse response) {
|
||||||
|
return new TokenData(data.getEnclave(), data.getBasicAuth(), response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTriesRemaining() {
|
||||||
return tokenResponse.getTries();
|
return tokenResponse.getTries();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @NonNull String getBasicAuth() {
|
||||||
|
return basicAuth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull TokenResponse getTokenResponse() {
|
||||||
|
return tokenResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull KbsEnclave getEnclave() {
|
||||||
|
return enclave;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeString(enclave.getEnclaveName());
|
||||||
|
dest.writeString(enclave.getServiceId());
|
||||||
|
dest.writeString(enclave.getMrEnclave());
|
||||||
|
|
||||||
|
dest.writeString(basicAuth);
|
||||||
|
|
||||||
|
dest.writeByteArray(tokenResponse.getBackupId());
|
||||||
|
dest.writeByteArray(tokenResponse.getToken());
|
||||||
|
dest.writeInt(tokenResponse.getTries());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<TokenData> CREATOR = new Creator<TokenData>() {
|
||||||
|
@Override
|
||||||
|
public TokenData createFromParcel(Parcel in) {
|
||||||
|
return new TokenData(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TokenData[] newArray(int size) {
|
||||||
|
return new TokenData[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class PinResultData {
|
static class PinResultData {
|
||||||
|
@ -92,7 +185,7 @@ class PinRestoreRepository {
|
||||||
private final TokenData tokenData;
|
private final TokenData tokenData;
|
||||||
|
|
||||||
PinResultData(@NonNull PinResult result, @NonNull TokenData tokenData) {
|
PinResultData(@NonNull PinResult result, @NonNull TokenData tokenData) {
|
||||||
this.result = result;
|
this.result = result;
|
||||||
this.tokenData = tokenData;
|
this.tokenData = tokenData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,11 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.BuildConfig;
|
||||||
|
import org.thoughtcrime.securesms.KbsEnclave;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobmanager.JobTracker;
|
import org.thoughtcrime.securesms.jobmanager.JobTracker;
|
||||||
|
import org.thoughtcrime.securesms.jobs.ClearFallbackKbsEnclaveJob;
|
||||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
||||||
import org.thoughtcrime.securesms.jobs.StorageForcePushJob;
|
import org.thoughtcrime.securesms.jobs.StorageForcePushJob;
|
||||||
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
||||||
|
@ -26,12 +29,14 @@ import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -46,6 +51,7 @@ public final class PinState {
|
||||||
* Does not affect {@link PinState}.
|
* Does not affect {@link PinState}.
|
||||||
*/
|
*/
|
||||||
public static synchronized @Nullable KbsPinData restoreMasterKey(@Nullable String pin,
|
public static synchronized @Nullable KbsPinData restoreMasterKey(@Nullable String pin,
|
||||||
|
@NonNull KbsEnclave enclave,
|
||||||
@Nullable String basicStorageCredentials,
|
@Nullable String basicStorageCredentials,
|
||||||
@NonNull TokenResponse tokenResponse)
|
@NonNull TokenResponse tokenResponse)
|
||||||
throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException
|
throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException
|
||||||
|
@ -58,20 +64,31 @@ public final class PinState {
|
||||||
throw new AssertionError("Cannot restore KBS key, no storage credentials supplied");
|
throw new AssertionError("Cannot restore KBS key, no storage credentials supplied");
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
Log.i(TAG, "Preparing to restore from " + enclave.getEnclaveName());
|
||||||
|
return restoreMasterKeyFromEnclave(enclave, pin, basicStorageCredentials, tokenResponse);
|
||||||
|
}
|
||||||
|
|
||||||
Log.i(TAG, "Opening key backup service session");
|
private static @NonNull KbsPinData restoreMasterKeyFromEnclave(@NonNull KbsEnclave enclave,
|
||||||
KeyBackupService.RestoreSession session = keyBackupService.newRegistrationSession(basicStorageCredentials, tokenResponse);
|
@NonNull String pin,
|
||||||
|
@NonNull String basicStorageCredentials,
|
||||||
|
@NonNull TokenResponse tokenResponse)
|
||||||
|
throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException
|
||||||
|
{
|
||||||
|
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(enclave);
|
||||||
|
KeyBackupService.RestoreSession session = keyBackupService.newRegistrationSession(basicStorageCredentials, tokenResponse);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Log.i(TAG, "Restoring pin from KBS");
|
Log.i(TAG, "Restoring pin from KBS");
|
||||||
|
|
||||||
HashedPin hashedPin = PinHashing.hashPin(pin, session);
|
HashedPin hashedPin = PinHashing.hashPin(pin, session);
|
||||||
KbsPinData kbsData = session.restorePin(hashedPin);
|
KbsPinData kbsData = session.restorePin(hashedPin);
|
||||||
|
|
||||||
if (kbsData != null) {
|
if (kbsData != null) {
|
||||||
Log.i(TAG, "Found registration lock token on KBS.");
|
Log.i(TAG, "Found registration lock token on KBS.");
|
||||||
} else {
|
} else {
|
||||||
throw new AssertionError("Null not expected");
|
throw new AssertionError("Null not expected");
|
||||||
}
|
}
|
||||||
|
|
||||||
return kbsData;
|
return kbsData;
|
||||||
} catch (UnauthenticatedResponseException e) {
|
} catch (UnauthenticatedResponseException e) {
|
||||||
Log.w(TAG, "Failed to restore key", e);
|
Log.w(TAG, "Failed to restore key", e);
|
||||||
|
@ -90,7 +107,7 @@ public final class PinState {
|
||||||
@Nullable String pin,
|
@Nullable String pin,
|
||||||
boolean hasPinToRestore)
|
boolean hasPinToRestore)
|
||||||
{
|
{
|
||||||
Log.i(TAG, "onNewRegistration()");
|
Log.i(TAG, "onRegistration()");
|
||||||
|
|
||||||
TextSecurePreferences.setV1RegistrationLockPin(context, pin);
|
TextSecurePreferences.setV1RegistrationLockPin(context, pin);
|
||||||
|
|
||||||
|
@ -106,7 +123,8 @@ public final class PinState {
|
||||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(true);
|
SignalStore.kbsValues().setV2RegistrationLockEnabled(true);
|
||||||
SignalStore.kbsValues().setKbsMasterKey(kbsData, pin);
|
SignalStore.kbsValues().setKbsMasterKey(kbsData, pin);
|
||||||
SignalStore.pinValues().resetPinReminders();
|
SignalStore.pinValues().resetPinReminders();
|
||||||
resetPinRetryCount(context, pin, kbsData);
|
resetPinRetryCount(context, pin);
|
||||||
|
ClearFallbackKbsEnclaveJob.clearAll();
|
||||||
} else if (hasPinToRestore) {
|
} else if (hasPinToRestore) {
|
||||||
Log.i(TAG, "Has a PIN to restore.");
|
Log.i(TAG, "Has a PIN to restore.");
|
||||||
SignalStore.kbsValues().clearRegistrationLockAndPin();
|
SignalStore.kbsValues().clearRegistrationLockAndPin();
|
||||||
|
@ -131,7 +149,8 @@ public final class PinState {
|
||||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(false);
|
SignalStore.kbsValues().setV2RegistrationLockEnabled(false);
|
||||||
SignalStore.pinValues().resetPinReminders();
|
SignalStore.pinValues().resetPinReminders();
|
||||||
SignalStore.storageServiceValues().setNeedsAccountRestore(false);
|
SignalStore.storageServiceValues().setNeedsAccountRestore(false);
|
||||||
resetPinRetryCount(context, pin, kbsData);
|
resetPinRetryCount(context, pin);
|
||||||
|
ClearFallbackKbsEnclaveJob.clearAll();
|
||||||
|
|
||||||
updateState(buildInferredStateFromOtherFields());
|
updateState(buildInferredStateFromOtherFields());
|
||||||
}
|
}
|
||||||
|
@ -158,7 +177,7 @@ public final class PinState {
|
||||||
KbsValues kbsValues = SignalStore.kbsValues();
|
KbsValues kbsValues = SignalStore.kbsValues();
|
||||||
boolean isFirstPin = !kbsValues.hasPin() || kbsValues.hasOptedOut();
|
boolean isFirstPin = !kbsValues.hasPin() || kbsValues.hasOptedOut();
|
||||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(KbsEnclaves.current());
|
||||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||||
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
||||||
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||||
|
@ -217,7 +236,7 @@ public final class PinState {
|
||||||
assertState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED);
|
assertState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED);
|
||||||
|
|
||||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(false);
|
SignalStore.kbsValues().setV2RegistrationLockEnabled(false);
|
||||||
ApplicationDependencies.getKeyBackupService()
|
ApplicationDependencies.getKeyBackupService(KbsEnclaves.current())
|
||||||
.newPinChangeSession(SignalStore.kbsValues().getRegistrationLockTokenResponse())
|
.newPinChangeSession(SignalStore.kbsValues().getRegistrationLockTokenResponse())
|
||||||
.enableRegistrationLock(SignalStore.kbsValues().getOrCreateMasterKey());
|
.enableRegistrationLock(SignalStore.kbsValues().getOrCreateMasterKey());
|
||||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(true);
|
SignalStore.kbsValues().setV2RegistrationLockEnabled(true);
|
||||||
|
@ -240,7 +259,7 @@ public final class PinState {
|
||||||
assertState(State.PIN_WITH_REGISTRATION_LOCK_ENABLED);
|
assertState(State.PIN_WITH_REGISTRATION_LOCK_ENABLED);
|
||||||
|
|
||||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(true);
|
SignalStore.kbsValues().setV2RegistrationLockEnabled(true);
|
||||||
ApplicationDependencies.getKeyBackupService()
|
ApplicationDependencies.getKeyBackupService(KbsEnclaves.current())
|
||||||
.newPinChangeSession(SignalStore.kbsValues().getRegistrationLockTokenResponse())
|
.newPinChangeSession(SignalStore.kbsValues().getRegistrationLockTokenResponse())
|
||||||
.disableRegistrationLock();
|
.disableRegistrationLock();
|
||||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(false);
|
SignalStore.kbsValues().setV2RegistrationLockEnabled(false);
|
||||||
|
@ -259,7 +278,7 @@ public final class PinState {
|
||||||
|
|
||||||
KbsValues kbsValues = SignalStore.kbsValues();
|
KbsValues kbsValues = SignalStore.kbsValues();
|
||||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(KbsEnclaves.current());
|
||||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||||
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
||||||
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||||
|
@ -272,6 +291,22 @@ public final class PinState {
|
||||||
updateState(buildInferredStateFromOtherFields());
|
updateState(buildInferredStateFromOtherFields());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should only be called by {@link org.thoughtcrime.securesms.jobs.KbsEnclaveMigrationWorkerJob}.
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
public static synchronized void onMigrateToNewEnclave(@NonNull String pin)
|
||||||
|
throws IOException, UnauthenticatedResponseException
|
||||||
|
{
|
||||||
|
Log.i(TAG, "onMigrateToNewEnclave()");
|
||||||
|
assertState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED, State.PIN_WITH_REGISTRATION_LOCK_ENABLED);
|
||||||
|
|
||||||
|
Log.i(TAG, "Migrating to enclave " + KbsEnclaves.current().getEnclaveName());
|
||||||
|
setPinOnEnclave(KbsEnclaves.current(), pin, SignalStore.kbsValues().getOrCreateMasterKey());
|
||||||
|
|
||||||
|
ClearFallbackKbsEnclaveJob.clearAll();
|
||||||
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private static void bestEffortRefreshAttributes() {
|
private static void bestEffortRefreshAttributes() {
|
||||||
Optional<JobTracker.JobState> result = ApplicationDependencies.getJobManager().runSynchronously(new RefreshAttributesJob(), TimeUnit.SECONDS.toMillis(10));
|
Optional<JobTracker.JobState> result = ApplicationDependencies.getJobManager().runSynchronously(new RefreshAttributesJob(), TimeUnit.SECONDS.toMillis(10));
|
||||||
|
@ -301,23 +336,14 @@ public final class PinState {
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private static void resetPinRetryCount(@NonNull Context context, @Nullable String pin, @NonNull KbsPinData kbsData) {
|
private static void resetPinRetryCount(@NonNull Context context, @Nullable String pin) {
|
||||||
if (pin == null) {
|
if (pin == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
KbsValues kbsValues = SignalStore.kbsValues();
|
setPinOnEnclave(KbsEnclaves.current(), pin, SignalStore.kbsValues().getOrCreateMasterKey());
|
||||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
|
||||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession(kbsData.getTokenResponse());
|
|
||||||
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
|
||||||
KbsPinData newData = pinChangeSession.setPin(hashedPin, masterKey);
|
|
||||||
|
|
||||||
kbsValues.setKbsMasterKey(newData, pin);
|
|
||||||
TextSecurePreferences.clearRegistrationLockV1(context);
|
TextSecurePreferences.clearRegistrationLockV1(context);
|
||||||
|
|
||||||
Log.i(TAG, "Pin set/attempts reset on KBS");
|
Log.i(TAG, "Pin set/attempts reset on KBS");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, "May have failed to reset pin attempts!", e);
|
Log.w(TAG, "May have failed to reset pin attempts!", e);
|
||||||
|
@ -326,6 +352,20 @@ public final class PinState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private static @NonNull KbsPinData setPinOnEnclave(@NonNull KbsEnclave enclave, @NonNull String pin, @NonNull MasterKey masterKey)
|
||||||
|
throws IOException, UnauthenticatedResponseException
|
||||||
|
{
|
||||||
|
KeyBackupService kbs = ApplicationDependencies.getKeyBackupService(enclave);
|
||||||
|
KeyBackupService.PinChangeSession pinChangeSession = kbs.newPinChangeSession();
|
||||||
|
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
||||||
|
KbsPinData newData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||||
|
|
||||||
|
SignalStore.kbsValues().setKbsMasterKey(newData, pin);
|
||||||
|
|
||||||
|
return newData;
|
||||||
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private static void optOutOfPin() {
|
private static void optOutOfPin() {
|
||||||
SignalStore.kbsValues().optOut();
|
SignalStore.kbsValues().optOut();
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.components.registration.CallMeCountDownView;
|
||||||
import org.thoughtcrime.securesms.components.registration.VerificationCodeView;
|
import org.thoughtcrime.securesms.components.registration.VerificationCodeView;
|
||||||
import org.thoughtcrime.securesms.components.registration.VerificationPinKeyboard;
|
import org.thoughtcrime.securesms.components.registration.VerificationPinKeyboard;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.pin.PinRestoreRepository;
|
||||||
import org.thoughtcrime.securesms.registration.ReceivedSmsEvent;
|
import org.thoughtcrime.securesms.registration.ReceivedSmsEvent;
|
||||||
import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest;
|
import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest;
|
||||||
import org.thoughtcrime.securesms.registration.service.RegistrationCodeRequest;
|
import org.thoughtcrime.securesms.registration.service.RegistrationCodeRequest;
|
||||||
|
@ -30,7 +31,6 @@ import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
import org.thoughtcrime.securesms.util.SupportEmailUtil;
|
import org.thoughtcrime.securesms.util.SupportEmailUtil;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -107,7 +107,7 @@ public final class EnterCodeFragment extends BaseRegistrationFragment {
|
||||||
|
|
||||||
RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
|
RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
|
||||||
|
|
||||||
registrationService.verifyAccount(requireActivity(), model.getFcmToken(), code, null, null, null,
|
registrationService.verifyAccount(requireActivity(), model.getFcmToken(), code, null, null,
|
||||||
new CodeVerificationRequest.VerifyCallback() {
|
new CodeVerificationRequest.VerifyCallback() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -133,10 +133,9 @@ public final class EnterCodeFragment extends BaseRegistrationFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse tokenResponse, @NonNull String kbsStorageCredentials) {
|
public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull PinRestoreRepository.TokenData tokenData, @NonNull String kbsStorageCredentials) {
|
||||||
model.setLockedTimeRemaining(timeRemaining);
|
model.setLockedTimeRemaining(timeRemaining);
|
||||||
model.setStorageCredentials(kbsStorageCredentials);
|
model.setKeyBackupTokenData(tokenData);
|
||||||
model.setKeyBackupCurrentToken(tokenResponse);
|
|
||||||
keyboard.displayLocked().addListener(new AssertedSuccessListener<Boolean>() {
|
keyboard.displayLocked().addListener(new AssertedSuccessListener<Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Boolean r) {
|
public void onSuccess(Boolean r) {
|
||||||
|
@ -147,7 +146,7 @@ public final class EnterCodeFragment extends BaseRegistrationFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse tokenResponse) {
|
public void onIncorrectKbsRegistrationLockPin(@NonNull PinRestoreRepository.TokenData tokenData) {
|
||||||
throw new AssertionError("Unexpected, user has made no pin guesses");
|
throw new AssertionError("Unexpected, user has made no pin guesses");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,12 +25,12 @@ import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType;
|
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.pin.PinRestoreRepository.TokenData;
|
||||||
import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest;
|
import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest;
|
||||||
import org.thoughtcrime.securesms.registration.service.RegistrationService;
|
import org.thoughtcrime.securesms.registration.service.RegistrationService;
|
||||||
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
|
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -106,10 +106,10 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
|
||||||
getModel().getLockedTimeRemaining()
|
getModel().getLockedTimeRemaining()
|
||||||
.observe(getViewLifecycleOwner(), t -> timeRemaining = t);
|
.observe(getViewLifecycleOwner(), t -> timeRemaining = t);
|
||||||
|
|
||||||
TokenResponse keyBackupCurrentToken = getModel().getKeyBackupCurrentToken();
|
TokenData keyBackupCurrentToken = getModel().getKeyBackupCurrentToken();
|
||||||
|
|
||||||
if (keyBackupCurrentToken != null) {
|
if (keyBackupCurrentToken != null) {
|
||||||
int triesRemaining = keyBackupCurrentToken.getTries();
|
int triesRemaining = keyBackupCurrentToken.getTriesRemaining();
|
||||||
if (triesRemaining <= 3) {
|
if (triesRemaining <= 3) {
|
||||||
int daysRemaining = getLockoutDays(timeRemaining);
|
int daysRemaining = getLockoutDays(timeRemaining);
|
||||||
|
|
||||||
|
@ -158,8 +158,7 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
|
||||||
|
|
||||||
RegistrationViewModel model = getModel();
|
RegistrationViewModel model = getModel();
|
||||||
RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
|
RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
|
||||||
TokenResponse tokenResponse = model.getKeyBackupCurrentToken();
|
TokenData tokenData = model.getKeyBackupCurrentToken();
|
||||||
String basicStorageCredentials = model.getBasicStorageCredentials();
|
|
||||||
|
|
||||||
setSpinning(pinButton);
|
setSpinning(pinButton);
|
||||||
|
|
||||||
|
@ -167,8 +166,7 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
|
||||||
model.getFcmToken(),
|
model.getFcmToken(),
|
||||||
model.getTextCodeEntered(),
|
model.getTextCodeEntered(),
|
||||||
pin,
|
pin,
|
||||||
basicStorageCredentials,
|
tokenData,
|
||||||
tokenResponse,
|
|
||||||
|
|
||||||
new CodeVerificationRequest.VerifyCallback() {
|
new CodeVerificationRequest.VerifyCallback() {
|
||||||
|
|
||||||
|
@ -189,19 +187,19 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse kbsTokenResponse, @NonNull String kbsStorageCredentials) {
|
public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenData kbsTokenData, @NonNull String kbsStorageCredentials) {
|
||||||
throw new AssertionError("Not expected after a pin guess");
|
throw new AssertionError("Not expected after a pin guess");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse tokenResponse) {
|
public void onIncorrectKbsRegistrationLockPin(@NonNull TokenData tokenData) {
|
||||||
cancelSpinning(pinButton);
|
cancelSpinning(pinButton);
|
||||||
pinEntry.getText().clear();
|
pinEntry.getText().clear();
|
||||||
enableAndFocusPinEntry();
|
enableAndFocusPinEntry();
|
||||||
|
|
||||||
model.setKeyBackupCurrentToken(tokenResponse);
|
model.setKeyBackupTokenData(tokenData);
|
||||||
|
|
||||||
int triesRemaining = tokenResponse.getTries();
|
int triesRemaining = tokenData.getTriesRemaining();
|
||||||
|
|
||||||
if (triesRemaining == 0) {
|
if (triesRemaining == 0) {
|
||||||
Log.w(TAG, "Account locked. User out of attempts on KBS.");
|
Log.w(TAG, "Account locked. User out of attempts on KBS.");
|
||||||
|
|
|
@ -22,6 +22,8 @@ import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
|
||||||
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
|
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.pin.PinRestoreRepository;
|
||||||
|
import org.thoughtcrime.securesms.pin.PinRestoreRepository.TokenData;
|
||||||
import org.thoughtcrime.securesms.pin.PinState;
|
import org.thoughtcrime.securesms.pin.PinState;
|
||||||
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
@ -65,41 +67,40 @@ public final class CodeVerificationRequest {
|
||||||
/**
|
/**
|
||||||
* Asynchronously verify the account via the code.
|
* Asynchronously verify the account via the code.
|
||||||
*
|
*
|
||||||
* @param fcmToken The FCM token for the device.
|
* @param fcmToken The FCM token for the device.
|
||||||
* @param code The code that was delivered to the user.
|
* @param code The code that was delivered to the user.
|
||||||
* @param pin The users registration pin.
|
* @param pin The users registration pin.
|
||||||
* @param callback Exactly one method on this callback will be called.
|
* @param callback Exactly one method on this callback will be called.
|
||||||
* @param kbsTokenResponse By keeping the token, on failure, a newly returned token will be reused in subsequent pin
|
* @param kbsTokenData By keeping the token, on failure, a newly returned token will be reused in subsequent pin
|
||||||
* attempts, preventing certain attacks, we can also track the attempts making missing replies easier to spot.
|
* attempts, preventing certain attacks, we can also track the attempts making missing replies easier to spot.
|
||||||
*/
|
*/
|
||||||
static void verifyAccount(@NonNull Context context,
|
static void verifyAccount(@NonNull Context context,
|
||||||
@NonNull Credentials credentials,
|
@NonNull Credentials credentials,
|
||||||
@Nullable String fcmToken,
|
@Nullable String fcmToken,
|
||||||
@NonNull String code,
|
@NonNull String code,
|
||||||
@Nullable String pin,
|
@Nullable String pin,
|
||||||
@Nullable String basicStorageCredentials,
|
@Nullable TokenData kbsTokenData,
|
||||||
@Nullable TokenResponse kbsTokenResponse,
|
|
||||||
@NonNull VerifyCallback callback)
|
@NonNull VerifyCallback callback)
|
||||||
{
|
{
|
||||||
new AsyncTask<Void, Void, Result>() {
|
new AsyncTask<Void, Void, Result>() {
|
||||||
|
|
||||||
private volatile LockedException lockedException;
|
private volatile LockedException lockedException;
|
||||||
private volatile TokenResponse kbsToken;
|
private volatile TokenData tokenData;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Result doInBackground(Void... voids) {
|
protected Result doInBackground(Void... voids) {
|
||||||
final boolean pinSupplied = pin != null;
|
final boolean pinSupplied = pin != null;
|
||||||
final boolean tryKbs = kbsTokenResponse != null;
|
final boolean tryKbs = tokenData != null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
kbsToken = kbsTokenResponse;
|
this.tokenData = kbsTokenData;
|
||||||
verifyAccount(context, credentials, code, pin, kbsTokenResponse, basicStorageCredentials, fcmToken);
|
verifyAccount(context, credentials, code, pin, tokenData, fcmToken);
|
||||||
return Result.SUCCESS;
|
return Result.SUCCESS;
|
||||||
} catch (KeyBackupSystemNoDataException e) {
|
} catch (KeyBackupSystemNoDataException e) {
|
||||||
Log.w(TAG, "No data found on KBS");
|
Log.w(TAG, "No data found on KBS");
|
||||||
return Result.KBS_ACCOUNT_LOCKED;
|
return Result.KBS_ACCOUNT_LOCKED;
|
||||||
} catch (KeyBackupSystemWrongPinException e) {
|
} catch (KeyBackupSystemWrongPinException e) {
|
||||||
kbsToken = e.getTokenResponse();
|
tokenData = TokenData.withResponse(tokenData, e.getTokenResponse());
|
||||||
return Result.KBS_WRONG_PIN;
|
return Result.KBS_WRONG_PIN;
|
||||||
} catch (LockedException e) {
|
} catch (LockedException e) {
|
||||||
if (pinSupplied && tryKbs) {
|
if (pinSupplied && tryKbs) {
|
||||||
|
@ -110,8 +111,8 @@ public final class CodeVerificationRequest {
|
||||||
lockedException = e;
|
lockedException = e;
|
||||||
if (e.getBasicStorageCredentials() != null) {
|
if (e.getBasicStorageCredentials() != null) {
|
||||||
try {
|
try {
|
||||||
kbsToken = getToken(e.getBasicStorageCredentials());
|
tokenData = getToken(e.getBasicStorageCredentials());
|
||||||
if (kbsToken == null || kbsToken.getTries() == 0) {
|
if (tokenData == null || tokenData.getTriesRemaining() == 0) {
|
||||||
return Result.KBS_ACCOUNT_LOCKED;
|
return Result.KBS_ACCOUNT_LOCKED;
|
||||||
}
|
}
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
|
@ -137,12 +138,12 @@ public final class CodeVerificationRequest {
|
||||||
callback.onSuccessfulRegistration();
|
callback.onSuccessfulRegistration();
|
||||||
break;
|
break;
|
||||||
case PIN_LOCKED:
|
case PIN_LOCKED:
|
||||||
if (kbsToken != null) {
|
if (tokenData != null) {
|
||||||
if (lockedException.getBasicStorageCredentials() == null) {
|
if (lockedException.getBasicStorageCredentials() == null) {
|
||||||
throw new AssertionError("KBS Token set, but no storage credentials supplied.");
|
throw new AssertionError("KBS Token set, but no storage credentials supplied.");
|
||||||
}
|
}
|
||||||
Log.w(TAG, "Reg Locked: V2 pin needed for registration");
|
Log.w(TAG, "Reg Locked: V2 pin needed for registration");
|
||||||
callback.onKbsRegistrationLockPinRequired(lockedException.getTimeRemaining(), kbsToken, lockedException.getBasicStorageCredentials());
|
callback.onKbsRegistrationLockPinRequired(lockedException.getTimeRemaining(), tokenData, lockedException.getBasicStorageCredentials());
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Reg Locked: V1 pin needed for registration");
|
Log.w(TAG, "Reg Locked: V1 pin needed for registration");
|
||||||
callback.onV1RegistrationLockPinRequiredOrIncorrect(lockedException.getTimeRemaining());
|
callback.onV1RegistrationLockPinRequiredOrIncorrect(lockedException.getTimeRemaining());
|
||||||
|
@ -156,7 +157,7 @@ public final class CodeVerificationRequest {
|
||||||
break;
|
break;
|
||||||
case KBS_WRONG_PIN:
|
case KBS_WRONG_PIN:
|
||||||
Log.w(TAG, "KBS Pin was wrong");
|
Log.w(TAG, "KBS Pin was wrong");
|
||||||
callback.onIncorrectKbsRegistrationLockPin(kbsToken);
|
callback.onIncorrectKbsRegistrationLockPin(tokenData);
|
||||||
break;
|
break;
|
||||||
case KBS_ACCOUNT_LOCKED:
|
case KBS_ACCOUNT_LOCKED:
|
||||||
Log.w(TAG, "KBS Account is locked");
|
Log.w(TAG, "KBS Account is locked");
|
||||||
|
@ -167,9 +168,9 @@ public final class CodeVerificationRequest {
|
||||||
}.executeOnExecutor(SignalExecutors.UNBOUNDED);
|
}.executeOnExecutor(SignalExecutors.UNBOUNDED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TokenResponse getToken(@Nullable String basicStorageCredentials) throws IOException {
|
private static TokenData getToken(@Nullable String basicStorageCredentials) throws IOException {
|
||||||
if (basicStorageCredentials == null) return null;
|
if (basicStorageCredentials == null) return null;
|
||||||
return ApplicationDependencies.getKeyBackupService().getToken(basicStorageCredentials);
|
return new PinRestoreRepository().getTokenSync(basicStorageCredentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void handleSuccessfulRegistration(@NonNull Context context) {
|
private static void handleSuccessfulRegistration(@NonNull Context context) {
|
||||||
|
@ -185,12 +186,11 @@ public final class CodeVerificationRequest {
|
||||||
@NonNull Credentials credentials,
|
@NonNull Credentials credentials,
|
||||||
@NonNull String code,
|
@NonNull String code,
|
||||||
@Nullable String pin,
|
@Nullable String pin,
|
||||||
@Nullable TokenResponse kbsTokenResponse,
|
@Nullable TokenData kbsTokenData,
|
||||||
@Nullable String kbsStorageCredentials,
|
|
||||||
@Nullable String fcmToken)
|
@Nullable String fcmToken)
|
||||||
throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException
|
throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException
|
||||||
{
|
{
|
||||||
boolean isV2RegistrationLock = kbsTokenResponse != null;
|
boolean isV2RegistrationLock = kbsTokenData != null;
|
||||||
int registrationId = KeyHelper.generateRegistrationId(false);
|
int registrationId = KeyHelper.generateRegistrationId(false);
|
||||||
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
|
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
|
||||||
ProfileKey profileKey = findExistingProfileKey(context, credentials.getE164number());
|
ProfileKey profileKey = findExistingProfileKey(context, credentials.getE164number());
|
||||||
|
@ -206,7 +206,7 @@ public final class CodeVerificationRequest {
|
||||||
SessionUtil.archiveAllSessions(context);
|
SessionUtil.archiveAllSessions(context);
|
||||||
|
|
||||||
SignalServiceAccountManager accountManager = AccountManagerFactory.createUnauthenticated(context, credentials.getE164number(), credentials.getPassword());
|
SignalServiceAccountManager accountManager = AccountManagerFactory.createUnauthenticated(context, credentials.getE164number(), credentials.getPassword());
|
||||||
KbsPinData kbsData = isV2RegistrationLock ? PinState.restoreMasterKey(pin, kbsStorageCredentials, kbsTokenResponse) : null;
|
KbsPinData kbsData = isV2RegistrationLock ? PinState.restoreMasterKey(pin, kbsTokenData.getEnclave(), kbsTokenData.getBasicAuth(), kbsTokenData.getTokenResponse()) : null;
|
||||||
String registrationLockV2 = kbsData != null ? kbsData.getMasterKey().deriveRegistrationLock() : null;
|
String registrationLockV2 = kbsData != null ? kbsData.getMasterKey().deriveRegistrationLock() : null;
|
||||||
String registrationLockV1 = isV2RegistrationLock ? null : pin;
|
String registrationLockV1 = isV2RegistrationLock ? null : pin;
|
||||||
boolean hasFcm = fcmToken != null;
|
boolean hasFcm = fcmToken != null;
|
||||||
|
@ -292,14 +292,14 @@ public final class CodeVerificationRequest {
|
||||||
/**
|
/**
|
||||||
* The account is locked with a V2 (KBS) pin. Called before any user pin guesses.
|
* The account is locked with a V2 (KBS) pin. Called before any user pin guesses.
|
||||||
*/
|
*/
|
||||||
void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse kbsTokenResponse, @NonNull String kbsStorageCredentials);
|
void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenData kbsTokenData, @NonNull String kbsStorageCredentials);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The account is locked with a V2 (KBS) pin. Called after a user pin guess.
|
* The account is locked with a V2 (KBS) pin. Called after a user pin guess.
|
||||||
* <p>
|
* <p>
|
||||||
* i.e. an attempt has likely been used.
|
* i.e. an attempt has likely been used.
|
||||||
*/
|
*/
|
||||||
void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse kbsTokenResponse);
|
void onIncorrectKbsRegistrationLockPin(@NonNull TokenData kbsTokenResponse);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* V2 (KBS) pin is set, but there is no data on KBS.
|
* V2 (KBS) pin is set, but there is no data on KBS.
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.app.Activity;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.pin.PinRestoreRepository;
|
||||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -39,10 +40,9 @@ public final class RegistrationService {
|
||||||
@Nullable String fcmToken,
|
@Nullable String fcmToken,
|
||||||
@NonNull String code,
|
@NonNull String code,
|
||||||
@Nullable String pin,
|
@Nullable String pin,
|
||||||
@Nullable String basicStorageCredentials,
|
@Nullable PinRestoreRepository.TokenData tokenData,
|
||||||
@Nullable TokenResponse tokenResponse,
|
|
||||||
@NonNull CodeVerificationRequest.VerifyCallback callback)
|
@NonNull CodeVerificationRequest.VerifyCallback callback)
|
||||||
{
|
{
|
||||||
CodeVerificationRequest.verifyAccount(activity, credentials, fcmToken, code, pin, basicStorageCredentials, tokenResponse, callback);
|
CodeVerificationRequest.verifyAccount(activity, credentials, fcmToken, code, pin, tokenData, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import androidx.lifecycle.SavedStateHandle;
|
||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.pin.PinRestoreRepository.TokenData;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||||
import org.whispersystems.signalservice.internal.util.JsonUtil;
|
import org.whispersystems.signalservice.internal.util.JsonUtil;
|
||||||
|
@ -28,11 +29,10 @@ public final class RegistrationViewModel extends ViewModel {
|
||||||
private final MutableLiveData<String> textCodeEntered;
|
private final MutableLiveData<String> textCodeEntered;
|
||||||
private final MutableLiveData<String> captchaToken;
|
private final MutableLiveData<String> captchaToken;
|
||||||
private final MutableLiveData<String> fcmToken;
|
private final MutableLiveData<String> fcmToken;
|
||||||
private final MutableLiveData<String> basicStorageCredentials;
|
|
||||||
private final MutableLiveData<Boolean> restoreFlowShown;
|
private final MutableLiveData<Boolean> restoreFlowShown;
|
||||||
private final MutableLiveData<Integer> successfulCodeRequestAttempts;
|
private final MutableLiveData<Integer> successfulCodeRequestAttempts;
|
||||||
private final MutableLiveData<LocalCodeRequestRateLimiter> requestLimiter;
|
private final MutableLiveData<LocalCodeRequestRateLimiter> requestLimiter;
|
||||||
private final MutableLiveData<String> keyBackupCurrentTokenJson;
|
private final MutableLiveData<TokenData> kbsTokenData;
|
||||||
private final MutableLiveData<Long> lockedTimeRemaining;
|
private final MutableLiveData<Long> lockedTimeRemaining;
|
||||||
private final MutableLiveData<Long> canCallAtTime;
|
private final MutableLiveData<Long> canCallAtTime;
|
||||||
|
|
||||||
|
@ -43,11 +43,10 @@ public final class RegistrationViewModel extends ViewModel {
|
||||||
textCodeEntered = savedStateHandle.getLiveData("TEXT_CODE_ENTERED", "");
|
textCodeEntered = savedStateHandle.getLiveData("TEXT_CODE_ENTERED", "");
|
||||||
captchaToken = savedStateHandle.getLiveData("CAPTCHA");
|
captchaToken = savedStateHandle.getLiveData("CAPTCHA");
|
||||||
fcmToken = savedStateHandle.getLiveData("FCM_TOKEN");
|
fcmToken = savedStateHandle.getLiveData("FCM_TOKEN");
|
||||||
basicStorageCredentials = savedStateHandle.getLiveData("BASIC_STORAGE_CREDENTIALS");
|
|
||||||
restoreFlowShown = savedStateHandle.getLiveData("RESTORE_FLOW_SHOWN", false);
|
restoreFlowShown = savedStateHandle.getLiveData("RESTORE_FLOW_SHOWN", false);
|
||||||
successfulCodeRequestAttempts = savedStateHandle.getLiveData("SUCCESSFUL_CODE_REQUEST_ATTEMPTS", 0);
|
successfulCodeRequestAttempts = savedStateHandle.getLiveData("SUCCESSFUL_CODE_REQUEST_ATTEMPTS", 0);
|
||||||
requestLimiter = savedStateHandle.getLiveData("REQUEST_RATE_LIMITER", new LocalCodeRequestRateLimiter(60_000));
|
requestLimiter = savedStateHandle.getLiveData("REQUEST_RATE_LIMITER", new LocalCodeRequestRateLimiter(60_000));
|
||||||
keyBackupCurrentTokenJson = savedStateHandle.getLiveData("KBS_TOKEN");
|
kbsTokenData = savedStateHandle.getLiveData("KBS_TOKEN");
|
||||||
lockedTimeRemaining = savedStateHandle.getLiveData("TIME_REMAINING", 0L);
|
lockedTimeRemaining = savedStateHandle.getLiveData("TIME_REMAINING", 0L);
|
||||||
canCallAtTime = savedStateHandle.getLiveData("CAN_CALL_AT_TIME", 0L);
|
canCallAtTime = savedStateHandle.getLiveData("CAN_CALL_AT_TIME", 0L);
|
||||||
}
|
}
|
||||||
|
@ -158,28 +157,12 @@ public final class RegistrationViewModel extends ViewModel {
|
||||||
requestLimiter.setValue(requestLimiter.getValue());
|
requestLimiter.setValue(requestLimiter.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStorageCredentials(@Nullable String storageCredentials) {
|
public @Nullable TokenData getKeyBackupCurrentToken() {
|
||||||
basicStorageCredentials.setValue(storageCredentials);
|
return kbsTokenData.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable String getBasicStorageCredentials() {
|
public void setKeyBackupTokenData(TokenData tokenData) {
|
||||||
return basicStorageCredentials.getValue();
|
kbsTokenData.setValue(tokenData);
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable TokenResponse getKeyBackupCurrentToken() {
|
|
||||||
String json = keyBackupCurrentTokenJson.getValue();
|
|
||||||
if (json == null) return null;
|
|
||||||
try {
|
|
||||||
return JsonUtil.fromJson(json, TokenResponse.class);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setKeyBackupCurrentToken(TokenResponse tokenResponse) {
|
|
||||||
String json = tokenResponse == null ? null : JsonUtil.toJson(tokenResponse);
|
|
||||||
keyBackupCurrentTokenJson.setValue(json);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Long> getLockedTimeRemaining() {
|
public LiveData<Long> getLockedTimeRemaining() {
|
||||||
|
|
|
@ -116,6 +116,18 @@ public class Util {
|
||||||
return join(boxed, delimeter);
|
return join(boxed, delimeter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public static @NonNull <E> List<E> join(@NonNull List<E>... lists) {
|
||||||
|
int totalSize = Stream.of(lists).reduce(0, (sum, list) -> sum + list.size());
|
||||||
|
List<E> joined = new ArrayList<>(totalSize);
|
||||||
|
|
||||||
|
for (List<E> list : lists) {
|
||||||
|
joined.addAll(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
return joined;
|
||||||
|
}
|
||||||
|
|
||||||
public static String join(List<Long> list, String delimeter) {
|
public static String join(List<Long> list, String delimeter) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import org.whispersystems.signalservice.internal.keybackup.protos.BackupResponse
|
||||||
import org.whispersystems.signalservice.internal.keybackup.protos.RestoreResponse;
|
import org.whispersystems.signalservice.internal.keybackup.protos.RestoreResponse;
|
||||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
|
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
|
||||||
import org.whispersystems.signalservice.internal.push.RemoteAttestationUtil;
|
import org.whispersystems.signalservice.internal.push.RemoteAttestationUtil;
|
||||||
|
import org.whispersystems.signalservice.internal.util.Hex;
|
||||||
import org.whispersystems.signalservice.internal.util.Util;
|
import org.whispersystems.signalservice.internal.util.Util;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -215,6 +216,21 @@ public final class KeyBackupService {
|
||||||
return new KbsPinData(masterKey, tokenResponse);
|
return new KbsPinData(masterKey, tokenResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removePin()
|
||||||
|
throws IOException, UnauthenticatedResponseException
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
RemoteAttestation remoteAttestation = getAndVerifyRemoteAttestation();
|
||||||
|
KeyBackupRequest request = KeyBackupCipher.createKeyDeleteRequest(currentToken, remoteAttestation, serviceId);
|
||||||
|
KeyBackupResponse response = pushServiceSocket.putKbsData(authorization, request, remoteAttestation.getCookies(), enclaveName);
|
||||||
|
|
||||||
|
KeyBackupCipher.getKeyDeleteResponseStatus(response, remoteAttestation);
|
||||||
|
} catch (InvalidCiphertextException e) {
|
||||||
|
throw new UnauthenticatedResponseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enableRegistrationLock(MasterKey masterKey) throws IOException {
|
public void enableRegistrationLock(MasterKey masterKey) throws IOException {
|
||||||
pushServiceSocket.setRegistrationLockV2(masterKey.deriveRegistrationLock());
|
pushServiceSocket.setRegistrationLockV2(masterKey.deriveRegistrationLock());
|
||||||
|
@ -266,6 +282,9 @@ public final class KeyBackupService {
|
||||||
/** Creates a PIN. Does nothing to registration lock. */
|
/** Creates a PIN. Does nothing to registration lock. */
|
||||||
KbsPinData setPin(HashedPin hashedPin, MasterKey masterKey) throws IOException, UnauthenticatedResponseException;
|
KbsPinData setPin(HashedPin hashedPin, MasterKey masterKey) throws IOException, UnauthenticatedResponseException;
|
||||||
|
|
||||||
|
/** Removes the PIN data from KBS. */
|
||||||
|
void removePin() throws IOException, UnauthenticatedResponseException;
|
||||||
|
|
||||||
/** Enables registration lock. This assumes a PIN is set. */
|
/** Enables registration lock. This assumes a PIN is set. */
|
||||||
void enableRegistrationLock(MasterKey masterKey) throws IOException;
|
void enableRegistrationLock(MasterKey masterKey) throws IOException;
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,6 @@ package org.whispersystems.signalservice.api;
|
||||||
|
|
||||||
public final class KeyBackupSystemNoDataException extends Exception {
|
public final class KeyBackupSystemNoDataException extends Exception {
|
||||||
|
|
||||||
KeyBackupSystemNoDataException() {
|
public KeyBackupSystemNoDataException() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue