refactor: Add secretstoragekeycontent

This commit is contained in:
Christian Pauly 2020-12-10 09:39:27 +01:00
parent 49f0679fbf
commit b563aec7bb
13 changed files with 226 additions and 139 deletions

View File

@ -24,15 +24,12 @@ import 'package:olm/olm.dart' as olm;
import '../famedlysdk.dart'; import '../famedlysdk.dart';
import 'encryption.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 { class CrossSigning {
final Encryption encryption; final Encryption encryption;
Client get client => encryption.client; Client get client => encryption.client;
CrossSigning(this.encryption) { CrossSigning(this.encryption) {
encryption.ssss.setValidator(SELF_SIGNING_KEY, (String secret) async { encryption.ssss.setValidator(EventTypes.CrossSigningSelfSigning,
(String secret) async {
final keyObj = olm.PkSigning(); final keyObj = olm.PkSigning();
try { try {
return keyObj.init_with_seed(base64.decode(secret)) == return keyObj.init_with_seed(base64.decode(secret)) ==
@ -43,7 +40,8 @@ class CrossSigning {
keyObj.free(); keyObj.free();
} }
}); });
encryption.ssss.setValidator(USER_SIGNING_KEY, (String secret) async { encryption.ssss.setValidator(EventTypes.CrossSigningUserSigning,
(String secret) async {
final keyObj = olm.PkSigning(); final keyObj = olm.PkSigning();
try { try {
return keyObj.init_with_seed(base64.decode(secret)) == return keyObj.init_with_seed(base64.decode(secret)) ==
@ -57,23 +55,27 @@ class CrossSigning {
} }
bool get enabled => bool get enabled =>
client.accountData[SELF_SIGNING_KEY] != null && client.accountData[EventTypes.CrossSigningSelfSigning] != null &&
client.accountData[USER_SIGNING_KEY] != null && client.accountData[EventTypes.CrossSigningUserSigning] != null &&
client.accountData[MASTER_KEY] != null; client.accountData[EventTypes.CrossSigningMasterKey] != null;
Future<bool> isCached() async { Future<bool> isCached() async {
if (!enabled) { if (!enabled) {
return false; return false;
} }
return (await encryption.ssss.getCached(SELF_SIGNING_KEY)) != null && return (await encryption.ssss
(await encryption.ssss.getCached(USER_SIGNING_KEY)) != null; .getCached(EventTypes.CrossSigningSelfSigning)) !=
null &&
(await encryption.ssss.getCached(EventTypes.CrossSigningUserSigning)) !=
null;
} }
Future<void> selfSign({String passphrase, String recoveryKey}) async { 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.unlock(passphrase: passphrase, recoveryKey: recoveryKey);
await handle.maybeCacheAll(); 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(); final keyObj = olm.PkSigning();
String masterPubkey; String masterPubkey;
try { try {
@ -85,11 +87,11 @@ class CrossSigning {
!client.userDeviceKeys.containsKey(client.userID) || !client.userDeviceKeys.containsKey(client.userID) ||
!client.userDeviceKeys[client.userID].deviceKeys !client.userDeviceKeys[client.userID].deviceKeys
.containsKey(client.deviceID)) { .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; final masterKey = client.userDeviceKeys[client.userID].masterKey;
if (masterKey == null || masterKey.ed25519Key != masterPubkey) { 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 // master key is valid, set it to verified
await masterKey.setVerified(true, false); await masterKey.setVerified(true, false);
@ -146,8 +148,9 @@ class CrossSigning {
// we don't care about signing other cross-signing keys // we don't care about signing other cross-signing keys
} else { } else {
// okay, we'll sign a device key with our self signing key // okay, we'll sign a device key with our self signing key
selfSigningKey ??= base64 selfSigningKey ??= base64.decode(await encryption.ssss
.decode(await encryption.ssss.getCached(SELF_SIGNING_KEY) ?? ''); .getCached(EventTypes.CrossSigningSelfSigning) ??
'');
if (selfSigningKey.isNotEmpty) { if (selfSigningKey.isNotEmpty) {
final signature = _sign(key.signingContent, selfSigningKey); final signature = _sign(key.signingContent, selfSigningKey);
addSignature(key, addSignature(key,
@ -156,8 +159,9 @@ class CrossSigning {
} }
} else if (key is CrossSigningKey && key.usage.contains('master')) { } else if (key is CrossSigningKey && key.usage.contains('master')) {
// we are signing someone elses master key // we are signing someone elses master key
userSigningKey ??= base64 userSigningKey ??= base64.decode(await encryption.ssss
.decode(await encryption.ssss.getCached(USER_SIGNING_KEY) ?? ''); .getCached(EventTypes.CrossSigningUserSigning) ??
'');
if (userSigningKey.isNotEmpty) { if (userSigningKey.isNotEmpty) {
final signature = _sign(key.signingContent, userSigningKey); final signature = _sign(key.signingContent, userSigningKey);
addSignature(key, client.userDeviceKeys[client.userID].userSigningKey, addSignature(key, client.userDeviceKeys[client.userID].userSigningKey,

View File

@ -46,6 +46,13 @@ class Encryption {
String get fingerprintKey => olmManager.fingerprintKey; String get fingerprintKey => olmManager.fingerprintKey;
String get identityKey => olmManager.identityKey; 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; KeyManager keyManager;
OlmManager olmManager; OlmManager olmManager;
KeyVerificationManager keyVerificationManager; KeyVerificationManager keyVerificationManager;
@ -132,7 +139,21 @@ class Encryption {
} }
Future<ToDeviceEvent> decryptToDeviceEvent(ToDeviceEvent event) async { Future<ToDeviceEvent> decryptToDeviceEvent(ToDeviceEvent event) async {
try {
return await olmManager.decryptToDeviceEvent(event); 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) { Event decryptRoomEventSync(String roomId, Event event) {

View File

@ -53,6 +53,9 @@ Map<String, dynamic> _deepcopy(Map<String, dynamic> data) {
return json.decode(json.encode(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 { class SSSS {
final Encryption encryption; final Encryption encryption;
Client get client => encryption.client; Client get client => encryption.client;
@ -113,7 +116,7 @@ class SSSS {
.encode(Hmac(sha256, keys.hmacKey).convert(cipher).bytes) .encode(Hmac(sha256, keys.hmacKey).convert(cipher).bytes)
.replaceAll(RegExp(r'=+$'), ''); .replaceAll(RegExp(r'=+$'), '');
if (hmac != data.mac.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) final decipher = AES(Key(keys.aesKey), mode: AESMode.ctr, padding: null)
.decrypt(Encrypted(cipher), iv: IV(base64.decode(data.iv))); .decrypt(Encrypted(cipher), iv: IV(base64.decode(data.iv)));
@ -128,26 +131,26 @@ class SSSS {
parity ^= b; parity ^= b;
} }
if (parity != 0) { if (parity != 0) {
throw 'Incorrect parity'; throw Exception('Incorrect parity');
} }
for (var i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; i++) { for (var i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; i++) {
if (result[i] != OLM_RECOVERY_KEY_PREFIX[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) { 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, return Uint8List.fromList(result.sublist(OLM_RECOVERY_KEY_PREFIX.length,
OLM_RECOVERY_KEY_PREFIX.length + SSSS_KEY_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) { if (info.algorithm != AlgorithmTypes.pbkdf2) {
throw 'Unknown algorithm'; throw Exception('Unknown algorithm');
} }
final generator = PBKDF2(hashAlgorithm: sha512); final generator = PBKDF2(hashAlgorithm: sha512);
return Uint8List.fromList(generator.generateKey(passphrase, info.salt, return Uint8List.fromList(generator.generateKey(passphrase, info.salt,
@ -162,55 +165,45 @@ class SSSS {
_cacheCallbacks[type] = callback; _cacheCallbacks[type] = callback;
} }
String get defaultKeyId { String get defaultKeyId => client
final keyData = client.accountData[EventTypes.SecretStorageDefaultKey]; .accountData[EventTypes.SecretStorageDefaultKey]
if (keyData == null || !(keyData.content['key'] is String)) { ?.parsedSecretStorageDefaultKeyContent
return null; ?.key;
}
return keyData.content['key'];
}
Future<void> setDefaultKeyId(String keyId) async { Future<void> setDefaultKeyId(String keyId) async {
await client.setAccountData( await client.setAccountData(
client.userID, EventTypes.SecretStorageDefaultKey, <String, dynamic>{ client.userID,
'key': keyId, EventTypes.SecretStorageDefaultKey,
}); (SecretStorageDefaultKeyContent()..key = keyId).toJson(),
);
} }
BasicEvent getKey(String keyId) { SecretStorageKeyContent getKey(String keyId) {
return client.accountData[EventTypes.secretStorageKey(keyId)]; return client.accountData[EventTypes.secretStorageKey(keyId)]
?.parsedSecretStorageKeyContent;
} }
bool isKeyValid(String keyId) { bool isKeyValid(String keyId) =>
final keyData = getKey(keyId); getKey(keyId)?.algorithm == AlgorithmTypes.secretStorageV1AesHmcSha2;
if (keyData == null) {
return false;
}
return keyData.content['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 { Future<OpenSSSS> createKey([String passphrase]) async {
Uint8List privateKey; Uint8List privateKey;
final content = <String, dynamic>{}; final content = SecretStorageKeyContent();
if (passphrase != null) { if (passphrase != null) {
// we need to derive the key off of the passphrase // we need to derive the key off of the passphrase
content['passphrase'] = <String, dynamic>{}; content.passphrase = PassphraseInfo();
content['passphrase']['algorithm'] = AlgorithmTypes.pbkdf2; content.passphrase.algorithm = AlgorithmTypes.pbkdf2;
content['passphrase']['salt'] = base64 content.passphrase.salt = base64
.encode(SecureRandom(PBKDF2_SALT_LENGTH).bytes); // generate salt .encode(SecureRandom(PBKDF2_SALT_LENGTH).bytes); // generate salt
content['passphrase']['iterations'] = PBKDF2_DEFAULT_ITERATIONS; content.passphrase.iterations = PBKDF2_DEFAULT_ITERATIONS;
content['passphrase']['bits'] = SSSS_KEY_LENGTH * 8; content.passphrase.bits = SSSS_KEY_LENGTH * 8;
privateKey = await runInBackground( privateKey = await runInBackground(
_keyFromPassphrase, _keyFromPassphrase,
_KeyFromPassphraseArgs( _KeyFromPassphraseArgs(
passphrase: passphrase, passphrase: passphrase,
info: _PassphraseInfo( info: content.passphrase,
algorithm: content['passphrase']['algorithm'],
salt: content['passphrase']['salt'],
iterations: content['passphrase']['iterations'],
bits: content['passphrase']['bits'],
),
)); ));
} else { } else {
// we need to just generate a new key from scratch // 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 // now that we have the private key, let's create the iv and mac
final encrypted = encryptAes(ZERO_STR, privateKey, ''); final encrypted = encryptAes(ZERO_STR, privateKey, '');
content['iv'] = encrypted.iv; content.iv = encrypted.iv;
content['mac'] = encrypted.mac; content.mac = encrypted.mac;
content['algorithm'] = AlgorithmTypes.secretStorageV1AesHmcSha2; content.algorithm = AlgorithmTypes.secretStorageV1AesHmcSha2;
const KEYID_BYTE_LENGTH = 24; const KEYID_BYTE_LENGTH = 24;
@ -235,7 +228,8 @@ class SSSS {
final waitForAccountData = client.onSync.stream.firstWhere((syncUpdate) => final waitForAccountData = client.onSync.stream.firstWhere((syncUpdate) =>
syncUpdate.accountData syncUpdate.accountData
.any((accountData) => accountData.type == accountDataType)); .any((accountData) => accountData.type == accountDataType));
await client.setAccountData(client.userID, accountDataType, content); await client.setAccountData(
client.userID, accountDataType, content.toJson());
await waitForAccountData; await waitForAccountData;
final key = open(keyId); final key = open(keyId);
@ -243,19 +237,18 @@ class SSSS {
return key; return key;
} }
bool checkKey(Uint8List key, BasicEvent keyData) { bool checkKey(Uint8List key, SecretStorageKeyContent info) {
final info = keyData.content; if (info.algorithm == AlgorithmTypes.secretStorageV1AesHmcSha2) {
if (info['algorithm'] == AlgorithmTypes.secretStorageV1AesHmcSha2) { if ((info.mac is String) && (info.iv is String)) {
if ((info['mac'] is String) && (info['iv'] is String)) { final encrypted = encryptAes(ZERO_STR, key, '', info.iv);
final encrypted = encryptAes(ZERO_STR, key, '', info['iv']); return info.mac.replaceAll(RegExp(r'=+$'), '') ==
return info['mac'].replaceAll(RegExp(r'=+$'), '') ==
encrypted.mac.replaceAll(RegExp(r'=+$'), ''); encrypted.mac.replaceAll(RegExp(r'=+$'), '');
} else { } else {
// no real information about the key, assume it is valid // no real information about the key, assume it is valid
return true; return true;
} }
} else { } else {
throw 'Unknown Algorithm'; throw Exception('Unknown Algorithm');
} }
} }
@ -287,13 +280,13 @@ class SSSS {
Future<String> getStored(String type, String keyId, Uint8List key) async { Future<String> getStored(String type, String keyId, Uint8List key) async {
final secretInfo = client.accountData[type]; final secretInfo = client.accountData[type];
if (secretInfo == null) { if (secretInfo == null) {
throw 'Not found'; throw Exception('Not found');
} }
if (!(secretInfo.content['encrypted'] is Map)) { if (!(secretInfo.content['encrypted'] is Map)) {
throw 'Content is not encrypted'; throw Exception('Content is not encrypted');
} }
if (!(secretInfo.content['encrypted'][keyId] is Map)) { if (!(secretInfo.content['encrypted'][keyId] is Map)) {
throw 'Wrong / unknown key'; throw Exception('Wrong / unknown key');
} }
final enc = secretInfo.content['encrypted'][keyId]; final enc = secretInfo.content['encrypted'][keyId];
final encryptInfo = _Encrypted( final encryptInfo = _Encrypted(
@ -345,7 +338,7 @@ class SSSS {
Future<void> validateAndStripOtherKeys( Future<void> validateAndStripOtherKeys(
String type, String secret, String keyId, Uint8List key) async { String type, String secret, String keyId, Uint8List key) async {
if (await getStored(type, keyId, key) != secret) { 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 // now remove all other keys
final content = _deepcopy(client.accountData[type].content); final content = _deepcopy(client.accountData[type].content);
@ -354,7 +347,7 @@ class SSSS {
content['encrypted'].removeWhere((k, v) => otherKeys.contains(k)); content['encrypted'].removeWhere((k, v) => otherKeys.contains(k));
// yes, we are paranoid... // yes, we are paranoid...
if (await getStored(type, keyId, key) != secret) { 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 // store the thing in your account data
await client.setAccountData(client.userID, type, content); await client.setAccountData(client.userID, type, content);
@ -561,15 +554,15 @@ class SSSS {
OpenSSSS open([String identifier]) { OpenSSSS open([String identifier]) {
identifier ??= defaultKeyId; identifier ??= defaultKeyId;
if (identifier == null) { if (identifier == null) {
throw 'Dont know what to open'; throw Exception('Dont know what to open');
} }
final keyToOpen = keyIdFromType(identifier) ?? identifier; final keyToOpen = keyIdFromType(identifier) ?? identifier;
if (keyToOpen == null) { if (keyToOpen == null) {
throw 'No key found to open'; throw Exception('No key found to open');
} }
final key = getKey(keyToOpen); final key = getKey(keyToOpen);
if (key == null) { if (key == null) {
throw 'Unknown key to open'; throw Exception('Unknown key to open');
} }
return OpenSSSS(ssss: this, keyId: keyToOpen, keyData: key); return OpenSSSS(ssss: this, keyId: keyToOpen, keyData: key);
} }
@ -600,19 +593,10 @@ class _DerivedKeys {
_DerivedKeys({this.aesKey, this.hmacKey}); _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 { class OpenSSSS {
final SSSS ssss; final SSSS ssss;
final String keyId; final String keyId;
final BasicEvent keyData; final SecretStorageKeyContent keyData;
OpenSSSS({this.ssss, this.keyId, this.keyData}); OpenSSSS({this.ssss, this.keyId, this.keyData});
Uint8List privateKey; Uint8List privateKey;
@ -632,28 +616,23 @@ class OpenSSSS {
_keyFromPassphrase, _keyFromPassphrase,
_KeyFromPassphraseArgs( _KeyFromPassphraseArgs(
passphrase: passphrase, passphrase: passphrase,
info: _PassphraseInfo( info: keyData.passphrase,
algorithm: keyData.content['passphrase']['algorithm'],
salt: keyData.content['passphrase']['salt'],
iterations: keyData.content['passphrase']['iterations'],
bits: keyData.content['passphrase']['bits'],
),
)); ));
} else if (recoveryKey != null) { } else if (recoveryKey != null) {
privateKey = SSSS.decodeRecoveryKey(recoveryKey); privateKey = SSSS.decodeRecoveryKey(recoveryKey);
} else { } else {
throw 'Nothing specified'; throw Exception('Nothing specified');
} }
// verify the validity of the key // verify the validity of the key
if (!ssss.checkKey(privateKey, keyData)) { if (!ssss.checkKey(privateKey, keyData)) {
privateKey = null; privateKey = null;
throw 'Inalid key'; throw Exception('Inalid key');
} }
} }
void setPrivateKey(Uint8List key) { void setPrivateKey(Uint8List key) {
if (!ssss.checkKey(key, keyData)) { if (!ssss.checkKey(key, keyData)) {
throw 'Invalid key'; throw Exception('Invalid key');
} }
privateKey = key; privateKey = key;
} }
@ -677,7 +656,7 @@ class OpenSSSS {
class _KeyFromPassphraseArgs { class _KeyFromPassphraseArgs {
final String passphrase; final String passphrase;
final _PassphraseInfo info; final PassphraseInfo info;
_KeyFromPassphraseArgs({this.passphrase, this.info}); _KeyFromPassphraseArgs({this.passphrase, this.info});
} }

View File

@ -24,7 +24,6 @@ import 'package:olm/olm.dart' as olm;
import '../encryption.dart'; import '../encryption.dart';
import '../ssss.dart'; import '../ssss.dart';
import '../cross_signing.dart';
import '../key_manager.dart'; import '../key_manager.dart';
import '../../famedlysdk.dart'; import '../../famedlysdk.dart';
import '../../matrix_api/utils/logs.dart'; import '../../matrix_api/utils/logs.dart';
@ -45,6 +44,7 @@ enum BootstrapState {
done, // done done, // done
} }
/// Bootstrapping SSSS and cross-signing
class Bootstrap { class Bootstrap {
final Encryption encryption; final Encryption encryption;
Client get client => encryption.client; Client get client => encryption.client;
@ -162,7 +162,7 @@ class Bootstrap {
void wipeSsss(bool wipe) { void wipeSsss(bool wipe) {
if (state != BootstrapState.askWipeSsss) { if (state != BootstrapState.askWipeSsss) {
throw Exception('Wrong State'); throw BootstrapBadStateException('Wrong State');
} }
if (wipe) { if (wipe) {
state = BootstrapState.askNewSsss; state = BootstrapState.askNewSsss;
@ -178,7 +178,7 @@ class Bootstrap {
void useExistingSsss(bool use) { void useExistingSsss(bool use) {
if (state != BootstrapState.askUseExistingSsss) { if (state != BootstrapState.askUseExistingSsss) {
throw Exception('Wrong State'); throw BootstrapBadStateException('Wrong State');
} }
if (use) { if (use) {
newSsssKey = encryption.ssss.open(encryption.ssss.defaultKeyId); newSsssKey = encryption.ssss.open(encryption.ssss.defaultKeyId);
@ -192,7 +192,7 @@ class Bootstrap {
void ignoreBadSecrets(bool ignore) { void ignoreBadSecrets(bool ignore) {
if (state != BootstrapState.askBadSsss) { if (state != BootstrapState.askBadSsss) {
throw Exception('Wrong State'); throw BootstrapBadStateException('Wrong State');
} }
if (ignore) { if (ignore) {
migrateOldSsss(); migrateOldSsss();
@ -221,14 +221,14 @@ class Bootstrap {
void unlockedSsss() { void unlockedSsss() {
if (state != BootstrapState.askUnlockSsss) { if (state != BootstrapState.askUnlockSsss) {
throw Exception('Wrong State'); throw BootstrapBadStateException('Wrong State');
} }
state = BootstrapState.askNewSsss; state = BootstrapState.askNewSsss;
} }
Future<void> newSsss([String passphrase]) async { Future<void> newSsss([String passphrase]) async {
if (state != BootstrapState.askNewSsss) { if (state != BootstrapState.askNewSsss) {
throw Exception('Wrong State'); throw BootstrapBadStateException('Wrong State');
} }
state = BootstrapState.loading; state = BootstrapState.loading;
try { try {
@ -285,10 +285,10 @@ class Bootstrap {
void openExistingSsss() { void openExistingSsss() {
if (state != BootstrapState.openExistingSsss) { if (state != BootstrapState.openExistingSsss) {
throw 'Bad State'; throw BootstrapBadStateException();
} }
if (!newSsssKey.isUnlocked) { if (!newSsssKey.isUnlocked) {
throw 'Key not unlocked'; throw BootstrapBadStateException('Key not unlocked');
} }
checkCrossSigning(); checkCrossSigning();
} }
@ -306,7 +306,7 @@ class Bootstrap {
void wipeCrossSigning(bool wipe) { void wipeCrossSigning(bool wipe) {
if (state != BootstrapState.askWipeCrossSigning) { if (state != BootstrapState.askWipeCrossSigning) {
throw 'Bad State'; throw BootstrapBadStateException();
} }
if (wipe) { if (wipe) {
state = BootstrapState.askSetupCrossSigning; state = BootstrapState.askSetupCrossSigning;
@ -320,7 +320,7 @@ class Bootstrap {
bool setupSelfSigningKey = false, bool setupSelfSigningKey = false,
bool setupUserSigningKey = false}) async { bool setupUserSigningKey = false}) async {
if (state != BootstrapState.askSetupCrossSigning) { if (state != BootstrapState.askSetupCrossSigning) {
throw 'Bad State'; throw BootstrapBadStateException();
} }
if (!setupMasterKey && !setupSelfSigningKey && !setupUserSigningKey) { if (!setupMasterKey && !setupSelfSigningKey && !setupUserSigningKey) {
checkOnlineKeyBackup(); checkOnlineKeyBackup();
@ -345,16 +345,17 @@ class Bootstrap {
}, },
}; };
masterKey = MatrixCrossSigningKey.fromJson(json); masterKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[MASTER_KEY] = base64.encode(masterSigningKey); secretsToStore[EventTypes.CrossSigningMasterKey] =
base64.encode(masterSigningKey);
} finally { } finally {
master.free(); master.free();
} }
} else { } else {
masterSigningKey = masterSigningKey = base64.decode(
base64.decode(await newSsssKey.getStored(MASTER_KEY) ?? ''); await newSsssKey.getStored(EventTypes.CrossSigningMasterKey) ?? '');
if (masterSigningKey == null || masterSigningKey.isEmpty) { if (masterSigningKey == null || masterSigningKey.isEmpty) {
// no master signing key :( // no master signing key :(
throw 'No master key'; throw BootstrapBadStateException('No master key');
} }
final master = olm.PkSigning(); final master = olm.PkSigning();
try { try {
@ -391,7 +392,8 @@ class Bootstrap {
}, },
}; };
selfSigningKey = MatrixCrossSigningKey.fromJson(json); selfSigningKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[SELF_SIGNING_KEY] = base64.encode(selfSigningPriv); secretsToStore[EventTypes.CrossSigningSelfSigning] =
base64.encode(selfSigningPriv);
} finally { } finally {
selfSigning.free(); selfSigning.free();
} }
@ -415,7 +417,8 @@ class Bootstrap {
}, },
}; };
userSigningKey = MatrixCrossSigningKey.fromJson(json); userSigningKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[USER_SIGNING_KEY] = base64.encode(userSigningPriv); secretsToStore[EventTypes.CrossSigningUserSigning] =
base64.encode(userSigningPriv);
} finally { } finally {
userSigning.free(); userSigning.free();
} }
@ -451,7 +454,8 @@ class Bootstrap {
if (masterKey != null) { if (masterKey != null) {
if (client.userDeviceKeys[client.userID].masterKey.ed25519Key != if (client.userDeviceKeys[client.userID].masterKey.ed25519Key !=
masterKey.publicKey) { 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 await client.userDeviceKeys[client.userID].masterKey
.setVerified(true, false); .setVerified(true, false);
@ -484,7 +488,7 @@ class Bootstrap {
void wipeOnlineKeyBackup(bool wipe) { void wipeOnlineKeyBackup(bool wipe) {
if (state != BootstrapState.askWipeOnlineKeyBackup) { if (state != BootstrapState.askWipeOnlineKeyBackup) {
throw 'Bad State'; throw BootstrapBadStateException();
} }
if (wipe) { if (wipe) {
state = BootstrapState.askSetupOnlineKeyBackup; state = BootstrapState.askSetupOnlineKeyBackup;
@ -495,7 +499,7 @@ class Bootstrap {
Future<void> askSetupOnlineKeyBackup(bool setup) async { Future<void> askSetupOnlineKeyBackup(bool setup) async {
if (state != BootstrapState.askSetupOnlineKeyBackup) { if (state != BootstrapState.askSetupOnlineKeyBackup) {
throw 'Bad State'; throw BootstrapBadStateException();
} }
if (!setup) { if (!setup) {
state = BootstrapState.done; state = BootstrapState.done;
@ -527,6 +531,7 @@ class Bootstrap {
'[Bootstrapping] Error setting up online key backup: ' + e.toString(), '[Bootstrapping] Error setting up online key backup: ' + e.toString(),
s); s);
state = BootstrapState.error; state = BootstrapState.error;
encryption.onError.add(SdkError(exception: e, stackTrace: s));
return; return;
} }
state = BootstrapState.done; 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';
}

View File

@ -114,7 +114,7 @@ _KeyVerificationMethod _makeVerificationMethod(
if (type == 'm.sas.v1') { if (type == 'm.sas.v1') {
return _KeyVerificationMethodSas(request: request); return _KeyVerificationMethodSas(request: request);
} }
throw 'Unkown method type'; throw Exception('Unkown method type');
} }
class KeyVerification { class KeyVerification {
@ -883,7 +883,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
: theirInfo + ourInfo) + : theirInfo + ourInfo) +
request.transactionId; request.transactionId;
} else { } else {
throw 'Unknown key agreement protocol'; throw Exception('Unknown key agreement protocol');
} }
return sas.generate_bytes(sasInfo, bytes); return sas.generate_bytes(sasInfo, bytes);
} }
@ -965,14 +965,14 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
olmutil.free(); olmutil.free();
return ret; return ret;
} }
throw 'Unknown hash method'; throw Exception('Unknown hash method');
} }
String _calculateMac(String input, String info) { String _calculateMac(String input, String info) {
if (messageAuthenticationCode == 'hkdf-hmac-sha256') { if (messageAuthenticationCode == 'hkdf-hmac-sha256') {
return sas.calculate_mac(input, info); return sas.calculate_mac(input, info);
} else { } else {
throw 'Unknown message authentification code'; throw Exception('Unknown message authentification code');
} }
} }
} }

View File

@ -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/user_search_result.dart';
export 'matrix_api/model/well_known_informations.dart'; export 'matrix_api/model/well_known_informations.dart';
export 'matrix_api/model/who_is_info.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'; export 'matrix_api/model/events/tombstone_content.dart';

View File

@ -44,6 +44,7 @@ abstract class EventTypes {
static const String CrossSigningSelfSigning = 'm.cross_signing.self_signing'; static const String CrossSigningSelfSigning = 'm.cross_signing.self_signing';
static const String CrossSigningUserSigning = 'm.cross_signing.user_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 MegolmBackup = 'm.megolm_backup.v1';
static const String SecretStorageDefaultKey = 'm.secret_storage.default_key'; static const String SecretStorageDefaultKey = 'm.secret_storage.default_key';
static String secretStorageKey(String keyId) => 'm.secret_storage.key.$keyId'; static String secretStorageKey(String keyId) => 'm.secret_storage.key.$keyId';

View File

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

View File

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

View File

@ -325,7 +325,7 @@ class Client extends MatrixApi {
if (response.accessToken == null || if (response.accessToken == null ||
response.deviceId == null || response.deviceId == null ||
response.userId == 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( await init(
newToken: response.accessToken, 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> uiaRequestBackground<T>(
Future<T> Function(Map<String, dynamic> auth) request) { Future<T> Function(Map<String, dynamic> auth) request) {
final completer = Completer<T>(); final completer = Completer<T>();
@ -573,6 +574,7 @@ class Client extends MatrixApi {
final StreamController<SdkError> onSyncError = StreamController.broadcast(); final StreamController<SdkError> onSyncError = StreamController.broadcast();
/// Synchronization erros are coming here. /// Synchronization erros are coming here.
@Deprecated('Please use encryption.onToDeviceEventDecryptionError instead')
final StreamController<ToDeviceEventDecryptionError> onOlmError = final StreamController<ToDeviceEventDecryptionError> onOlmError =
StreamController.broadcast(); StreamController.broadcast();
@ -948,22 +950,7 @@ class Client extends MatrixApi {
for (var i = 0; i < events.length; i++) { for (var i = 0; i < events.length; i++) {
var toDeviceEvent = ToDeviceEvent.fromJson(events[i].toJson()); var toDeviceEvent = ToDeviceEvent.fromJson(events[i].toJson());
if (toDeviceEvent.type == EventTypes.Encrypted && encryptionEnabled) { if (toDeviceEvent.type == EventTypes.Encrypted && encryptionEnabled) {
try {
toDeviceEvent = await encryption.decryptToDeviceEvent(toDeviceEvent); 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());
}
} }
if (encryptionEnabled) { if (encryptionEnabled) {
await encryption.handleToDeviceEvent(toDeviceEvent); await encryption.handleToDeviceEvent(toDeviceEvent);

View File

@ -59,7 +59,7 @@ class DeviceKeysList {
final roomId = final roomId =
await User(userId, room: Room(client: client)).startDirectChat(); await User(userId, room: Room(client: client)).startDirectChat();
if (roomId == null) { if (roomId == null) {
throw 'Unable to start new room'; throw Exception('Unable to start new room');
} }
final room = final room =
client.getRoomById(roomId) ?? Room(id: roomId, client: client); client.getRoomById(roomId) ?? Room(id: roomId, client: client);

View File

@ -18,6 +18,7 @@
import '../../famedlysdk.dart'; import '../../famedlysdk.dart';
/// Wrapper to handle User interactive authentication requests
class UiaRequest<T> { class UiaRequest<T> {
void Function() onUpdate; void Function() onUpdate;
void Function() onDone; void Function() onDone;

View File

@ -79,7 +79,7 @@ void main() {
var finished = false; var finished = false;
final request = UiaRequest( final request = UiaRequest(
request: (auth) async { request: (auth) async {
throw 'nope'; throw Exception('nope');
}, },
onUpdate: () => updated = true, onUpdate: () => updated = true,
onDone: () => finished = true, onDone: () => finished = true,