Device dehydration v2

- add support for device dehydration v2 i.e. `org.matrix.msc3814`
- run dehydration flows after successfully recovering or creating secrets
- enable said flows based on .well-known `org.matrix.msc3814` config key
- delete previous implementation and helper methods
This commit is contained in:
Stefan Ceriu
2023-07-27 16:03:13 +03:00
parent f8716cb929
commit ef94fd8fcc
16 changed files with 72 additions and 160 deletions
@@ -305,15 +305,5 @@
*/
- (void)showAuthenticationFallBackView;
#pragma mark - Device rehydration
/**
Call this method at an appropriate time to attempt rehydrating from an existing dehydrated device
@param keyData Secret key data
@param credentials Account credentials
*/
- (void)attemptDeviceRehydrationWithKeyData:(NSData *)keyData credentials:(MXCredentials *)credentials;
@end
@@ -1495,68 +1495,6 @@
[self _createAccountWithCredentials:credentials];
}
- (void)attemptDeviceRehydrationWithKeyData:(NSData *)keyData
credentials:(MXCredentials *)credentials
{
[self attemptDeviceRehydrationWithKeyData:keyData
credentials:credentials
retry:YES];
}
- (void)attemptDeviceRehydrationWithKeyData:(NSData *)keyData
credentials:(MXCredentials *)credentials
retry:(BOOL)retry
{
MXLogDebug(@"[MXKAuthenticationViewController] attemptDeviceRehydration: starting device rehydration");
if (keyData == nil)
{
MXLogError(@"[MXKAuthenticationViewController] attemptDeviceRehydration: no key provided for device rehydration");
[self _createAccountWithCredentials:credentials];
return;
}
MXRestClient *mxRestClient = [[MXRestClient alloc] initWithCredentials:credentials andOnUnrecognizedCertificateBlock:^BOOL(NSData *certificate) {
return NO;
} andPersistentTokenDataHandler:^(void (^handler)(NSArray<MXCredentials *> *credentials, void (^completion)(BOOL didUpdateCredentials))) {
[[MXKAccountManager sharedManager] readAndWriteCredentials:handler];
} andUnauthenticatedHandler: nil];
MXWeakify(self);
[[MXKAccountManager sharedManager].dehydrationService rehydrateDeviceWithMatrixRestClient:mxRestClient dehydrationKey:keyData success:^(NSString * deviceId) {
MXStrongifyAndReturnIfNil(self);
if (deviceId)
{
MXLogDebug(@"[MXKAuthenticationViewController] attemptDeviceRehydration: device %@ rehydrated successfully.", deviceId);
credentials.deviceId = deviceId;
}
else
{
MXLogDebug(@"[MXKAuthenticationViewController] attemptDeviceRehydration: device rehydration has been canceled.");
}
[self _createAccountWithCredentials:credentials];
} failure:^(NSError *error) {
MXStrongifyAndReturnIfNil(self);
if (retry)
{
MXLogErrorDetails(@"[MXKAuthenticationViewController] attemptDeviceRehydration: device rehydration failed due to error: Retrying", @{
@"error": error ?: @"unknown"
});
[self attemptDeviceRehydrationWithKeyData:keyData credentials:credentials retry:NO];
return;
}
MXLogErrorDetails(@"[MXKAuthenticationViewController] attemptDeviceRehydration: device rehydration failed due to error", @{
@"error": error ?: @"unknown"
});
[self _createAccountWithCredentials:credentials];
}];
}
- (void)_createAccountWithCredentials:(MXCredentials *)credentials
{
MXKAccount *account = [[MXKAccount alloc] initWithCredentials:credentials];
@@ -360,13 +360,6 @@ typedef BOOL (^MXKAccountOnCertificateChange)(MXKAccount *mxAccount, NSData *cer
#pragma mark - Sync filter
/**
Call this method at an appropriate time to attempt dehydrating to a new backup device
*/
- (void)attemptDeviceDehydrationWithKeyData:(NSData *)keyData
success:(void (^)(void))success
failure:(void (^)(NSError *error))failure;
/**
Handle unauthenticated errors from the server triggering hard/soft logouts as appropriate.
*/
@@ -1715,70 +1715,6 @@ static NSArray<NSNumber*> *initialSyncSilentErrorsHTTPStatusCodes;
}];
}
- (void)attemptDeviceDehydrationWithKeyData:(NSData *)keyData
success:(void (^)(void))success
failure:(void (^)(NSError *error))failure
{
[self attemptDeviceDehydrationWithKeyData:keyData retry:YES success:success failure:failure];
}
- (void)attemptDeviceDehydrationWithKeyData:(NSData *)keyData
retry:(BOOL)retry
success:(void (^)(void))success
failure:(void (^)(NSError *error))failure
{
if (keyData == nil)
{
MXLogWarning(@"[MXKAccount] attemptDeviceDehydrationWithRetry: no key provided for device dehydration");
if (failure)
{
failure(nil);
}
return;
}
if (![mxSession.crypto.crossSigning isKindOfClass:[MXLegacyCrossSigning class]]) {
MXLogFailure(@"Device dehydratation is currently only supported by legacy cross signing, add support to all implementations");
if (failure)
{
failure(nil);
}
return;
}
MXLegacyCrossSigning *crossSigning = (MXLegacyCrossSigning *)mxSession.crypto.crossSigning;;
MXLogDebug(@"[MXKAccount] attemptDeviceDehydrationWithRetry: starting device dehydration");
[[MXKAccountManager sharedManager].dehydrationService dehydrateDeviceWithMatrixRestClient:mxRestClient crossSigning:crossSigning dehydrationKey:keyData success:^(NSString *deviceId) {
MXLogDebug(@"[MXKAccount] attemptDeviceDehydrationWithRetry: device successfully dehydrated");
if (success)
{
success();
}
} failure:^(NSError *error) {
if (retry)
{
[self attemptDeviceDehydrationWithKeyData:keyData retry:NO success:success failure:failure];
MXLogErrorDetails(@"[MXKAccount] attemptDeviceDehydrationWithRetry: device dehydration failed due to error: Retrying.", @{
@"error": error ?: @"unknown"
});
}
else
{
MXLogErrorDetails(@"[MXKAccount] attemptDeviceDehydrationWithRetry: device dehydration failed due to error", @{
@"error": error ?: @"unknown"
});
if (failure)
{
failure(error);
}
}
}];
}
- (void)onMatrixSessionStateChange
{
// Check if pause has been requested
@@ -104,8 +104,6 @@ extern NSString *const MXKAccountManagerDataType;
*/
@property (nonatomic) BOOL isPushAvailable;
@property (nonatomic, readonly) MXDehydrationService *dehydrationService;
/**
Retrieve the MXKAccounts manager.
@@ -71,7 +71,6 @@ NSString *const MXKAccountManagerDataType = @"org.matrix.kit.MXKAccountManagerDa
if (self)
{
_storeClass = [MXFileStore class];
_dehydrationService = [MXDehydrationService new];
_savingAccountsEnabled = YES;
// Migrate old account file to new format
@@ -34,9 +34,9 @@ final class SecretsRecoveryWithKeyCoordinator: SecretsRecoveryWithKeyCoordinator
// MARK: - Setup
init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal, cancellable: Bool) {
init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal, cancellable: Bool, dehydrationService: DehydrationService?) {
let secretsRecoveryWithKeyViewModel = SecretsRecoveryWithKeyViewModel(recoveryService: recoveryService, recoveryGoal: recoveryGoal)
let secretsRecoveryWithKeyViewModel = SecretsRecoveryWithKeyViewModel(recoveryService: recoveryService, recoveryGoal: recoveryGoal, dehydrationService: dehydrationService)
let secretsRecoveryWithKeyViewController = SecretsRecoveryWithKeyViewController.instantiate(with: secretsRecoveryWithKeyViewModel, cancellable: cancellable)
self.secretsRecoveryWithKeyViewController = secretsRecoveryWithKeyViewController
self.secretsRecoveryWithKeyViewModel = secretsRecoveryWithKeyViewModel
@@ -24,6 +24,8 @@ final class SecretsRecoveryWithKeyViewModel: SecretsRecoveryWithKeyViewModelType
private let recoveryService: MXRecoveryService
private let dehydrationService: DehydrationService?
// MARK: Public
let recoveryGoal: SecretsRecoveryGoal
@@ -39,8 +41,9 @@ final class SecretsRecoveryWithKeyViewModel: SecretsRecoveryWithKeyViewModelType
// MARK: - Setup
init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal) {
init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal, dehydrationService: DehydrationService?) {
self.recoveryService = recoveryService
self.dehydrationService = dehydrationService
self.recoveryGoal = recoveryGoal
}
@@ -83,6 +86,10 @@ final class SecretsRecoveryWithKeyViewModel: SecretsRecoveryWithKeyViewModelType
}
self.update(viewState: .loaded)
self.coordinatorDelegate?.secretsRecoveryWithKeyViewModelDidRecover(self)
Task {
await self.dehydrationService?.runDeviceDehydrationFlow(privateKeyData: privateKey)
}
}, failure: { [weak self] error in
guard let self = self else {
return
@@ -34,8 +34,8 @@ final class SecretsRecoveryWithPassphraseCoordinator: SecretsRecoveryWithPassphr
// MARK: - Setup
init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal, cancellable: Bool) {
let secretsRecoveryWithPassphraseViewModel = SecretsRecoveryWithPassphraseViewModel(recoveryService: recoveryService, recoveryGoal: recoveryGoal)
init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal, cancellable: Bool, dehydrationService: DehydrationService?) {
let secretsRecoveryWithPassphraseViewModel = SecretsRecoveryWithPassphraseViewModel(recoveryService: recoveryService, recoveryGoal: recoveryGoal, dehydrationService: dehydrationService)
let secretsRecoveryWithPassphraseViewController = SecretsRecoveryWithPassphraseViewController.instantiate(with: secretsRecoveryWithPassphraseViewModel, cancellable: cancellable)
self.secretsRecoveryWithPassphraseViewController = secretsRecoveryWithPassphraseViewController
self.secretsRecoveryWithPassphraseViewModel = secretsRecoveryWithPassphraseViewModel
@@ -24,6 +24,8 @@ final class SecretsRecoveryWithPassphraseViewModel: SecretsRecoveryWithPassphras
private let recoveryService: MXRecoveryService
private let dehydrationService: DehydrationService?
// MARK: Public
let recoveryGoal: SecretsRecoveryGoal
@@ -39,8 +41,9 @@ final class SecretsRecoveryWithPassphraseViewModel: SecretsRecoveryWithPassphras
// MARK: - Setup
init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal) {
init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal, dehydrationService: DehydrationService?) {
self.recoveryService = recoveryService
self.dehydrationService = dehydrationService
self.recoveryGoal = recoveryGoal
}
@@ -103,6 +106,10 @@ final class SecretsRecoveryWithPassphraseViewModel: SecretsRecoveryWithPassphras
}
self.update(viewState: .loaded)
self.coordinatorDelegate?.secretsRecoveryWithPassphraseViewModelDidRecover(self)
Task {
await self.dehydrationService?.runDeviceDehydrationFlow(privateKeyData: privateKey)
}
}, failure: { [weak self] error in
guard let self = self else {
return
@@ -85,14 +85,28 @@ final class SecretsRecoveryCoordinator: SecretsRecoveryCoordinatorType {
// MARK: - Private
private var dehydrationService: DehydrationService? {
if self.session.vc_homeserverConfiguration().encryption.deviceDehydrationEnabled {
return self.session.crypto.dehydrationService
}
return nil
}
private func createRecoverFromKeyCoordinator() -> SecretsRecoveryWithKeyCoordinator {
let coordinator = SecretsRecoveryWithKeyCoordinator(recoveryService: self.session.crypto.recoveryService, recoveryGoal: self.recoveryGoal, cancellable: self.cancellable)
let coordinator = SecretsRecoveryWithKeyCoordinator(recoveryService: self.session.crypto.recoveryService,
recoveryGoal: self.recoveryGoal,
cancellable: self.cancellable,
dehydrationService: dehydrationService)
coordinator.delegate = self
return coordinator
}
private func createRecoverFromPassphraseCoordinator() -> SecretsRecoveryWithPassphraseCoordinator {
let coordinator = SecretsRecoveryWithPassphraseCoordinator(recoveryService: self.session.crypto.recoveryService, recoveryGoal: self.recoveryGoal, cancellable: self.cancellable)
let coordinator = SecretsRecoveryWithPassphraseCoordinator(recoveryService: self.session.crypto.recoveryService,
recoveryGoal: self.recoveryGoal,
cancellable: self.cancellable,
dehydrationService: dehydrationService)
coordinator.delegate = self
return coordinator
}
@@ -42,8 +42,13 @@ final class SecretsSetupRecoveryKeyCoordinator: SecretsSetupRecoveryKeyCoordinat
passphrase: String?,
passphraseOnly: Bool,
allowOverwrite: Bool = false,
cancellable: Bool) {
let secretsSetupRecoveryKeyViewModel = SecretsSetupRecoveryKeyViewModel(recoveryService: recoveryService, passphrase: passphrase, passphraseOnly: passphraseOnly, allowOverwrite: allowOverwrite)
cancellable: Bool,
dehydrationService: DehydrationService?) {
let secretsSetupRecoveryKeyViewModel = SecretsSetupRecoveryKeyViewModel(recoveryService: recoveryService,
passphrase: passphrase,
passphraseOnly: passphraseOnly,
allowOverwrite: allowOverwrite,
dehydrationService: dehydrationService)
let secretsSetupRecoveryKeyViewController = SecretsSetupRecoveryKeyViewController.instantiate(with: secretsSetupRecoveryKeyViewModel, cancellable: cancellable)
self.secretsSetupRecoveryKeyViewModel = secretsSetupRecoveryKeyViewModel
self.secretsSetupRecoveryKeyViewController = secretsSetupRecoveryKeyViewController
@@ -28,6 +28,7 @@ final class SecretsSetupRecoveryKeyViewModel: SecretsSetupRecoveryKeyViewModelTy
private let passphrase: String?
private let passphraseOnly: Bool
private let allowOverwrite: Bool
private let dehydrationService: DehydrationService?
// MARK: Public
@@ -36,11 +37,12 @@ final class SecretsSetupRecoveryKeyViewModel: SecretsSetupRecoveryKeyViewModelTy
// MARK: - Setup
init(recoveryService: MXRecoveryService, passphrase: String?, passphraseOnly: Bool, allowOverwrite: Bool = false) {
init(recoveryService: MXRecoveryService, passphrase: String?, passphraseOnly: Bool, allowOverwrite: Bool = false, dehydrationService: DehydrationService?) {
self.recoveryService = recoveryService
self.passphrase = passphrase
self.passphraseOnly = passphraseOnly
self.allowOverwrite = allowOverwrite
self.dehydrationService = dehydrationService
}
// MARK: - Public
@@ -76,6 +78,10 @@ final class SecretsSetupRecoveryKeyViewModel: SecretsSetupRecoveryKeyViewModelTy
self.recoveryService.createRecovery(forSecrets: nil, withPassphrase: self.passphrase, createServicesBackups: true, success: { secretStorageKeyCreationInfo in
self.update(viewState: .recoveryCreated(secretStorageKeyCreationInfo.recoveryKey))
Task {
await self.dehydrationService?.runDeviceDehydrationFlow(privateKeyData: secretStorageKeyCreationInfo.privateKey)
}
}, failure: { error in
self.update(viewState: .error(error))
})
@@ -104,8 +104,21 @@ final class SecureBackupSetupCoordinator: SecureBackupSetupCoordinatorType {
return introViewController
}
private var dehydrationService: DehydrationService? {
if self.session.vc_homeserverConfiguration().encryption.deviceDehydrationEnabled {
return self.session.crypto.dehydrationService
}
return nil
}
private func showSetupKey(passphraseOnly: Bool, passphrase: String? = nil) {
let coordinator = SecretsSetupRecoveryKeyCoordinator(recoveryService: self.recoveryService, passphrase: passphrase, passphraseOnly: passphraseOnly, allowOverwrite: allowOverwrite, cancellable: self.cancellable)
let coordinator = SecretsSetupRecoveryKeyCoordinator(recoveryService: self.recoveryService,
passphrase: passphrase,
passphraseOnly: passphraseOnly,
allowOverwrite: allowOverwrite,
cancellable: self.cancellable,
dehydrationService: dehydrationService)
coordinator.delegate = self
coordinator.start()