205 lines
6.9 KiB
Java
205 lines
6.9 KiB
Java
package org.thoughtcrime.securesms.pin;
|
|
|
|
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.jobs.StorageAccountRestoreJob;
|
|
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
|
|
import org.thoughtcrime.securesms.logging.Log;
|
|
import org.thoughtcrime.securesms.registration.service.KeyBackupSystemWrongPinException;
|
|
import org.thoughtcrime.securesms.util.Stopwatch;
|
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
|
import org.whispersystems.libsignal.util.guava.Optional;
|
|
import org.whispersystems.signalservice.api.KbsPinData;
|
|
import org.whispersystems.signalservice.api.KeyBackupService;
|
|
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
|
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
|
|
|
import java.io.IOException;
|
|
import java.util.Objects;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
public class PinRestoreRepository {
|
|
|
|
private static final String TAG = Log.tag(PinRestoreRepository.class);
|
|
|
|
private final Executor executor = SignalExecutors.UNBOUNDED;
|
|
|
|
void getToken(@NonNull Callback<Optional<TokenData>> callback) {
|
|
executor.execute(() -> {
|
|
try {
|
|
callback.onComplete(Optional.fromNullable(getTokenSync(null)));
|
|
} catch (IOException e) {
|
|
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) {
|
|
executor.execute(() -> {
|
|
try {
|
|
Stopwatch stopwatch = new Stopwatch("PinSubmission");
|
|
|
|
KbsPinData kbsData = PinState.restoreMasterKey(pin, tokenData.getEnclave(), tokenData.getBasicAuth(), tokenData.getTokenResponse());
|
|
PinState.onSignalPinRestore(ApplicationDependencies.getApplication(), Objects.requireNonNull(kbsData), pin);
|
|
stopwatch.split("MasterKey");
|
|
|
|
ApplicationDependencies.getJobManager().runSynchronously(new StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN);
|
|
stopwatch.split("AccountRestore");
|
|
|
|
ApplicationDependencies.getJobManager().runSynchronously(new StorageSyncJob(), TimeUnit.SECONDS.toMillis(10));
|
|
stopwatch.split("ContactRestore");
|
|
|
|
stopwatch.stop(TAG);
|
|
|
|
callback.onComplete(new PinResultData(PinResult.SUCCESS, tokenData));
|
|
} catch (IOException e) {
|
|
callback.onComplete(new PinResultData(PinResult.NETWORK_ERROR, tokenData));
|
|
} catch (KeyBackupSystemNoDataException e) {
|
|
callback.onComplete(new PinResultData(PinResult.LOCKED, tokenData));
|
|
} catch (KeyBackupSystemWrongPinException e) {
|
|
callback.onComplete(new PinResultData(PinResult.INCORRECT, TokenData.withResponse(tokenData, e.getTokenResponse())));
|
|
}
|
|
});
|
|
}
|
|
|
|
interface Callback<T> {
|
|
void onComplete(@NonNull T value);
|
|
}
|
|
|
|
public static class TokenData implements Parcelable {
|
|
private final KbsEnclave enclave;
|
|
private final String basicAuth;
|
|
private final TokenResponse tokenResponse;
|
|
|
|
TokenData(@NonNull KbsEnclave enclave, @NonNull String basicAuth, @NonNull TokenResponse tokenResponse) {
|
|
this.enclave = enclave;
|
|
this.basicAuth = basicAuth;
|
|
this.tokenResponse = tokenResponse;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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 {
|
|
private final PinResult result;
|
|
private final TokenData tokenData;
|
|
|
|
PinResultData(@NonNull PinResult result, @NonNull TokenData tokenData) {
|
|
this.result = result;
|
|
this.tokenData = tokenData;
|
|
}
|
|
|
|
public @NonNull PinResult getResult() {
|
|
return result;
|
|
}
|
|
|
|
public @NonNull TokenData getTokenData() {
|
|
return tokenData;
|
|
}
|
|
}
|
|
|
|
enum PinResult {
|
|
SUCCESS, INCORRECT, LOCKED, NETWORK_ERROR
|
|
}
|
|
}
|