additional validation of received secrets
This commit is contained in:
parent
a7bb8375dc
commit
41a08d4c28
|
@ -97,8 +97,8 @@ class Client {
|
||||||
/// enableE2eeRecovery: Enable additional logic to try to recover from bad e2ee sessions
|
/// enableE2eeRecovery: Enable additional logic to try to recover from bad e2ee sessions
|
||||||
Client(this.clientName,
|
Client(this.clientName,
|
||||||
{this.debug = false, this.database, this.enableE2eeRecovery = false}) {
|
{this.debug = false, this.database, this.enableE2eeRecovery = false}) {
|
||||||
keyManager = KeyManager(this);
|
|
||||||
ssss = SSSS(this);
|
ssss = SSSS(this);
|
||||||
|
keyManager = KeyManager(this);
|
||||||
crossSigning = CrossSigning(this);
|
crossSigning = CrossSigning(this);
|
||||||
onLoginStateChanged.stream.listen((loginState) {
|
onLoginStateChanged.stream.listen((loginState) {
|
||||||
print('LoginState: ${loginState.toString()}');
|
print('LoginState: ${loginState.toString()}');
|
||||||
|
|
|
@ -12,7 +12,28 @@ const MASTER_KEY = 'm.cross_signing.master';
|
||||||
|
|
||||||
class CrossSigning {
|
class CrossSigning {
|
||||||
final Client client;
|
final Client client;
|
||||||
CrossSigning(this.client);
|
CrossSigning(this.client) {
|
||||||
|
client.ssss.setValidator(SELF_SIGNING_KEY, (String secret) async {
|
||||||
|
final keyObj = olm.PkSigning();
|
||||||
|
try {
|
||||||
|
return keyObj.init_with_seed(base64.decode(secret)) == client.userDeviceKeys[client.userID].selfSigningKey.ed25519Key;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
keyObj.free();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client.ssss.setValidator(USER_SIGNING_KEY, (String secret) async {
|
||||||
|
final keyObj = olm.PkSigning();
|
||||||
|
try {
|
||||||
|
return keyObj.init_with_seed(base64.decode(secret)) == client.userDeviceKeys[client.userID].userSigningKey.ed25519Key;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
keyObj.free();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
bool get enabled =>
|
bool get enabled =>
|
||||||
client.accountData[SELF_SIGNING_KEY] != null &&
|
client.accountData[SELF_SIGNING_KEY] != null &&
|
||||||
|
|
|
@ -15,7 +15,19 @@ class KeyManager {
|
||||||
final outgoingShareRequests = <String, KeyManagerKeyShareRequest>{};
|
final outgoingShareRequests = <String, KeyManagerKeyShareRequest>{};
|
||||||
final incomingShareRequests = <String, KeyManagerKeyShareRequest>{};
|
final incomingShareRequests = <String, KeyManagerKeyShareRequest>{};
|
||||||
|
|
||||||
KeyManager(this.client);
|
KeyManager(this.client) {
|
||||||
|
client.ssss.setValidator(MEGOLM_KEY, (String secret) async {
|
||||||
|
final keyObj = olm.PkDecryption();
|
||||||
|
try {
|
||||||
|
final info = await getRoomKeysInfo();
|
||||||
|
return keyObj.init_with_private_key(base64.decode(secret)) == info['auth_data']['public_key'];
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
keyObj.free();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
bool get enabled => client.accountData[MEGOLM_KEY] != null;
|
bool get enabled => client.accountData[MEGOLM_KEY] != null;
|
||||||
|
|
||||||
|
@ -42,50 +54,51 @@ class KeyManager {
|
||||||
}
|
}
|
||||||
final privateKey = base64.decode(await client.ssss.getCached(MEGOLM_KEY));
|
final privateKey = base64.decode(await client.ssss.getCached(MEGOLM_KEY));
|
||||||
final decryption = olm.PkDecryption();
|
final decryption = olm.PkDecryption();
|
||||||
|
final info = await getRoomKeysInfo();
|
||||||
String backupPubKey;
|
String backupPubKey;
|
||||||
try {
|
try {
|
||||||
backupPubKey = decryption.init_with_private_key(privateKey);
|
backupPubKey = decryption.init_with_private_key(privateKey);
|
||||||
} catch (_) {
|
|
||||||
decryption.free();
|
if (backupPubKey == null || !info.containsKey('auth_data') || !(info['auth_data'] is Map) || info['auth_data']['public_key'] != backupPubKey) {
|
||||||
rethrow;
|
|
||||||
}
|
return;
|
||||||
if (backupPubKey == null) {
|
|
||||||
decryption.free();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO: check if pubkey is valid
|
|
||||||
for (final roomEntries in payload['rooms'].entries) {
|
|
||||||
final roomId = roomEntries.key;
|
|
||||||
if (!(roomEntries.value is Map) || !(roomEntries.value['sessions'] is Map)) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
for (final sessionEntries in roomEntries.value['sessions'].entries) {
|
// TODO: check if pubkey is valid
|
||||||
final sessionId = sessionEntries.key;
|
for (final roomEntries in payload['rooms'].entries) {
|
||||||
final rawEncryptedSession = sessionEntries.value;
|
final roomId = roomEntries.key;
|
||||||
if (!(rawEncryptedSession is Map)) {
|
if (!(roomEntries.value is Map) || !(roomEntries.value['sessions'] is Map)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final firstMessageIndex = rawEncryptedSession['first_message_index'] is int ? rawEncryptedSession['first_message_index'] : null;
|
for (final sessionEntries in roomEntries.value['sessions'].entries) {
|
||||||
final forwardedCount = rawEncryptedSession['forwarded_count'] is int ? rawEncryptedSession['forwarded_count'] : null;
|
final sessionId = sessionEntries.key;
|
||||||
final isVerified = rawEncryptedSession['is_verified'] is bool ? rawEncryptedSession['is_verified'] : null;
|
final rawEncryptedSession = sessionEntries.value;
|
||||||
final sessionData = rawEncryptedSession['session_data'];
|
if (!(rawEncryptedSession is Map)) {
|
||||||
if (firstMessageIndex == null || forwardedCount == null || isVerified == null || !(sessionData is Map)) {
|
continue;
|
||||||
continue;
|
}
|
||||||
}
|
final firstMessageIndex = rawEncryptedSession['first_message_index'] is int ? rawEncryptedSession['first_message_index'] : null;
|
||||||
final senderKey = sessionData['sender_key'];
|
final forwardedCount = rawEncryptedSession['forwarded_count'] is int ? rawEncryptedSession['forwarded_count'] : null;
|
||||||
Map<String, dynamic> decrypted;
|
final isVerified = rawEncryptedSession['is_verified'] is bool ? rawEncryptedSession['is_verified'] : null;
|
||||||
try {
|
final sessionData = rawEncryptedSession['session_data'];
|
||||||
decrypted = json.decode(decryption.decrypt(sessionData['ephemeral'], sessionData['mac'], sessionData['ciphertext']));
|
if (firstMessageIndex == null || forwardedCount == null || isVerified == null || !(sessionData is Map)) {
|
||||||
} catch (err) {
|
continue;
|
||||||
print('[LibOlm] Error decrypting room key: ' + err.toString());
|
}
|
||||||
}
|
final senderKey = sessionData['sender_key'];
|
||||||
if (decrypted != null) {
|
Map<String, dynamic> decrypted;
|
||||||
decrypted['session_id'] = sessionId;
|
try {
|
||||||
decrypted['room_id'] = roomId;
|
decrypted = json.decode(decryption.decrypt(sessionData['ephemeral'], sessionData['mac'], sessionData['ciphertext']));
|
||||||
final room = client.getRoomById(roomId) ?? Room(id: roomId, client: client);
|
} catch (err) {
|
||||||
room.setInboundGroupSession(sessionId, decrypted, forwarded: true);
|
print('[LibOlm] Error decrypting room key: ' + err.toString());
|
||||||
|
}
|
||||||
|
if (decrypted != null) {
|
||||||
|
decrypted['session_id'] = sessionId;
|
||||||
|
decrypted['room_id'] = roomId;
|
||||||
|
final room = client.getRoomById(roomId) ?? Room(id: roomId, client: client);
|
||||||
|
room.setInboundGroupSession(sessionId, decrypted, forwarded: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
decryption.free();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ const OLM_PRIVATE_KEY_LENGTH = 32; // TODO: fetch from dart-olm
|
||||||
class SSSS {
|
class SSSS {
|
||||||
final Client client;
|
final Client client;
|
||||||
final pendingShareRequests = <String, _ShareRequest>{};
|
final pendingShareRequests = <String, _ShareRequest>{};
|
||||||
|
final _validators = <String, Future<bool> Function(String)>{};
|
||||||
SSSS(this.client);
|
SSSS(this.client);
|
||||||
|
|
||||||
static _DerivedKeys deriveKeys(Uint8List key, String name) {
|
static _DerivedKeys deriveKeys(Uint8List key, String name) {
|
||||||
|
@ -119,6 +120,10 @@ class SSSS {
|
||||||
info.iterations, info.bits != null ? info.bits / 8 : 32));
|
info.iterations, info.bits != null ? info.bits / 8 : 32));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setValidator(String type, Future<bool> Function(String) validator) {
|
||||||
|
_validators[type] = validator;
|
||||||
|
}
|
||||||
|
|
||||||
String get defaultKeyId {
|
String get defaultKeyId {
|
||||||
final keyData = client.accountData['m.secret_storage.default_key'];
|
final keyData = client.accountData['m.secret_storage.default_key'];
|
||||||
if (keyData == null || !(keyData.content['key'] is String)) {
|
if (keyData == null || !(keyData.content['key'] is String)) {
|
||||||
|
@ -312,11 +317,17 @@ class SSSS {
|
||||||
print('[SSSS] Someone else replied?');
|
print('[SSSS] Someone else replied?');
|
||||||
return; // someone replied whom we didn't send the share request to
|
return; // someone replied whom we didn't send the share request to
|
||||||
}
|
}
|
||||||
pendingShareRequests.remove(request.requestId);
|
final secret = event.content['secret'];
|
||||||
if (!(event.content['secret'] is String)) {
|
if (!(event.content['secret'] is String)) {
|
||||||
print('[SSSS] Secret wasn\'t a string');
|
print('[SSSS] Secret wasn\'t a string');
|
||||||
return; // the secret wasn't a string....wut?
|
return; // the secret wasn't a string....wut?
|
||||||
}
|
}
|
||||||
|
// let's validate if the secret is, well, valid
|
||||||
|
if (_validators.containsKey(request.type) && !(await _validators[request.type](secret))) {
|
||||||
|
print('[SSSS] The received secret was invalid');
|
||||||
|
return; // didn't pass the validator
|
||||||
|
}
|
||||||
|
pendingShareRequests.remove(request.requestId);
|
||||||
if (request.start.add(Duration(minutes: 15)).isBefore(DateTime.now())) {
|
if (request.start.add(Duration(minutes: 15)).isBefore(DateTime.now())) {
|
||||||
print('[SSSS] Request is too far in the past');
|
print('[SSSS] Request is too far in the past');
|
||||||
return; // our request is more than 15min in the past...better not trust it anymore
|
return; // our request is more than 15min in the past...better not trust it anymore
|
||||||
|
@ -326,7 +337,7 @@ class SSSS {
|
||||||
final keyId = keyIdFromType(request.type);
|
final keyId = keyIdFromType(request.type);
|
||||||
if (keyId != null) {
|
if (keyId != null) {
|
||||||
await client.database.storeSSSSCache(
|
await client.database.storeSSSSCache(
|
||||||
client.id, request.type, keyId, event.content['secret']);
|
client.id, request.type, keyId, secret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue