Add support for SVR2

This commit is contained in:
AsamK 2023-07-14 20:26:24 +02:00
parent 02d4cb4a14
commit 86f50e0355
9 changed files with 135 additions and 73 deletions

View file

@ -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":[] }]
},
{

View file

@ -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() {
}
}

View file

@ -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());
};
}
}

View file

@ -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;
}
}

View file

@ -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() {
}
}

View file

@ -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() {

View file

@ -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");
}
}
}

View file

@ -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

View file

@ -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()