additional validation of received secrets

This commit is contained in:
Sorunome 2020-05-30 13:13:42 +02:00
parent a7bb8375dc
commit 41a08d4c28
No known key found for this signature in database
GPG key ID: B19471D07FC9BE9C
4 changed files with 86 additions and 41 deletions

View file

@ -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()}');

View file

@ -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 &&

View file

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

View file

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