2020-01-17 17:14:54 +01:00
|
|
|
package org.thoughtcrime.securesms.keyvalue;
|
|
|
|
|
2020-01-22 21:02:06 +01:00
|
|
|
import androidx.annotation.NonNull;
|
2020-01-17 17:14:54 +01:00
|
|
|
import androidx.annotation.Nullable;
|
|
|
|
|
2020-04-02 23:09:25 +02:00
|
|
|
import org.thoughtcrime.securesms.logging.Log;
|
|
|
|
import org.thoughtcrime.securesms.util.Base64;
|
2020-01-17 17:14:54 +01:00
|
|
|
import org.thoughtcrime.securesms.util.JsonUtils;
|
2020-04-02 23:09:25 +02:00
|
|
|
import org.whispersystems.signalservice.api.KbsPinData;
|
2020-01-17 19:31:30 +01:00
|
|
|
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
2020-01-17 17:14:54 +01:00
|
|
|
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
2020-01-22 21:02:06 +01:00
|
|
|
import java.security.SecureRandom;
|
2020-01-17 17:14:54 +01:00
|
|
|
|
|
|
|
public final class KbsValues {
|
|
|
|
|
2020-04-02 23:09:25 +02:00
|
|
|
public static final String V2_LOCK_ENABLED = "kbs.v2_lock_enabled";
|
2020-01-22 21:02:06 +01:00
|
|
|
private static final String MASTER_KEY = "kbs.registration_lock_master_key";
|
|
|
|
private static final String TOKEN_RESPONSE = "kbs.token_response";
|
|
|
|
private static final String LOCK_LOCAL_PIN_HASH = "kbs.registration_lock_local_pin_hash";
|
2020-01-17 17:14:54 +01:00
|
|
|
|
|
|
|
private final KeyValueStore store;
|
|
|
|
|
|
|
|
KbsValues(KeyValueStore store) {
|
|
|
|
this.store = store;
|
|
|
|
}
|
|
|
|
|
2020-01-22 21:02:06 +01:00
|
|
|
/**
|
|
|
|
* Deliberately does not clear the {@link #MASTER_KEY}.
|
|
|
|
*/
|
2020-04-02 23:09:25 +02:00
|
|
|
public void clearRegistrationLockAndPin() {
|
2020-01-22 21:02:06 +01:00
|
|
|
store.beginWrite()
|
|
|
|
.remove(V2_LOCK_ENABLED)
|
|
|
|
.remove(TOKEN_RESPONSE)
|
|
|
|
.remove(LOCK_LOCAL_PIN_HASH)
|
|
|
|
.commit();
|
|
|
|
}
|
2020-01-17 17:14:54 +01:00
|
|
|
|
2020-04-02 23:09:25 +02:00
|
|
|
public synchronized void setKbsMasterKey(@NonNull KbsPinData pinData, @NonNull String localPinHash) {
|
|
|
|
MasterKey masterKey = pinData.getMasterKey();
|
2020-01-22 21:02:06 +01:00
|
|
|
String tokenResponse;
|
|
|
|
try {
|
2020-04-02 23:09:25 +02:00
|
|
|
tokenResponse = JsonUtils.toJson(pinData.getTokenResponse());
|
2020-01-22 21:02:06 +01:00
|
|
|
} catch (IOException e) {
|
|
|
|
throw new AssertionError(e);
|
2020-01-17 17:14:54 +01:00
|
|
|
}
|
|
|
|
|
2020-01-22 21:02:06 +01:00
|
|
|
store.beginWrite()
|
|
|
|
.putString(TOKEN_RESPONSE, tokenResponse)
|
|
|
|
.putBlob(MASTER_KEY, masterKey.serialize())
|
|
|
|
.putString(LOCK_LOCAL_PIN_HASH, localPinHash)
|
|
|
|
.commit();
|
2020-01-17 17:14:54 +01:00
|
|
|
}
|
|
|
|
|
2020-04-02 23:09:25 +02:00
|
|
|
public synchronized void setV2RegistrationLockEnabled(boolean enabled) {
|
|
|
|
store.beginWrite().putBoolean(V2_LOCK_ENABLED, enabled).apply();
|
|
|
|
}
|
|
|
|
|
|
|
|
public synchronized boolean isV2RegistrationLockEnabled() {
|
|
|
|
return store.getBoolean(V2_LOCK_ENABLED, false);
|
|
|
|
}
|
|
|
|
|
2020-01-22 21:02:06 +01:00
|
|
|
/**
|
|
|
|
* Finds or creates the master key. Therefore this will always return a master key whether backed
|
|
|
|
* up or not.
|
|
|
|
* <p>
|
|
|
|
* If you only want a key when it's backed up, use {@link #getPinBackedMasterKey()}.
|
|
|
|
*/
|
|
|
|
public synchronized @NonNull MasterKey getOrCreateMasterKey() {
|
|
|
|
byte[] blob = store.getBlob(MASTER_KEY, null);
|
|
|
|
|
|
|
|
if (blob == null) {
|
|
|
|
store.beginWrite()
|
|
|
|
.putBlob(MASTER_KEY, MasterKey.createNew(new SecureRandom()).serialize())
|
|
|
|
.commit();
|
|
|
|
blob = store.getBlob(MASTER_KEY, null);
|
2020-01-17 19:31:30 +01:00
|
|
|
}
|
2020-01-22 21:02:06 +01:00
|
|
|
|
|
|
|
return new MasterKey(blob);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns null if master key is not backed up by a pin.
|
|
|
|
*/
|
|
|
|
public synchronized @Nullable MasterKey getPinBackedMasterKey() {
|
|
|
|
if (!isV2RegistrationLockEnabled()) return null;
|
|
|
|
return getMasterKey();
|
|
|
|
}
|
|
|
|
|
|
|
|
private synchronized @Nullable MasterKey getMasterKey() {
|
|
|
|
byte[] blob = store.getBlob(MASTER_KEY, null);
|
|
|
|
return blob != null ? new MasterKey(blob) : null;
|
2020-01-17 17:14:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public @Nullable String getRegistrationLockToken() {
|
2020-01-22 21:02:06 +01:00
|
|
|
MasterKey masterKey = getPinBackedMasterKey();
|
|
|
|
if (masterKey == null) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
return masterKey.deriveRegistrationLock();
|
|
|
|
}
|
2020-01-17 17:14:54 +01:00
|
|
|
}
|
|
|
|
|
2020-04-02 01:55:18 +02:00
|
|
|
public synchronized @Nullable String getLocalPinHash() {
|
2020-01-22 21:02:06 +01:00
|
|
|
return store.getString(LOCK_LOCAL_PIN_HASH, null);
|
2020-01-17 17:14:54 +01:00
|
|
|
}
|
|
|
|
|
2020-04-02 23:09:25 +02:00
|
|
|
public synchronized boolean hasPin() {
|
|
|
|
return getLocalPinHash() != null;
|
2020-01-17 17:14:54 +01:00
|
|
|
}
|
|
|
|
|
2020-04-02 01:55:18 +02:00
|
|
|
public synchronized @Nullable TokenResponse getRegistrationLockTokenResponse() {
|
2020-01-22 21:02:06 +01:00
|
|
|
String token = store.getString(TOKEN_RESPONSE, null);
|
2020-01-17 17:14:54 +01:00
|
|
|
|
|
|
|
if (token == null) return null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
return JsonUtils.fromJson(token, TokenResponse.class);
|
|
|
|
} catch (IOException e) {
|
|
|
|
throw new AssertionError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|