From 86f50e03550683fb5213f690a6591341df38a312 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 14 Jul 2023 20:26:24 +0200 Subject: [PATCH] Add support for SVR2 --- graalvm-config-dir/reflect-config.json | 38 +++++ .../signal/manager/config/LiveConfig.java | 7 +- .../signal/manager/config/ServiceConfig.java | 6 +- .../config/ServiceEnvironmentConfig.java | 9 +- .../signal/manager/config/StagingConfig.java | 5 + .../asamk/signal/manager/helper/Context.java | 3 +- .../signal/manager/helper/PinHelper.java | 130 +++++++++--------- .../internal/RegistrationManagerImpl.java | 3 +- .../manager/internal/SignalDependencies.java | 7 + 9 files changed, 135 insertions(+), 73 deletions(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 34d6aea1..2c1fe0f0 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -459,15 +459,45 @@ { "name":"kotlin.Boolean" }, +{ + "name":"kotlin.BooleanArray" +}, +{ + "name":"kotlin.Byte" +}, { "name":"kotlin.ByteArray" }, +{ + "name":"kotlin.Char" +}, +{ + "name":"kotlin.CharArray" +}, +{ + "name":"kotlin.Double" +}, +{ + "name":"kotlin.DoubleArray" +}, +{ + "name":"kotlin.Float" +}, +{ + "name":"kotlin.FloatArray" +}, { "name":"kotlin.Int" }, +{ + "name":"kotlin.IntArray" +}, { "name":"kotlin.Long" }, +{ + "name":"kotlin.LongArray" +}, { "name":"kotlin.Metadata", "queryAllDeclaredMethods":true, @@ -477,6 +507,12 @@ "name":"kotlin.SafePublicationLazyImpl", "fields":[{"name":"_value"}] }, +{ + "name":"kotlin.Short" +}, +{ + "name":"kotlin.ShortArray" +}, { "name":"kotlin.String" }, @@ -2108,6 +2144,7 @@ "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true, + "allDeclaredClasses":true, "methods":[{"name":"getData","parameterTypes":[] }, {"name":"getIv","parameterTypes":[] }, {"name":"getMac","parameterTypes":[] }, {"name":"getRequestId","parameterTypes":[] }, {"name":"getType","parameterTypes":[] }] }, { @@ -2134,6 +2171,7 @@ "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true, + "allDeclaredClasses":true, "methods":[{"name":"getClientPublic","parameterTypes":[] }] }, { diff --git a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java index 80322b8b..285b7c93 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java @@ -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 = Optional.empty(); @@ -96,6 +97,10 @@ class LiveConfig { return CDSI_MRENCLAVE; } + static String getSvr2Mrenclave() { + return SVR2_MRENCLAVE; + } + private LiveConfig() { } } diff --git a/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java index f97e8f68..5b6bce47 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java @@ -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()); }; } } diff --git a/lib/src/main/java/org/asamk/signal/manager/config/ServiceEnvironmentConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/ServiceEnvironmentConfig.java index 2a95f34f..64f17589 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/ServiceEnvironmentConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/ServiceEnvironmentConfig.java @@ -17,6 +17,7 @@ public class ServiceEnvironmentConfig { private final Collection 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 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; + } } diff --git a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java index 95a3dd96..f573e505 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java @@ -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() { } } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/Context.java b/lib/src/main/java/org/asamk/signal/manager/helper/Context.java index 2caeb0fb..a11ea470 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/Context.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/Context.java @@ -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() { diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java index c734323f..2c56092e 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java @@ -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 fallbackKeyBackupServices; public PinHelper( - final KeyBackupService keyBackupService, final Collection fallbackKeyBackupServices + final KeyBackupService keyBackupService, + final Collection 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; - } - - try { - return getRegistrationLockData(pin, basicStorageCredentials); - } catch (SvrNoDataException ex) { - throw new IOException(e); - } catch (KeyBackupServicePinException ex) { - throw new IncorrectPinException(ex.getTriesRemaining()); + var svr1Credentials = e.getSvr1Credentials(); + if (svr1Credentials != null) { + return getRegistrationLockData(secureValueRecoveryV1, svr1Credentials, pin); } - } - 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(); - - var registrationLockData = restoreMasterKey(pin, basicStorageCredentials, tokenResponse, keyBackupService); - if (registrationLockData == null) { - throw new AssertionError("Failed to restore master key"); + var svr2Credentials = e.getSvr2Credentials(); + if (svr2Credentials != null) { + return getRegistrationLockData(secureValueRecoveryV2, svr2Credentials, pin); } - return registrationLockData; - } - private Pair 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."); + return null; } - 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); + public SecureValueRecovery.RestoreResponse.Success getRegistrationLockData( + SecureValueRecovery secureValueRecovery, AuthCredentials authCredentials, String pin + ) throws IOException, IncorrectPinException { + final var restoreResponse = secureValueRecovery.restoreDataPreRegistration(authCredentials, pin); + + 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"); } } } diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java index ff8076e0..bf53a9b8 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java @@ -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 diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index bf2f76df..7b3a5a54 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -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 getFallbackKeyBackupServices() { return serviceEnvironmentConfig.getFallbackKeyBackupConfigs() .stream() -- 2.50.1