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 '../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,
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
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) {
|
Event decryptRoomEventSync(String roomId, Event event) {
|
||||||
|
|
|
||||||
|
|
@ -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});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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 ||
|
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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue