serviceEnvironmentConfig.getKeyBackupConfig().getServiceId(),
serviceEnvironmentConfig.getKeyBackupConfig().getMrenclave(),
10);
- this.pinHelper = new PinHelper(keyBackupService);
+ final var fallbackKeyBackupServices = serviceEnvironmentConfig.getFallbackKeyBackupConfigs()
+ .stream()
+ .map(config -> accountManager.getKeyBackupService(ServiceConfig.getIasKeyStore(),
+ config.getEnclaveName(),
+ config.getServiceId(),
+ config.getMrenclave(),
+ 10))
+ .toList();
+ this.pinHelper = new PinHelper(keyBackupService, fallbackKeyBackupServices);
}
@Override
import org.whispersystems.signalservice.api.websocket.WebSocketFactory;
import org.whispersystems.signalservice.internal.websocket.WebSocketConnection;
+import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;
10));
}
+ public Collection<KeyBackupService> getFallbackKeyBackupServices() {
+ return serviceEnvironmentConfig.getFallbackKeyBackupConfigs()
+ .stream()
+ .map(config -> getAccountManager().getKeyBackupService(ServiceConfig.getIasKeyStore(),
+ config.getEnclaveName(),
+ config.getServiceId(),
+ config.getMrenclave(),
+ 10))
+ .toList();
+ }
+
public ProfileService getProfileService() {
return getOrCreate(() -> profileService,
() -> profileService = new ProfileService(getClientZkProfileOperations(),
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl;
import java.util.Base64;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
private final static byte[] KEY_BACKUP_SERVICE_ID = Hex.decode(
"3a485adb56e2058ef7737764c738c4069dd62bc457637eafb6bbce1ce29ddb89");
private final static String KEY_BACKUP_MRENCLAVE = "45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c";
+ private final static String FALLBACK_KEY_BACKUP_ENCLAVE_NAME = "0cedba03535b41b67729ce9924185f831d7767928a1d1689acb689bc079c375f";
+ private final static byte[] FALLBACK_KEY_BACKUP_SERVICE_ID = Hex.decode(
+ "187d2739d22be65e74b65f0055e74d31310e4267e5fac2b1246cc8beba81af39");
+ private final static String FALLBACK_KEY_BACKUP_MRENCLAVE = "ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba";
private final static String URL = "https://chat.signal.org";
private final static String CDN_URL = "https://cdn.signal.org";
return new KeyBackupConfig(KEY_BACKUP_ENCLAVE_NAME, KEY_BACKUP_SERVICE_ID, KEY_BACKUP_MRENCLAVE);
}
+ static Collection<KeyBackupConfig> createFallbackKeyBackupConfigs() {
+ return List.of(new KeyBackupConfig(FALLBACK_KEY_BACKUP_ENCLAVE_NAME,
+ FALLBACK_KEY_BACKUP_SERVICE_ID,
+ FALLBACK_KEY_BACKUP_MRENCLAVE));
+ }
+
static String getCdsMrenclave() {
return CDS_MRENCLAVE;
}
LiveConfig.createDefaultServiceConfiguration(interceptors),
LiveConfig.getUnidentifiedSenderTrustRoot(),
LiveConfig.createKeyBackupConfig(),
+ LiveConfig.createFallbackKeyBackupConfigs(),
LiveConfig.getCdsMrenclave());
case STAGING -> new ServiceEnvironmentConfig(serviceEnvironment,
StagingConfig.createDefaultServiceConfiguration(interceptors),
StagingConfig.getUnidentifiedSenderTrustRoot(),
StagingConfig.createKeyBackupConfig(),
+ StagingConfig.createFallbackKeyBackupConfigs(),
StagingConfig.getCdsMrenclave());
};
}
import org.signal.libsignal.protocol.ecc.ECPublicKey;
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
+import java.util.Collection;
+
public class ServiceEnvironmentConfig {
private final ServiceEnvironment type;
private final ECPublicKey unidentifiedSenderTrustRoot;
private final KeyBackupConfig keyBackupConfig;
+ private final Collection<KeyBackupConfig> fallbackKeyBackupConfigs;
private final String cdsMrenclave;
final SignalServiceConfiguration signalServiceConfiguration,
final ECPublicKey unidentifiedSenderTrustRoot,
final KeyBackupConfig keyBackupConfig,
+ final Collection<KeyBackupConfig> fallbackKeyBackupConfigs,
final String cdsMrenclave
) {
this.type = type;
this.signalServiceConfiguration = signalServiceConfiguration;
this.unidentifiedSenderTrustRoot = unidentifiedSenderTrustRoot;
this.keyBackupConfig = keyBackupConfig;
+ this.fallbackKeyBackupConfigs = fallbackKeyBackupConfigs;
this.cdsMrenclave = cdsMrenclave;
}
return keyBackupConfig;
}
+ public Collection<KeyBackupConfig> getFallbackKeyBackupConfigs() {
+ return fallbackKeyBackupConfigs;
+ }
+
public String getCdsMrenclave() {
return cdsMrenclave;
}
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl;
import java.util.Base64;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
private final static byte[] KEY_BACKUP_SERVICE_ID = Hex.decode(
"9dbc6855c198e04f21b5cc35df839fdcd51b53658454dfa3f817afefaffc95ef");
private final static String KEY_BACKUP_MRENCLAVE = "45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c";
+ private final static String FALLBACK_KEY_BACKUP_ENCLAVE_NAME = "dd6f66d397d9e8cf6ec6db238e59a7be078dd50e9715427b9c89b409ffe53f99";
+ private final static byte[] FALLBACK_KEY_BACKUP_SERVICE_ID = Hex.decode(
+ "4200003414528c151e2dccafbc87aa6d3d66a5eb8f8c05979a6e97cb33cd493a");
+ private final static String FALLBACK_KEY_BACKUP_MRENCLAVE = "ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba";
private final static String URL = "https://chat.staging.signal.org";
private final static String CDN_URL = "https://cdn-staging.signal.org";
return new KeyBackupConfig(KEY_BACKUP_ENCLAVE_NAME, KEY_BACKUP_SERVICE_ID, KEY_BACKUP_MRENCLAVE);
}
+ static Collection<KeyBackupConfig> createFallbackKeyBackupConfigs() {
+ return List.of(new KeyBackupConfig(FALLBACK_KEY_BACKUP_ENCLAVE_NAME,
+ FALLBACK_KEY_BACKUP_SERVICE_ID,
+ FALLBACK_KEY_BACKUP_MRENCLAVE));
+ }
+
static String getCdsMrenclave() {
return CDS_MRENCLAVE;
}
}
}
try {
+ updateAccountAttributes();
context.getPreKeyHelper().refreshPreKeysIfNecessary();
if (account.getAci() == null || account.getPni() == null) {
checkWhoAmiI();
if (!account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null) {
context.getSyncHelper().requestSyncPniIdentity();
}
- updateAccountAttributes();
+ if (account.getPreviousStorageVersion() < 4
+ && account.isPrimaryDevice()
+ && account.getRegistrationLockPin() != null) {
+ migrateRegistrationPin();
+ }
} catch (AuthorizationFailedException e) {
account.setRegistered(false);
throw e;
account.setMultiDevice(devices.size() > 1);
}
+ public void migrateRegistrationPin() throws IOException {
+ var masterKey = account.getOrCreatePinMasterKey();
+
+ context.getPinHelper().migrateRegistrationLockPin(account.getRegistrationLockPin(), masterKey);
+ }
+
public void setRegistrationPin(String pin) throws IOException {
var masterKey = account.getOrCreatePinMasterKey();
}
PinHelper getPinHelper() {
- return getOrCreate(() -> pinHelper, () -> pinHelper = new PinHelper(dependencies.getKeyBackupService()));
+ return getOrCreate(() -> pinHelper,
+ () -> pinHelper = new PinHelper(dependencies.getKeyBackupService(),
+ dependencies.getFallbackKeyBackupServices()));
}
public PreKeyHelper getPreKeyHelper() {
package org.asamk.signal.manager.helper;
import org.asamk.signal.manager.api.IncorrectPinException;
+import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.util.PinHashing;
import org.signal.libsignal.protocol.InvalidKeyException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.KbsPinData;
import org.whispersystems.signalservice.api.KeyBackupService;
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
import org.whispersystems.signalservice.internal.push.LockedException;
import java.io.IOException;
+import java.util.Collection;
+import java.util.stream.Stream;
public class PinHelper {
+ private final static Logger logger = LoggerFactory.getLogger(PinHelper.class);
+
private final KeyBackupService keyBackupService;
+ private final Collection<KeyBackupService> fallbackKeyBackupServices;
- public PinHelper(final KeyBackupService keyBackupService) {
+ public PinHelper(
+ final KeyBackupService keyBackupService, final Collection<KeyBackupService> fallbackKeyBackupServices
+ ) {
this.keyBackupService = keyBackupService;
+ this.fallbackKeyBackupServices = fallbackKeyBackupServices;
}
public void setRegistrationLockPin(
pinChangeSession.enableRegistrationLock(masterKey);
}
+ public void migrateRegistrationLockPin(String pin, MasterKey masterKey) throws IOException {
+ setRegistrationLockPin(pin, masterKey);
+
+ for (final var keyBackupService : fallbackKeyBackupServices) {
+ try {
+ final var pinChangeSession = keyBackupService.newPinChangeSession();
+ pinChangeSession.removePin();
+ } catch (Exception e) {
+ logger.warn("Failed to remove PIN from fallback KBS: {}", e.getMessage());
+ }
+ }
+ }
+
public void removeRegistrationLockPin() throws IOException {
final var pinChangeSession = keyBackupService.newPinChangeSession();
pinChangeSession.disableRegistrationLock();
private KbsPinData getRegistrationLockData(
String pin, String basicStorageCredentials
) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
- var tokenResponse = keyBackupService.getToken(basicStorageCredentials);
- if (tokenResponse == null || tokenResponse.getTries() == 0) {
- throw new IOException("KBS Account locked, maximum pin attempts reached.");
- }
+ var tokenResponsePair = getTokenResponse(basicStorageCredentials);
+ final var tokenResponse = tokenResponsePair.first();
+ final var keyBackupService = tokenResponsePair.second();
- var registrationLockData = restoreMasterKey(pin, basicStorageCredentials, tokenResponse);
+ var registrationLockData = restoreMasterKey(pin, basicStorageCredentials, tokenResponse, keyBackupService);
if (registrationLockData == null) {
throw new AssertionError("Failed to restore master key");
}
return registrationLockData;
}
+ private Pair<TokenResponse, KeyBackupService> getTokenResponse(String basicStorageCredentials) throws IOException {
+ final var keyBackupServices = Stream.concat(Stream.of(keyBackupService), fallbackKeyBackupServices.stream())
+ .toList();
+ for (final var keyBackupService : keyBackupServices) {
+ var tokenResponse = keyBackupService.getToken(basicStorageCredentials);
+ if (tokenResponse != null && tokenResponse.getTries() > 0) {
+ return new Pair<>(tokenResponse, keyBackupService);
+ }
+ }
+ throw new IOException("KBS Account locked, maximum pin attempts reached.");
+ }
+
private KbsPinData restoreMasterKey(
- String pin, String basicStorageCredentials, TokenResponse tokenResponse
+ String pin,
+ String basicStorageCredentials,
+ TokenResponse tokenResponse,
+ final KeyBackupService keyBackupService
) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
if (pin == null) return null;
private final static Logger logger = LoggerFactory.getLogger(SignalAccount.class);
private static final int MINIMUM_STORAGE_VERSION = 1;
- private static final int CURRENT_STORAGE_VERSION = 3;
+ private static final int CURRENT_STORAGE_VERSION = 4;
private final Object LOCK = new Object();
save();
}
+ public int getPreviousStorageVersion() {
+ return previousStorageVersion;
+ }
+
public SignalServiceDataStore getSignalServiceDataStore() {
return new SignalServiceDataStore() {
@Override
save();
}
+ public String getRegistrationLockPin() {
+ return registrationLockPin;
+ }
+
public String getRegistrationLock() {
final var masterKey = getPinBackedMasterKey();
if (masterKey == null) {