refactor: Add secretstoragekeycontent
This commit is contained in:
parent
49f0679fbf
commit
b563aec7bb
|
|
@ -24,15 +24,12 @@ import 'package:olm/olm.dart' as olm;
|
|||
import '../famedlysdk.dart';
|
||||
import 'encryption.dart';
|
||||
|
||||
const SELF_SIGNING_KEY = EventTypes.CrossSigningSelfSigning;
|
||||
const USER_SIGNING_KEY = EventTypes.CrossSigningUserSigning;
|
||||
const MASTER_KEY = 'm.cross_signing.master';
|
||||
|
||||
class CrossSigning {
|
||||
final Encryption encryption;
|
||||
Client get client => encryption.client;
|
||||
CrossSigning(this.encryption) {
|
||||
encryption.ssss.setValidator(SELF_SIGNING_KEY, (String secret) async {
|
||||
encryption.ssss.setValidator(EventTypes.CrossSigningSelfSigning,
|
||||
(String secret) async {
|
||||
final keyObj = olm.PkSigning();
|
||||
try {
|
||||
return keyObj.init_with_seed(base64.decode(secret)) ==
|
||||
|
|
@ -43,7 +40,8 @@ class CrossSigning {
|
|||
keyObj.free();
|
||||
}
|
||||
});
|
||||
encryption.ssss.setValidator(USER_SIGNING_KEY, (String secret) async {
|
||||
encryption.ssss.setValidator(EventTypes.CrossSigningUserSigning,
|
||||
(String secret) async {
|
||||
final keyObj = olm.PkSigning();
|
||||
try {
|
||||
return keyObj.init_with_seed(base64.decode(secret)) ==
|
||||
|
|
@ -57,23 +55,27 @@ class CrossSigning {
|
|||
}
|
||||
|
||||
bool get enabled =>
|
||||
client.accountData[SELF_SIGNING_KEY] != null &&
|
||||
client.accountData[USER_SIGNING_KEY] != null &&
|
||||
client.accountData[MASTER_KEY] != null;
|
||||
client.accountData[EventTypes.CrossSigningSelfSigning] != null &&
|
||||
client.accountData[EventTypes.CrossSigningUserSigning] != null &&
|
||||
client.accountData[EventTypes.CrossSigningMasterKey] != null;
|
||||
|
||||
Future<bool> isCached() async {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
return (await encryption.ssss.getCached(SELF_SIGNING_KEY)) != null &&
|
||||
(await encryption.ssss.getCached(USER_SIGNING_KEY)) != null;
|
||||
return (await encryption.ssss
|
||||
.getCached(EventTypes.CrossSigningSelfSigning)) !=
|
||||
null &&
|
||||
(await encryption.ssss.getCached(EventTypes.CrossSigningUserSigning)) !=
|
||||
null;
|
||||
}
|
||||
|
||||
Future<void> selfSign({String passphrase, String recoveryKey}) async {
|
||||
final handle = encryption.ssss.open(MASTER_KEY);
|
||||
final handle = encryption.ssss.open(EventTypes.CrossSigningMasterKey);
|
||||
await handle.unlock(passphrase: passphrase, recoveryKey: recoveryKey);
|
||||
await handle.maybeCacheAll();
|
||||
final masterPrivateKey = base64.decode(await handle.getStored(MASTER_KEY));
|
||||
final masterPrivateKey =
|
||||
base64.decode(await handle.getStored(EventTypes.CrossSigningMasterKey));
|
||||
final keyObj = olm.PkSigning();
|
||||
String masterPubkey;
|
||||
try {
|
||||
|
|
@ -85,11 +87,11 @@ class CrossSigning {
|
|||
!client.userDeviceKeys.containsKey(client.userID) ||
|
||||
!client.userDeviceKeys[client.userID].deviceKeys
|
||||
.containsKey(client.deviceID)) {
|
||||
throw 'Master or user keys not found';
|
||||
throw Exception('Master or user keys not found');
|
||||
}
|
||||
final masterKey = client.userDeviceKeys[client.userID].masterKey;
|
||||
if (masterKey == null || masterKey.ed25519Key != masterPubkey) {
|
||||
throw 'Master pubkey key doesn\'t match';
|
||||
throw Exception('Master pubkey key doesn\'t match');
|
||||
}
|
||||
// master key is valid, set it to verified
|
||||
await masterKey.setVerified(true, false);
|
||||
|
|
@ -146,8 +148,9 @@ class CrossSigning {
|
|||
// we don't care about signing other cross-signing keys
|
||||
} else {
|
||||
// okay, we'll sign a device key with our self signing key
|
||||
selfSigningKey ??= base64
|
||||
.decode(await encryption.ssss.getCached(SELF_SIGNING_KEY) ?? '');
|
||||
selfSigningKey ??= base64.decode(await encryption.ssss
|
||||
.getCached(EventTypes.CrossSigningSelfSigning) ??
|
||||
'');
|
||||
if (selfSigningKey.isNotEmpty) {
|
||||
final signature = _sign(key.signingContent, selfSigningKey);
|
||||
addSignature(key,
|
||||
|
|
@ -156,8 +159,9 @@ class CrossSigning {
|
|||
}
|
||||
} else if (key is CrossSigningKey && key.usage.contains('master')) {
|
||||
// we are signing someone elses master key
|
||||
userSigningKey ??= base64
|
||||
.decode(await encryption.ssss.getCached(USER_SIGNING_KEY) ?? '');
|
||||
userSigningKey ??= base64.decode(await encryption.ssss
|
||||
.getCached(EventTypes.CrossSigningUserSigning) ??
|
||||
'');
|
||||
if (userSigningKey.isNotEmpty) {
|
||||
final signature = _sign(key.signingContent, userSigningKey);
|
||||
addSignature(key, client.userDeviceKeys[client.userID].userSigningKey,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,13 @@ class Encryption {
|
|||
String get fingerprintKey => olmManager.fingerprintKey;
|
||||
String get identityKey => olmManager.identityKey;
|
||||
|
||||
/// ToDeviceEventDecryptionError erros are coming here.
|
||||
final StreamController<ToDeviceEventDecryptionError>
|
||||
onToDeviceEventDecryptionError = StreamController.broadcast();
|
||||
|
||||
/// All other erros are coming here.
|
||||
final StreamController<SdkError> onError = StreamController.broadcast();
|
||||
|
||||
KeyManager keyManager;
|
||||
OlmManager olmManager;
|
||||
KeyVerificationManager keyVerificationManager;
|
||||
|
|
@ -132,7 +139,21 @@ class Encryption {
|
|||
}
|
||||
|
||||
Future<ToDeviceEvent> decryptToDeviceEvent(ToDeviceEvent event) async {
|
||||
return await olmManager.decryptToDeviceEvent(event);
|
||||
try {
|
||||
return await olmManager.decryptToDeviceEvent(event);
|
||||
} catch (e, s) {
|
||||
Logs.error(
|
||||
'[LibOlm] Could not decrypt to device event from ${event.sender} with content: ${event.content}\n${e.toString()}',
|
||||
s);
|
||||
onToDeviceEventDecryptionError.add(
|
||||
ToDeviceEventDecryptionError(
|
||||
exception: e is Exception ? e : Exception(e),
|
||||
stackTrace: s,
|
||||
toDeviceEvent: event,
|
||||
),
|
||||
);
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
Event decryptRoomEventSync(String roomId, Event event) {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ Map<String, dynamic> _deepcopy(Map<String, dynamic> data) {
|
|||
return json.decode(json.encode(data));
|
||||
}
|
||||
|
||||
/// SSSS: **S**ecure **S**ecret **S**torage and **S**haring
|
||||
/// Read more about SSSS at:
|
||||
/// https://matrix.org/docs/guides/implementing-more-advanced-e-2-ee-features-such-as-cross-signing#3-implementing-ssss
|
||||
class SSSS {
|
||||
final Encryption encryption;
|
||||
Client get client => encryption.client;
|
||||
|
|
@ -113,7 +116,7 @@ class SSSS {
|
|||
.encode(Hmac(sha256, keys.hmacKey).convert(cipher).bytes)
|
||||
.replaceAll(RegExp(r'=+$'), '');
|
||||
if (hmac != data.mac.replaceAll(RegExp(r'=+$'), '')) {
|
||||
throw 'Bad MAC';
|
||||
throw Exception('Bad MAC');
|
||||
}
|
||||
final decipher = AES(Key(keys.aesKey), mode: AESMode.ctr, padding: null)
|
||||
.decrypt(Encrypted(cipher), iv: IV(base64.decode(data.iv)));
|
||||
|
|
@ -128,26 +131,26 @@ class SSSS {
|
|||
parity ^= b;
|
||||
}
|
||||
if (parity != 0) {
|
||||
throw 'Incorrect parity';
|
||||
throw Exception('Incorrect parity');
|
||||
}
|
||||
|
||||
for (var i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; i++) {
|
||||
if (result[i] != OLM_RECOVERY_KEY_PREFIX[i]) {
|
||||
throw 'Incorrect prefix';
|
||||
throw Exception('Incorrect prefix');
|
||||
}
|
||||
}
|
||||
|
||||
if (result.length != OLM_RECOVERY_KEY_PREFIX.length + SSSS_KEY_LENGTH + 1) {
|
||||
throw 'Incorrect length';
|
||||
throw Exception('Incorrect length');
|
||||
}
|
||||
|
||||
return Uint8List.fromList(result.sublist(OLM_RECOVERY_KEY_PREFIX.length,
|
||||
OLM_RECOVERY_KEY_PREFIX.length + SSSS_KEY_LENGTH));
|
||||
}
|
||||
|
||||
static Uint8List keyFromPassphrase(String passphrase, _PassphraseInfo info) {
|
||||
static Uint8List keyFromPassphrase(String passphrase, PassphraseInfo info) {
|
||||
if (info.algorithm != AlgorithmTypes.pbkdf2) {
|
||||
throw 'Unknown algorithm';
|
||||
throw Exception('Unknown algorithm');
|
||||
}
|
||||
final generator = PBKDF2(hashAlgorithm: sha512);
|
||||
return Uint8List.fromList(generator.generateKey(passphrase, info.salt,
|
||||
|
|
@ -162,55 +165,45 @@ class SSSS {
|
|||
_cacheCallbacks[type] = callback;
|
||||
}
|
||||
|
||||
String get defaultKeyId {
|
||||
final keyData = client.accountData[EventTypes.SecretStorageDefaultKey];
|
||||
if (keyData == null || !(keyData.content['key'] is String)) {
|
||||
return null;
|
||||
}
|
||||
return keyData.content['key'];
|
||||
}
|
||||
String get defaultKeyId => client
|
||||
.accountData[EventTypes.SecretStorageDefaultKey]
|
||||
?.parsedSecretStorageDefaultKeyContent
|
||||
?.key;
|
||||
|
||||
Future<void> setDefaultKeyId(String keyId) async {
|
||||
await client.setAccountData(
|
||||
client.userID, EventTypes.SecretStorageDefaultKey, <String, dynamic>{
|
||||
'key': keyId,
|
||||
});
|
||||
client.userID,
|
||||
EventTypes.SecretStorageDefaultKey,
|
||||
(SecretStorageDefaultKeyContent()..key = keyId).toJson(),
|
||||
);
|
||||
}
|
||||
|
||||
BasicEvent getKey(String keyId) {
|
||||
return client.accountData[EventTypes.secretStorageKey(keyId)];
|
||||
SecretStorageKeyContent getKey(String keyId) {
|
||||
return client.accountData[EventTypes.secretStorageKey(keyId)]
|
||||
?.parsedSecretStorageKeyContent;
|
||||
}
|
||||
|
||||
bool isKeyValid(String keyId) {
|
||||
final keyData = getKey(keyId);
|
||||
if (keyData == null) {
|
||||
return false;
|
||||
}
|
||||
return keyData.content['algorithm'] ==
|
||||
AlgorithmTypes.secretStorageV1AesHmcSha2;
|
||||
}
|
||||
bool isKeyValid(String keyId) =>
|
||||
getKey(keyId)?.algorithm == AlgorithmTypes.secretStorageV1AesHmcSha2;
|
||||
|
||||
/// Creates a new secret storage key, optional encrypts it with [passphrase]
|
||||
/// and stores it in the user's [accountData].
|
||||
Future<OpenSSSS> createKey([String passphrase]) async {
|
||||
Uint8List privateKey;
|
||||
final content = <String, dynamic>{};
|
||||
final content = SecretStorageKeyContent();
|
||||
if (passphrase != null) {
|
||||
// we need to derive the key off of the passphrase
|
||||
content['passphrase'] = <String, dynamic>{};
|
||||
content['passphrase']['algorithm'] = AlgorithmTypes.pbkdf2;
|
||||
content['passphrase']['salt'] = base64
|
||||
content.passphrase = PassphraseInfo();
|
||||
content.passphrase.algorithm = AlgorithmTypes.pbkdf2;
|
||||
content.passphrase.salt = base64
|
||||
.encode(SecureRandom(PBKDF2_SALT_LENGTH).bytes); // generate salt
|
||||
content['passphrase']['iterations'] = PBKDF2_DEFAULT_ITERATIONS;
|
||||
content['passphrase']['bits'] = SSSS_KEY_LENGTH * 8;
|
||||
content.passphrase.iterations = PBKDF2_DEFAULT_ITERATIONS;
|
||||
content.passphrase.bits = SSSS_KEY_LENGTH * 8;
|
||||
privateKey = await runInBackground(
|
||||
_keyFromPassphrase,
|
||||
_KeyFromPassphraseArgs(
|
||||
passphrase: passphrase,
|
||||
info: _PassphraseInfo(
|
||||
algorithm: content['passphrase']['algorithm'],
|
||||
salt: content['passphrase']['salt'],
|
||||
iterations: content['passphrase']['iterations'],
|
||||
bits: content['passphrase']['bits'],
|
||||
),
|
||||
info: content.passphrase,
|
||||
));
|
||||
} else {
|
||||
// we need to just generate a new key from scratch
|
||||
|
|
@ -218,9 +211,9 @@ class SSSS {
|
|||
}
|
||||
// now that we have the private key, let's create the iv and mac
|
||||
final encrypted = encryptAes(ZERO_STR, privateKey, '');
|
||||
content['iv'] = encrypted.iv;
|
||||
content['mac'] = encrypted.mac;
|
||||
content['algorithm'] = AlgorithmTypes.secretStorageV1AesHmcSha2;
|
||||
content.iv = encrypted.iv;
|
||||
content.mac = encrypted.mac;
|
||||
content.algorithm = AlgorithmTypes.secretStorageV1AesHmcSha2;
|
||||
|
||||
const KEYID_BYTE_LENGTH = 24;
|
||||
|
||||
|
|
@ -235,7 +228,8 @@ class SSSS {
|
|||
final waitForAccountData = client.onSync.stream.firstWhere((syncUpdate) =>
|
||||
syncUpdate.accountData
|
||||
.any((accountData) => accountData.type == accountDataType));
|
||||
await client.setAccountData(client.userID, accountDataType, content);
|
||||
await client.setAccountData(
|
||||
client.userID, accountDataType, content.toJson());
|
||||
await waitForAccountData;
|
||||
|
||||
final key = open(keyId);
|
||||
|
|
@ -243,19 +237,18 @@ class SSSS {
|
|||
return key;
|
||||
}
|
||||
|
||||
bool checkKey(Uint8List key, BasicEvent keyData) {
|
||||
final info = keyData.content;
|
||||
if (info['algorithm'] == AlgorithmTypes.secretStorageV1AesHmcSha2) {
|
||||
if ((info['mac'] is String) && (info['iv'] is String)) {
|
||||
final encrypted = encryptAes(ZERO_STR, key, '', info['iv']);
|
||||
return info['mac'].replaceAll(RegExp(r'=+$'), '') ==
|
||||
bool checkKey(Uint8List key, SecretStorageKeyContent info) {
|
||||
if (info.algorithm == AlgorithmTypes.secretStorageV1AesHmcSha2) {
|
||||
if ((info.mac is String) && (info.iv is String)) {
|
||||
final encrypted = encryptAes(ZERO_STR, key, '', info.iv);
|
||||
return info.mac.replaceAll(RegExp(r'=+$'), '') ==
|
||||
encrypted.mac.replaceAll(RegExp(r'=+$'), '');
|
||||
} else {
|
||||
// no real information about the key, assume it is valid
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
throw 'Unknown Algorithm';
|
||||
throw Exception('Unknown Algorithm');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -287,13 +280,13 @@ class SSSS {
|
|||
Future<String> getStored(String type, String keyId, Uint8List key) async {
|
||||
final secretInfo = client.accountData[type];
|
||||
if (secretInfo == null) {
|
||||
throw 'Not found';
|
||||
throw Exception('Not found');
|
||||
}
|
||||
if (!(secretInfo.content['encrypted'] is Map)) {
|
||||
throw 'Content is not encrypted';
|
||||
throw Exception('Content is not encrypted');
|
||||
}
|
||||
if (!(secretInfo.content['encrypted'][keyId] is Map)) {
|
||||
throw 'Wrong / unknown key';
|
||||
throw Exception('Wrong / unknown key');
|
||||
}
|
||||
final enc = secretInfo.content['encrypted'][keyId];
|
||||
final encryptInfo = _Encrypted(
|
||||
|
|
@ -345,7 +338,7 @@ class SSSS {
|
|||
Future<void> validateAndStripOtherKeys(
|
||||
String type, String secret, String keyId, Uint8List key) async {
|
||||
if (await getStored(type, keyId, key) != secret) {
|
||||
throw 'Secrets do not match up!';
|
||||
throw Exception('Secrets do not match up!');
|
||||
}
|
||||
// now remove all other keys
|
||||
final content = _deepcopy(client.accountData[type].content);
|
||||
|
|
@ -354,7 +347,7 @@ class SSSS {
|
|||
content['encrypted'].removeWhere((k, v) => otherKeys.contains(k));
|
||||
// yes, we are paranoid...
|
||||
if (await getStored(type, keyId, key) != secret) {
|
||||
throw 'Secrets do not match up!';
|
||||
throw Exception('Secrets do not match up!');
|
||||
}
|
||||
// store the thing in your account data
|
||||
await client.setAccountData(client.userID, type, content);
|
||||
|
|
@ -561,15 +554,15 @@ class SSSS {
|
|||
OpenSSSS open([String identifier]) {
|
||||
identifier ??= defaultKeyId;
|
||||
if (identifier == null) {
|
||||
throw 'Dont know what to open';
|
||||
throw Exception('Dont know what to open');
|
||||
}
|
||||
final keyToOpen = keyIdFromType(identifier) ?? identifier;
|
||||
if (keyToOpen == null) {
|
||||
throw 'No key found to open';
|
||||
throw Exception('No key found to open');
|
||||
}
|
||||
final key = getKey(keyToOpen);
|
||||
if (key == null) {
|
||||
throw 'Unknown key to open';
|
||||
throw Exception('Unknown key to open');
|
||||
}
|
||||
return OpenSSSS(ssss: this, keyId: keyToOpen, keyData: key);
|
||||
}
|
||||
|
|
@ -600,19 +593,10 @@ class _DerivedKeys {
|
|||
_DerivedKeys({this.aesKey, this.hmacKey});
|
||||
}
|
||||
|
||||
class _PassphraseInfo {
|
||||
final String algorithm;
|
||||
final String salt;
|
||||
final int iterations;
|
||||
final int bits;
|
||||
|
||||
_PassphraseInfo({this.algorithm, this.salt, this.iterations, this.bits});
|
||||
}
|
||||
|
||||
class OpenSSSS {
|
||||
final SSSS ssss;
|
||||
final String keyId;
|
||||
final BasicEvent keyData;
|
||||
final SecretStorageKeyContent keyData;
|
||||
OpenSSSS({this.ssss, this.keyId, this.keyData});
|
||||
Uint8List privateKey;
|
||||
|
||||
|
|
@ -632,28 +616,23 @@ class OpenSSSS {
|
|||
_keyFromPassphrase,
|
||||
_KeyFromPassphraseArgs(
|
||||
passphrase: passphrase,
|
||||
info: _PassphraseInfo(
|
||||
algorithm: keyData.content['passphrase']['algorithm'],
|
||||
salt: keyData.content['passphrase']['salt'],
|
||||
iterations: keyData.content['passphrase']['iterations'],
|
||||
bits: keyData.content['passphrase']['bits'],
|
||||
),
|
||||
info: keyData.passphrase,
|
||||
));
|
||||
} else if (recoveryKey != null) {
|
||||
privateKey = SSSS.decodeRecoveryKey(recoveryKey);
|
||||
} else {
|
||||
throw 'Nothing specified';
|
||||
throw Exception('Nothing specified');
|
||||
}
|
||||
// verify the validity of the key
|
||||
if (!ssss.checkKey(privateKey, keyData)) {
|
||||
privateKey = null;
|
||||
throw 'Inalid key';
|
||||
throw Exception('Inalid key');
|
||||
}
|
||||
}
|
||||
|
||||
void setPrivateKey(Uint8List key) {
|
||||
if (!ssss.checkKey(key, keyData)) {
|
||||
throw 'Invalid key';
|
||||
throw Exception('Invalid key');
|
||||
}
|
||||
privateKey = key;
|
||||
}
|
||||
|
|
@ -677,7 +656,7 @@ class OpenSSSS {
|
|||
|
||||
class _KeyFromPassphraseArgs {
|
||||
final String passphrase;
|
||||
final _PassphraseInfo info;
|
||||
final PassphraseInfo info;
|
||||
_KeyFromPassphraseArgs({this.passphrase, this.info});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import 'package:olm/olm.dart' as olm;
|
|||
|
||||
import '../encryption.dart';
|
||||
import '../ssss.dart';
|
||||
import '../cross_signing.dart';
|
||||
import '../key_manager.dart';
|
||||
import '../../famedlysdk.dart';
|
||||
import '../../matrix_api/utils/logs.dart';
|
||||
|
|
@ -45,6 +44,7 @@ enum BootstrapState {
|
|||
done, // done
|
||||
}
|
||||
|
||||
/// Bootstrapping SSSS and cross-signing
|
||||
class Bootstrap {
|
||||
final Encryption encryption;
|
||||
Client get client => encryption.client;
|
||||
|
|
@ -162,7 +162,7 @@ class Bootstrap {
|
|||
|
||||
void wipeSsss(bool wipe) {
|
||||
if (state != BootstrapState.askWipeSsss) {
|
||||
throw Exception('Wrong State');
|
||||
throw BootstrapBadStateException('Wrong State');
|
||||
}
|
||||
if (wipe) {
|
||||
state = BootstrapState.askNewSsss;
|
||||
|
|
@ -178,7 +178,7 @@ class Bootstrap {
|
|||
|
||||
void useExistingSsss(bool use) {
|
||||
if (state != BootstrapState.askUseExistingSsss) {
|
||||
throw Exception('Wrong State');
|
||||
throw BootstrapBadStateException('Wrong State');
|
||||
}
|
||||
if (use) {
|
||||
newSsssKey = encryption.ssss.open(encryption.ssss.defaultKeyId);
|
||||
|
|
@ -192,7 +192,7 @@ class Bootstrap {
|
|||
|
||||
void ignoreBadSecrets(bool ignore) {
|
||||
if (state != BootstrapState.askBadSsss) {
|
||||
throw Exception('Wrong State');
|
||||
throw BootstrapBadStateException('Wrong State');
|
||||
}
|
||||
if (ignore) {
|
||||
migrateOldSsss();
|
||||
|
|
@ -221,14 +221,14 @@ class Bootstrap {
|
|||
|
||||
void unlockedSsss() {
|
||||
if (state != BootstrapState.askUnlockSsss) {
|
||||
throw Exception('Wrong State');
|
||||
throw BootstrapBadStateException('Wrong State');
|
||||
}
|
||||
state = BootstrapState.askNewSsss;
|
||||
}
|
||||
|
||||
Future<void> newSsss([String passphrase]) async {
|
||||
if (state != BootstrapState.askNewSsss) {
|
||||
throw Exception('Wrong State');
|
||||
throw BootstrapBadStateException('Wrong State');
|
||||
}
|
||||
state = BootstrapState.loading;
|
||||
try {
|
||||
|
|
@ -285,10 +285,10 @@ class Bootstrap {
|
|||
|
||||
void openExistingSsss() {
|
||||
if (state != BootstrapState.openExistingSsss) {
|
||||
throw 'Bad State';
|
||||
throw BootstrapBadStateException();
|
||||
}
|
||||
if (!newSsssKey.isUnlocked) {
|
||||
throw 'Key not unlocked';
|
||||
throw BootstrapBadStateException('Key not unlocked');
|
||||
}
|
||||
checkCrossSigning();
|
||||
}
|
||||
|
|
@ -306,7 +306,7 @@ class Bootstrap {
|
|||
|
||||
void wipeCrossSigning(bool wipe) {
|
||||
if (state != BootstrapState.askWipeCrossSigning) {
|
||||
throw 'Bad State';
|
||||
throw BootstrapBadStateException();
|
||||
}
|
||||
if (wipe) {
|
||||
state = BootstrapState.askSetupCrossSigning;
|
||||
|
|
@ -320,7 +320,7 @@ class Bootstrap {
|
|||
bool setupSelfSigningKey = false,
|
||||
bool setupUserSigningKey = false}) async {
|
||||
if (state != BootstrapState.askSetupCrossSigning) {
|
||||
throw 'Bad State';
|
||||
throw BootstrapBadStateException();
|
||||
}
|
||||
if (!setupMasterKey && !setupSelfSigningKey && !setupUserSigningKey) {
|
||||
checkOnlineKeyBackup();
|
||||
|
|
@ -345,16 +345,17 @@ class Bootstrap {
|
|||
},
|
||||
};
|
||||
masterKey = MatrixCrossSigningKey.fromJson(json);
|
||||
secretsToStore[MASTER_KEY] = base64.encode(masterSigningKey);
|
||||
secretsToStore[EventTypes.CrossSigningMasterKey] =
|
||||
base64.encode(masterSigningKey);
|
||||
} finally {
|
||||
master.free();
|
||||
}
|
||||
} else {
|
||||
masterSigningKey =
|
||||
base64.decode(await newSsssKey.getStored(MASTER_KEY) ?? '');
|
||||
masterSigningKey = base64.decode(
|
||||
await newSsssKey.getStored(EventTypes.CrossSigningMasterKey) ?? '');
|
||||
if (masterSigningKey == null || masterSigningKey.isEmpty) {
|
||||
// no master signing key :(
|
||||
throw 'No master key';
|
||||
throw BootstrapBadStateException('No master key');
|
||||
}
|
||||
final master = olm.PkSigning();
|
||||
try {
|
||||
|
|
@ -391,7 +392,8 @@ class Bootstrap {
|
|||
},
|
||||
};
|
||||
selfSigningKey = MatrixCrossSigningKey.fromJson(json);
|
||||
secretsToStore[SELF_SIGNING_KEY] = base64.encode(selfSigningPriv);
|
||||
secretsToStore[EventTypes.CrossSigningSelfSigning] =
|
||||
base64.encode(selfSigningPriv);
|
||||
} finally {
|
||||
selfSigning.free();
|
||||
}
|
||||
|
|
@ -415,7 +417,8 @@ class Bootstrap {
|
|||
},
|
||||
};
|
||||
userSigningKey = MatrixCrossSigningKey.fromJson(json);
|
||||
secretsToStore[USER_SIGNING_KEY] = base64.encode(userSigningPriv);
|
||||
secretsToStore[EventTypes.CrossSigningUserSigning] =
|
||||
base64.encode(userSigningPriv);
|
||||
} finally {
|
||||
userSigning.free();
|
||||
}
|
||||
|
|
@ -451,7 +454,8 @@ class Bootstrap {
|
|||
if (masterKey != null) {
|
||||
if (client.userDeviceKeys[client.userID].masterKey.ed25519Key !=
|
||||
masterKey.publicKey) {
|
||||
throw 'ERROR: New master key does not match up!';
|
||||
throw BootstrapBadStateException(
|
||||
'ERROR: New master key does not match up!');
|
||||
}
|
||||
await client.userDeviceKeys[client.userID].masterKey
|
||||
.setVerified(true, false);
|
||||
|
|
@ -484,7 +488,7 @@ class Bootstrap {
|
|||
|
||||
void wipeOnlineKeyBackup(bool wipe) {
|
||||
if (state != BootstrapState.askWipeOnlineKeyBackup) {
|
||||
throw 'Bad State';
|
||||
throw BootstrapBadStateException();
|
||||
}
|
||||
if (wipe) {
|
||||
state = BootstrapState.askSetupOnlineKeyBackup;
|
||||
|
|
@ -495,7 +499,7 @@ class Bootstrap {
|
|||
|
||||
Future<void> askSetupOnlineKeyBackup(bool setup) async {
|
||||
if (state != BootstrapState.askSetupOnlineKeyBackup) {
|
||||
throw 'Bad State';
|
||||
throw BootstrapBadStateException();
|
||||
}
|
||||
if (!setup) {
|
||||
state = BootstrapState.done;
|
||||
|
|
@ -527,6 +531,7 @@ class Bootstrap {
|
|||
'[Bootstrapping] Error setting up online key backup: ' + e.toString(),
|
||||
s);
|
||||
state = BootstrapState.error;
|
||||
encryption.onError.add(SdkError(exception: e, stackTrace: s));
|
||||
return;
|
||||
}
|
||||
state = BootstrapState.done;
|
||||
|
|
@ -541,3 +546,11 @@ class Bootstrap {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BootstrapBadStateException implements Exception {
|
||||
String cause;
|
||||
BootstrapBadStateException([this.cause = 'Bad state']);
|
||||
|
||||
@override
|
||||
String toString() => 'BootstrapBadStateException: $cause';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ _KeyVerificationMethod _makeVerificationMethod(
|
|||
if (type == 'm.sas.v1') {
|
||||
return _KeyVerificationMethodSas(request: request);
|
||||
}
|
||||
throw 'Unkown method type';
|
||||
throw Exception('Unkown method type');
|
||||
}
|
||||
|
||||
class KeyVerification {
|
||||
|
|
@ -883,7 +883,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
|
|||
: theirInfo + ourInfo) +
|
||||
request.transactionId;
|
||||
} else {
|
||||
throw 'Unknown key agreement protocol';
|
||||
throw Exception('Unknown key agreement protocol');
|
||||
}
|
||||
return sas.generate_bytes(sasInfo, bytes);
|
||||
}
|
||||
|
|
@ -965,14 +965,14 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
|
|||
olmutil.free();
|
||||
return ret;
|
||||
}
|
||||
throw 'Unknown hash method';
|
||||
throw Exception('Unknown hash method');
|
||||
}
|
||||
|
||||
String _calculateMac(String input, String info) {
|
||||
if (messageAuthenticationCode == 'hkdf-hmac-sha256') {
|
||||
return sas.calculate_mac(input, info);
|
||||
} else {
|
||||
throw 'Unknown message authentification code';
|
||||
throw Exception('Unknown message authentification code');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,4 +66,6 @@ export 'matrix_api/model/upload_key_signatures_response.dart';
|
|||
export 'matrix_api/model/user_search_result.dart';
|
||||
export 'matrix_api/model/well_known_informations.dart';
|
||||
export 'matrix_api/model/who_is_info.dart';
|
||||
export 'matrix_api/model/events/secret_storage_default_key_content.dart';
|
||||
export 'matrix_api/model/events/secret_storage_key_content.dart';
|
||||
export 'matrix_api/model/events/tombstone_content.dart';
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ abstract class EventTypes {
|
|||
|
||||
static const String CrossSigningSelfSigning = 'm.cross_signing.self_signing';
|
||||
static const String CrossSigningUserSigning = 'm.cross_signing.user_signing';
|
||||
static const String CrossSigningMasterKey = 'm.cross_signing.master';
|
||||
static const String MegolmBackup = 'm.megolm_backup.v1';
|
||||
static const String SecretStorageDefaultKey = 'm.secret_storage.default_key';
|
||||
static String secretStorageKey(String keyId) => 'm.secret_storage.key.$keyId';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
import 'package:famedlysdk/matrix_api/model/basic_event.dart';
|
||||
import '../../utils/try_get_map_extension.dart';
|
||||
|
||||
extension SecretStorageDefaultKeyContentBasicEventExtension on BasicEvent {
|
||||
SecretStorageDefaultKeyContent get parsedSecretStorageDefaultKeyContent =>
|
||||
SecretStorageDefaultKeyContent.fromJson(content);
|
||||
}
|
||||
|
||||
class SecretStorageDefaultKeyContent {
|
||||
String key;
|
||||
|
||||
SecretStorageDefaultKeyContent();
|
||||
|
||||
SecretStorageDefaultKeyContent.fromJson(Map<String, dynamic> json)
|
||||
: key = json.tryGet<String>('key');
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (key != null) data['key'] = key;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import 'package:famedlysdk/matrix_api/model/basic_event.dart';
|
||||
import '../../utils/try_get_map_extension.dart';
|
||||
|
||||
extension SecretStorageKeyContentBasicEventExtension on BasicEvent {
|
||||
SecretStorageKeyContent get parsedSecretStorageKeyContent =>
|
||||
SecretStorageKeyContent.fromJson(content);
|
||||
}
|
||||
|
||||
class SecretStorageKeyContent {
|
||||
PassphraseInfo passphrase;
|
||||
String iv;
|
||||
String mac;
|
||||
String algorithm;
|
||||
|
||||
SecretStorageKeyContent();
|
||||
|
||||
SecretStorageKeyContent.fromJson(Map<String, dynamic> json)
|
||||
: passphrase = json['passphrase'] is Map<String, dynamic>
|
||||
? PassphraseInfo.fromJson(json['passphrase'])
|
||||
: null,
|
||||
iv = json.tryGet<String>('iv'),
|
||||
mac = json.tryGet<String>('mac'),
|
||||
algorithm = json.tryGet<String>('algorithm');
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (passphrase != null) data['passphrase'] = passphrase.toJson();
|
||||
if (iv != null) data['iv'] = iv;
|
||||
if (mac != null) data['mac'] = mac;
|
||||
if (algorithm != null) data['algorithm'] = algorithm;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class PassphraseInfo {
|
||||
String algorithm;
|
||||
String salt;
|
||||
int iterations;
|
||||
int bits;
|
||||
|
||||
PassphraseInfo();
|
||||
|
||||
PassphraseInfo.fromJson(Map<String, dynamic> json)
|
||||
: algorithm = json.tryGet<String>('algorithm'),
|
||||
salt = json.tryGet<String>('salt'),
|
||||
iterations = json.tryGet<int>('iterations'),
|
||||
bits = json.tryGet<int>('bits');
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (algorithm != null) data['algorithm'] = algorithm;
|
||||
if (salt != null) data['salt'] = salt;
|
||||
if (iterations != null) data['iterations'] = iterations;
|
||||
if (bits != null) data['bits'] = bits;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
@ -325,7 +325,7 @@ class Client extends MatrixApi {
|
|||
if (response.accessToken == null ||
|
||||
response.deviceId == null ||
|
||||
response.userId == null) {
|
||||
throw 'Registered but token, device ID or user ID is null.';
|
||||
throw Exception('Registered but token, device ID or user ID is null.');
|
||||
}
|
||||
await init(
|
||||
newToken: response.accessToken,
|
||||
|
|
@ -410,6 +410,7 @@ class Client extends MatrixApi {
|
|||
}
|
||||
}
|
||||
|
||||
/// Run any request and react on user interactive authentication flows here.
|
||||
Future<T> uiaRequestBackground<T>(
|
||||
Future<T> Function(Map<String, dynamic> auth) request) {
|
||||
final completer = Completer<T>();
|
||||
|
|
@ -573,6 +574,7 @@ class Client extends MatrixApi {
|
|||
final StreamController<SdkError> onSyncError = StreamController.broadcast();
|
||||
|
||||
/// Synchronization erros are coming here.
|
||||
@Deprecated('Please use encryption.onToDeviceEventDecryptionError instead')
|
||||
final StreamController<ToDeviceEventDecryptionError> onOlmError =
|
||||
StreamController.broadcast();
|
||||
|
||||
|
|
@ -948,22 +950,7 @@ class Client extends MatrixApi {
|
|||
for (var i = 0; i < events.length; i++) {
|
||||
var toDeviceEvent = ToDeviceEvent.fromJson(events[i].toJson());
|
||||
if (toDeviceEvent.type == EventTypes.Encrypted && encryptionEnabled) {
|
||||
try {
|
||||
toDeviceEvent = await encryption.decryptToDeviceEvent(toDeviceEvent);
|
||||
} catch (e, s) {
|
||||
Logs.error(
|
||||
'[LibOlm] Could not decrypt to device event from ${toDeviceEvent.sender} with content: ${toDeviceEvent.content}\n${e.toString()}',
|
||||
s);
|
||||
|
||||
onOlmError.add(
|
||||
ToDeviceEventDecryptionError(
|
||||
exception: e is Exception ? e : Exception(e),
|
||||
stackTrace: s,
|
||||
toDeviceEvent: toDeviceEvent,
|
||||
),
|
||||
);
|
||||
toDeviceEvent = ToDeviceEvent.fromJson(events[i].toJson());
|
||||
}
|
||||
toDeviceEvent = await encryption.decryptToDeviceEvent(toDeviceEvent);
|
||||
}
|
||||
if (encryptionEnabled) {
|
||||
await encryption.handleToDeviceEvent(toDeviceEvent);
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class DeviceKeysList {
|
|||
final roomId =
|
||||
await User(userId, room: Room(client: client)).startDirectChat();
|
||||
if (roomId == null) {
|
||||
throw 'Unable to start new room';
|
||||
throw Exception('Unable to start new room');
|
||||
}
|
||||
final room =
|
||||
client.getRoomById(roomId) ?? Room(id: roomId, client: client);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
import '../../famedlysdk.dart';
|
||||
|
||||
/// Wrapper to handle User interactive authentication requests
|
||||
class UiaRequest<T> {
|
||||
void Function() onUpdate;
|
||||
void Function() onDone;
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ void main() {
|
|||
var finished = false;
|
||||
final request = UiaRequest(
|
||||
request: (auth) async {
|
||||
throw 'nope';
|
||||
throw Exception('nope');
|
||||
},
|
||||
onUpdate: () => updated = true,
|
||||
onDone: () => finished = true,
|
||||
|
|
|
|||
Loading…
Reference in New Issue