mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30:38 +00:00
Add support for SVR2
This commit is contained in:
parent
02d4cb4a14
commit
86f50e0355
9 changed files with 135 additions and 73 deletions
|
@ -28,6 +28,7 @@ class LiveConfig {
|
|||
private final static byte[] UNIDENTIFIED_SENDER_TRUST_ROOT = Base64.getDecoder()
|
||||
.decode("BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF");
|
||||
private final static String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57";
|
||||
private final static String SVR2_MRENCLAVE = "6ee1042f9e20f880326686dd4ba50c25359f01e9f733eeba4382bca001d45094";
|
||||
|
||||
private final static String KEY_BACKUP_ENCLAVE_NAME = "e18376436159cda3ad7a45d9320e382e4a497f26b0dca34d8eab0bd0139483b5";
|
||||
private final static byte[] KEY_BACKUP_SERVICE_ID = Hex.decode(
|
||||
|
@ -44,7 +45,7 @@ class LiveConfig {
|
|||
private final static String SIGNAL_KEY_BACKUP_URL = "https://api.backup.signal.org";
|
||||
private final static String STORAGE_URL = "https://storage.signal.org";
|
||||
private final static String SIGNAL_CDSI_URL = "https://cdsi.signal.org";
|
||||
private final static String SIGNAL_SVR2_URL = "https://svr2.staging.signal.org";
|
||||
private final static String SIGNAL_SVR2_URL = "https://svr2.signal.org";
|
||||
private final static TrustStore TRUST_STORE = new WhisperTrustStore();
|
||||
|
||||
private final static Optional<Dns> dns = Optional.empty();
|
||||
|
@ -96,6 +97,10 @@ class LiveConfig {
|
|||
return CDSI_MRENCLAVE;
|
||||
}
|
||||
|
||||
static String getSvr2Mrenclave() {
|
||||
return SVR2_MRENCLAVE;
|
||||
}
|
||||
|
||||
private LiveConfig() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,13 +66,15 @@ public class ServiceConfig {
|
|||
LiveConfig.getUnidentifiedSenderTrustRoot(),
|
||||
LiveConfig.createKeyBackupConfig(),
|
||||
LiveConfig.createFallbackKeyBackupConfigs(),
|
||||
LiveConfig.getCdsiMrenclave());
|
||||
LiveConfig.getCdsiMrenclave(),
|
||||
LiveConfig.getSvr2Mrenclave());
|
||||
case STAGING -> new ServiceEnvironmentConfig(serviceEnvironment,
|
||||
StagingConfig.createDefaultServiceConfiguration(interceptors),
|
||||
StagingConfig.getUnidentifiedSenderTrustRoot(),
|
||||
StagingConfig.createKeyBackupConfig(),
|
||||
StagingConfig.createFallbackKeyBackupConfigs(),
|
||||
StagingConfig.getCdsiMrenclave());
|
||||
StagingConfig.getCdsiMrenclave(),
|
||||
StagingConfig.getSvr2Mrenclave());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ public class ServiceEnvironmentConfig {
|
|||
private final Collection<KeyBackupConfig> fallbackKeyBackupConfigs;
|
||||
|
||||
private final String cdsiMrenclave;
|
||||
private final String svr2Mrenclave;
|
||||
|
||||
public ServiceEnvironmentConfig(
|
||||
final ServiceEnvironment type,
|
||||
|
@ -24,7 +25,8 @@ public class ServiceEnvironmentConfig {
|
|||
final ECPublicKey unidentifiedSenderTrustRoot,
|
||||
final KeyBackupConfig keyBackupConfig,
|
||||
final Collection<KeyBackupConfig> fallbackKeyBackupConfigs,
|
||||
final String cdsiMrenclave
|
||||
final String cdsiMrenclave,
|
||||
final String svr2Mrenclave
|
||||
) {
|
||||
this.type = type;
|
||||
this.signalServiceConfiguration = signalServiceConfiguration;
|
||||
|
@ -32,6 +34,7 @@ public class ServiceEnvironmentConfig {
|
|||
this.keyBackupConfig = keyBackupConfig;
|
||||
this.fallbackKeyBackupConfigs = fallbackKeyBackupConfigs;
|
||||
this.cdsiMrenclave = cdsiMrenclave;
|
||||
this.svr2Mrenclave = svr2Mrenclave;
|
||||
}
|
||||
|
||||
public ServiceEnvironment getType() {
|
||||
|
@ -57,4 +60,8 @@ public class ServiceEnvironmentConfig {
|
|||
public String getCdsiMrenclave() {
|
||||
return cdsiMrenclave;
|
||||
}
|
||||
|
||||
public String getSvr2Mrenclave() {
|
||||
return svr2Mrenclave;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ class StagingConfig {
|
|||
private final static byte[] UNIDENTIFIED_SENDER_TRUST_ROOT = Base64.getDecoder()
|
||||
.decode("BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx");
|
||||
private final static String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57";
|
||||
private final static String SVR2_MRENCLAVE = "a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95";
|
||||
|
||||
private final static String KEY_BACKUP_ENCLAVE_NAME = "39963b736823d5780be96ab174869a9499d56d66497aa8f9b2244f777ebc366b";
|
||||
private final static byte[] KEY_BACKUP_SERVICE_ID = Hex.decode(
|
||||
|
@ -96,6 +97,10 @@ class StagingConfig {
|
|||
return CDSI_MRENCLAVE;
|
||||
}
|
||||
|
||||
static String getSvr2Mrenclave() {
|
||||
return SVR2_MRENCLAVE;
|
||||
}
|
||||
|
||||
private StagingConfig() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,7 +116,8 @@ public class Context {
|
|||
PinHelper getPinHelper() {
|
||||
return getOrCreate(() -> pinHelper,
|
||||
() -> pinHelper = new PinHelper(dependencies.getKeyBackupService(),
|
||||
dependencies.getFallbackKeyBackupServices()));
|
||||
dependencies.getFallbackKeyBackupServices(),
|
||||
dependencies.getSecureValueRecoveryV2()));
|
||||
}
|
||||
|
||||
public PreKeyHelper getPreKeyHelper() {
|
||||
|
|
|
@ -1,37 +1,39 @@
|
|||
package org.asamk.signal.manager.helper;
|
||||
|
||||
import org.asamk.signal.manager.api.IncorrectPinException;
|
||||
import org.asamk.signal.manager.api.Pair;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.SvrNoDataException;
|
||||
import org.whispersystems.signalservice.api.SvrPinData;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.api.kbs.PinHashUtil;
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecovery;
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV1;
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV2;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
import org.whispersystems.signalservice.internal.push.AuthCredentials;
|
||||
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 SecureValueRecoveryV1 secureValueRecoveryV1;
|
||||
private final SecureValueRecoveryV2 secureValueRecoveryV2;
|
||||
private final Collection<KeyBackupService> fallbackKeyBackupServices;
|
||||
|
||||
public PinHelper(
|
||||
final KeyBackupService keyBackupService, final Collection<KeyBackupService> fallbackKeyBackupServices
|
||||
final KeyBackupService keyBackupService,
|
||||
final Collection<KeyBackupService> fallbackKeyBackupServices,
|
||||
SecureValueRecoveryV2 secureValueRecoveryV2
|
||||
) {
|
||||
this.keyBackupService = keyBackupService;
|
||||
this.fallbackKeyBackupServices = fallbackKeyBackupServices;
|
||||
this.secureValueRecoveryV1 = new SecureValueRecoveryV1(keyBackupService);
|
||||
this.secureValueRecoveryV2 = secureValueRecoveryV2;
|
||||
}
|
||||
|
||||
public void setRegistrationLockPin(
|
||||
|
@ -46,6 +48,22 @@ public class PinHelper {
|
|||
throw new IOException(e);
|
||||
}
|
||||
pinChangeSession.enableRegistrationLock(masterKey);
|
||||
|
||||
final var backupResponse = secureValueRecoveryV2.setPin(pin, masterKey).execute();
|
||||
if (backupResponse instanceof SecureValueRecovery.BackupResponse.Success) {
|
||||
} else if (backupResponse instanceof SecureValueRecovery.BackupResponse.ServerRejected) {
|
||||
logger.warn("Backup svr2 failed: ServerRejected");
|
||||
} else if (backupResponse instanceof SecureValueRecovery.BackupResponse.EnclaveNotFound) {
|
||||
logger.warn("Backup svr2 failed: EnclaveNotFound");
|
||||
} else if (backupResponse instanceof SecureValueRecovery.BackupResponse.ExposeFailure) {
|
||||
logger.warn("Backup svr2 failed: ExposeFailure");
|
||||
} else if (backupResponse instanceof SecureValueRecovery.BackupResponse.ApplicationError error) {
|
||||
throw new IOException(error.getException());
|
||||
} else if (backupResponse instanceof SecureValueRecovery.BackupResponse.NetworkError error) {
|
||||
throw error.getException();
|
||||
} else {
|
||||
throw new AssertionError("Unexpected response");
|
||||
}
|
||||
}
|
||||
|
||||
public void migrateRegistrationLockPin(String pin, MasterKey masterKey) throws IOException {
|
||||
|
@ -69,75 +87,53 @@ public class PinHelper {
|
|||
} catch (UnauthenticatedResponseException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
final var deleteResponse = secureValueRecoveryV2.deleteData();
|
||||
if (deleteResponse instanceof SecureValueRecovery.DeleteResponse.Success) {
|
||||
} else if (deleteResponse instanceof SecureValueRecovery.DeleteResponse.ServerRejected) {
|
||||
logger.warn("Delete svr2 failed: ServerRejected");
|
||||
} else if (deleteResponse instanceof SecureValueRecovery.DeleteResponse.EnclaveNotFound) {
|
||||
logger.warn("Delete svr2 failed: EnclaveNotFound");
|
||||
} else if (deleteResponse instanceof SecureValueRecovery.DeleteResponse.ApplicationError error) {
|
||||
throw new IOException(error.getException());
|
||||
} else if (deleteResponse instanceof SecureValueRecovery.DeleteResponse.NetworkError error) {
|
||||
throw error.getException();
|
||||
} else {
|
||||
throw new AssertionError("Unexpected response");
|
||||
}
|
||||
}
|
||||
|
||||
public SvrPinData getRegistrationLockData(
|
||||
public SecureValueRecovery.RestoreResponse.Success getRegistrationLockData(
|
||||
String pin, LockedException e
|
||||
) throws IOException, IncorrectPinException {
|
||||
var basicStorageCredentials = e.getSvr1Credentials();
|
||||
if (basicStorageCredentials == null) {
|
||||
return null;
|
||||
var svr1Credentials = e.getSvr1Credentials();
|
||||
if (svr1Credentials != null) {
|
||||
return getRegistrationLockData(secureValueRecoveryV1, svr1Credentials, pin);
|
||||
}
|
||||
|
||||
try {
|
||||
return getRegistrationLockData(pin, basicStorageCredentials);
|
||||
} catch (SvrNoDataException ex) {
|
||||
throw new IOException(e);
|
||||
} catch (KeyBackupServicePinException ex) {
|
||||
throw new IncorrectPinException(ex.getTriesRemaining());
|
||||
var svr2Credentials = e.getSvr2Credentials();
|
||||
if (svr2Credentials != null) {
|
||||
return getRegistrationLockData(secureValueRecoveryV2, svr2Credentials, pin);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private SvrPinData getRegistrationLockData(
|
||||
String pin, AuthCredentials authCredentials
|
||||
) throws IOException, SvrNoDataException, KeyBackupServicePinException {
|
||||
final var basicStorageCredentials = authCredentials.asBasic();
|
||||
var tokenResponsePair = getTokenResponse(basicStorageCredentials);
|
||||
final var tokenResponse = tokenResponsePair.first();
|
||||
final var keyBackupService = tokenResponsePair.second();
|
||||
public SecureValueRecovery.RestoreResponse.Success getRegistrationLockData(
|
||||
SecureValueRecovery secureValueRecovery, AuthCredentials authCredentials, String pin
|
||||
) throws IOException, IncorrectPinException {
|
||||
final var restoreResponse = secureValueRecovery.restoreDataPreRegistration(authCredentials, pin);
|
||||
|
||||
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 SvrPinData restoreMasterKey(
|
||||
String pin,
|
||||
String basicStorageCredentials,
|
||||
TokenResponse tokenResponse,
|
||||
final KeyBackupService keyBackupService
|
||||
) throws IOException, SvrNoDataException, KeyBackupServicePinException {
|
||||
if (pin == null) return null;
|
||||
|
||||
if (basicStorageCredentials == null) {
|
||||
throw new AssertionError("Cannot restore KBS key, no storage credentials supplied");
|
||||
}
|
||||
|
||||
var session = keyBackupService.newRegistrationSession(basicStorageCredentials, tokenResponse);
|
||||
|
||||
try {
|
||||
var hashedPin = PinHashUtil.hashPin(pin, session.hashSalt());
|
||||
var kbsData = session.restorePin(hashedPin);
|
||||
if (kbsData == null) {
|
||||
throw new AssertionError("Null not expected");
|
||||
}
|
||||
return kbsData;
|
||||
} catch (UnauthenticatedResponseException | InvalidKeyException e) {
|
||||
throw new IOException(e);
|
||||
if (restoreResponse instanceof SecureValueRecovery.RestoreResponse.Success s) {
|
||||
return s;
|
||||
} else if (restoreResponse instanceof SecureValueRecovery.RestoreResponse.PinMismatch pinMismatch) {
|
||||
throw new IncorrectPinException(pinMismatch.getTriesRemaining());
|
||||
} else if (restoreResponse instanceof SecureValueRecovery.RestoreResponse.ApplicationError error) {
|
||||
throw new IOException(error.getException());
|
||||
} else if (restoreResponse instanceof SecureValueRecovery.RestoreResponse.NetworkError error) {
|
||||
throw error.getException();
|
||||
} else {
|
||||
throw new AssertionError("Unexpected response");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,7 +107,8 @@ public class RegistrationManagerImpl implements RegistrationManager {
|
|||
config.getMrenclave(),
|
||||
10))
|
||||
.toList();
|
||||
this.pinHelper = new PinHelper(keyBackupService, fallbackKeyBackupServices);
|
||||
final var secureValueRecoveryV2 = accountManager.getSecureValueRecoveryV2(serviceEnvironmentConfig.getSvr2Mrenclave());
|
||||
this.pinHelper = new PinHelper(keyBackupService, fallbackKeyBackupServices, secureValueRecoveryV2);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -17,6 +17,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
|
|||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.services.ProfileService;
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV2;
|
||||
import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
||||
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
|
||||
import org.whispersystems.signalservice.api.websocket.WebSocketFactory;
|
||||
|
@ -50,6 +51,7 @@ public class SignalDependencies {
|
|||
private SignalServiceMessageSender messageSender;
|
||||
|
||||
private KeyBackupService keyBackupService;
|
||||
private SecureValueRecoveryV2 secureValueRecoveryV2;
|
||||
private ProfileService profileService;
|
||||
private SignalServiceCipher cipher;
|
||||
|
||||
|
@ -194,6 +196,11 @@ public class SignalDependencies {
|
|||
10));
|
||||
}
|
||||
|
||||
public SecureValueRecoveryV2 getSecureValueRecoveryV2() {
|
||||
return getOrCreate(() -> secureValueRecoveryV2,
|
||||
() -> secureValueRecoveryV2 = getAccountManager().getSecureValueRecoveryV2(serviceEnvironmentConfig.getSvr2Mrenclave()));
|
||||
}
|
||||
|
||||
public Collection<KeyBackupService> getFallbackKeyBackupServices() {
|
||||
return serviceEnvironmentConfig.getFallbackKeyBackupConfigs()
|
||||
.stream()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue