initCryptoIdentity method
- String? passphrase,
- String? reuseExistingStorageRecoveryKeyOrPassphrase,
- String? keyIdentifier,
- bool selfSign = true,
- bool wipeSecureStorage = true,
- bool wipeKeyBackup = true,
- bool wipeCrossSigning = true,
- bool setupMasterKey = true,
- bool setupSelfSigningKey = true,
- bool setupUserSigningKey = true,
- bool setupOnlineKeyBackup = true,
- String? keyName,
Bootstraps a crypto identity for the client. Creates secret storage and
cross-signing keys and optionally online key backup. Returns the recovery
key for the secret storage key in use — newly generated, or the existing
key when reuseExistingStorageRecoveryKeyOrPassphrase is set.
passphrase lets users remember a human-readable phrase from which a
new recovery key is derived using PBKDF2. It must not be combined with
reuseExistingStorageRecoveryKeyOrPassphrase.
When reuseExistingStorageRecoveryKeyOrPassphrase is set, the existing
secret storage key is kept and unlocked with that credential (secure
storage is never wiped). Use this to heal a partially provisioned identity
without invalidating the user's recovery key. keyIdentifier selects a
specific key when several exist. selfSign then signs this device with
the unlocked secrets when cross-signing is available.
When wipeSecureStorage or wipeKeyBackup or wipeCrossSigning are true,
existing data is wiped during setup. wipeSecureStorage is ignored when
reusing an existing storage key. The setup* flags control which
cross-signing keys and key backup are provisioned. keyName can label a
newly generated secret storage key.
Throws BootstrapBadStateException when reuse is requested but there is
no usable existing secret storage key, or bootstrap would otherwise create
a new key. Callers should fall back to a destructive
initCryptoIdentity() without the reuse parameter.
Implementation
Future<String> initCryptoIdentity({
String? passphrase,
String? reuseExistingStorageRecoveryKeyOrPassphrase,
String? keyIdentifier,
bool selfSign = true,
bool wipeSecureStorage = true,
bool wipeKeyBackup = true,
bool wipeCrossSigning = true,
bool setupMasterKey = true,
bool setupSelfSigningKey = true,
bool setupUserSigningKey = true,
bool setupOnlineKeyBackup = true,
String? keyName,
}) async {
final encryption = this.encryption;
if (encryption == null) {
throw Exception('End to end encryption not available!');
}
final reuseCredential = reuseExistingStorageRecoveryKeyOrPassphrase;
final reuseExisting = reuseCredential != null;
if (reuseExisting && passphrase != null) {
throw ArgumentError(
'Cannot set both passphrase and reuseExistingStorageRecoveryKeyOrPassphrase.',
);
}
if (reuseExisting) {
final ssss = encryption.ssss;
final keyToValidate = keyIdentifier ?? ssss.defaultKeyId;
if (keyToValidate == null || !ssss.isKeyValid(keyToValidate)) {
throw BootstrapBadStateException(
'No usable existing secret storage key. Use `Client.initCryptoIdentity()` without reuse.',
);
}
// Reuse mode never replaces the secret storage key or wipes existing components.
wipeSecureStorage = false;
wipeCrossSigning = false;
wipeKeyBackup = false;
}
String? recoveryKey;
OpenSSSS? openedSsss;
final completer = Completer();
encryption.bootstrap(
onUpdate: (bootstrap) async {
try {
recoveryKey ??= bootstrap.newSsssKey?.recoveryKey;
switch (bootstrap.state) {
case BootstrapState.loading:
break;
case BootstrapState.askWipeSsss:
bootstrap.wipeSsss(wipeSecureStorage);
break;
case BootstrapState.askUseExistingSsss:
bootstrap.useExistingSsss(
reuseExisting,
keyIdentifier: keyIdentifier,
);
break;
case BootstrapState.askUnlockSsss:
if (reuseExisting) {
throw BootstrapBadStateException(
'Cannot reuse existing storage from ${bootstrap.state}; use `Client.initCryptoIdentity()` without reuse.',
);
}
bootstrap.unlockedSsss();
break;
case BootstrapState.askBadSsss:
if (reuseExisting) {
throw BootstrapBadStateException(
'Cannot reuse existing storage from ${bootstrap.state}; use `Client.initCryptoIdentity()` without reuse.',
);
}
bootstrap.ignoreBadSecrets(true);
break;
case BootstrapState.askWipeCrossSigning:
await bootstrap.wipeCrossSigning(wipeCrossSigning);
break;
case BootstrapState.askWipeOnlineKeyBackup:
bootstrap.wipeOnlineKeyBackup(wipeKeyBackup);
break;
case BootstrapState.askSetupOnlineKeyBackup:
await bootstrap.askSetupOnlineKeyBackup(setupOnlineKeyBackup);
break;
case BootstrapState.askSetupCrossSigning:
await bootstrap.askSetupCrossSigning(
setupMasterKey: setupMasterKey,
setupSelfSigningKey: setupSelfSigningKey,
setupUserSigningKey: setupUserSigningKey,
selfSign: selfSign,
);
break;
case BootstrapState.askNewSsss:
if (reuseExisting) {
throw BootstrapBadStateException(
'Cannot reuse existing storage from ${bootstrap.state}; use `Client.initCryptoIdentity()` without reuse.',
);
}
await bootstrap.newSsss(passphrase, keyName);
break;
case BootstrapState.openExistingSsss:
if (!reuseExisting) {
throw Exception(
'Bootstrap state ${bootstrap.state} should not happen!',
);
}
await bootstrap.newSsssKey!.unlock(
keyOrPassphrase: reuseCredential,
);
recoveryKey ??= bootstrap.newSsssKey?.recoveryKey;
openedSsss = bootstrap.newSsssKey;
await bootstrap.openExistingSsss();
break;
case BootstrapState.error:
throw bootstrap.errorResult ?? Exception('Bootstrap error!');
case BootstrapState.done:
completer.complete();
break;
}
} catch (e, s) {
if (completer.isCompleted) {
return Logs().e('Bootstrap error after completed', e, s);
}
return completer.completeError(e, s);
}
},
);
await completer.future;
if (reuseExisting && selfSign && (encryption.crossSigning.enabled)) {
await encryption.crossSigning.selfSign(
keyOrPassphrase: reuseCredential,
openSsss: openedSsss,
);
}
return recoveryKey!;
}