configurable verification methods

This commit is contained in:
Sorunome 2020-05-30 13:55:09 +02:00
parent d7f2bbe2f9
commit 9971e7377e
No known key found for this signature in database
GPG key ID: B19471D07FC9BE9C
7 changed files with 87 additions and 40 deletions

View file

@ -90,16 +90,25 @@ class Client {
SSSS ssss;
CrossSigning crossSigning;
Set<KeyVerificationMethod> verificationMethods;
/// Create a client
/// clientName = unique identifier of this client
/// debug: Print debug output?
/// database: The database instance to use
/// enableE2eeRecovery: Enable additional logic to try to recover from bad e2ee sessions
/// verificationMethods: A set of all the verification methods this client can handle. Includes:
/// KeyVerificationMethod.numbers: Compare numbers. Most basic, should be supported
/// KeyVerificationMethod.emoji: Compare emojis
Client(this.clientName,
{this.debug = false, this.database, this.enableE2eeRecovery = false}) {
{this.debug = false,
this.database,
this.enableE2eeRecovery = false,
this.verificationMethods}) {
ssss = SSSS(this);
keyManager = KeyManager(this);
crossSigning = CrossSigning(this);
verificationMethods ??= {};
onLoginStateChanged.stream.listen((loginState) {
print('LoginState: ${loginState.toString()}');
});
@ -1209,7 +1218,8 @@ class Client {
}
void _handleToDeviceKeyVerificationRequest(ToDeviceEvent toDeviceEvent) {
if (!toDeviceEvent.type.startsWith('m.key.verification.')) {
if (!toDeviceEvent.type.startsWith('m.key.verification.') ||
verificationMethods.isEmpty) {
return;
}
// we have key verification going on!
@ -1243,7 +1253,8 @@ class Client {
final type = event['type'].startsWith('m.key.verification.')
? event['type']
: event['content']['msgtype'];
if (!type.startsWith('m.key.verification.')) {
if (!type.startsWith('m.key.verification.') ||
verificationMethods.isEmpty) {
return;
}
if (type == 'm.key.verification.request') {

View file

@ -16,7 +16,8 @@ class CrossSigning {
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;
return keyObj.init_with_seed(base64.decode(secret)) ==
client.userDeviceKeys[client.userID].selfSigningKey.ed25519Key;
} catch (_) {
return false;
} finally {
@ -26,7 +27,8 @@ class CrossSigning {
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;
return keyObj.init_with_seed(base64.decode(secret)) ==
client.userDeviceKeys[client.userID].userSigningKey.ed25519Key;
} catch (_) {
return false;
} finally {

View file

@ -20,7 +20,8 @@ class KeyManager {
final keyObj = olm.PkDecryption();
try {
final info = await getRoomKeysInfo();
return keyObj.init_with_private_key(base64.decode(secret)) == info['auth_data']['public_key'];
return keyObj.init_with_private_key(base64.decode(secret)) ==
info['auth_data']['public_key'];
} catch (_) {
return false;
} finally {
@ -59,13 +60,16 @@ class KeyManager {
try {
backupPubKey = decryption.init_with_private_key(privateKey);
if (backupPubKey == null || !info.containsKey('auth_data') || !(info['auth_data'] is Map) || info['auth_data']['public_key'] != backupPubKey) {
if (backupPubKey == null ||
!info.containsKey('auth_data') ||
!(info['auth_data'] is Map) ||
info['auth_data']['public_key'] != backupPubKey) {
return;
}
for (final roomEntries in payload['rooms'].entries) {
final roomId = roomEntries.key;
if (!(roomEntries.value is Map) || !(roomEntries.value['sessions'] is Map)) {
if (!(roomEntries.value is Map) ||
!(roomEntries.value['sessions'] is Map)) {
continue;
}
for (final sessionEntries in roomEntries.value['sessions'].entries) {
@ -74,24 +78,35 @@ class KeyManager {
if (!(rawEncryptedSession is Map)) {
continue;
}
final firstMessageIndex = rawEncryptedSession['first_message_index'] is int ? rawEncryptedSession['first_message_index'] : null;
final forwardedCount = rawEncryptedSession['forwarded_count'] is int ? rawEncryptedSession['forwarded_count'] : null;
final isVerified = rawEncryptedSession['is_verified'] is bool ? rawEncryptedSession['is_verified'] : null;
final firstMessageIndex =
rawEncryptedSession['first_message_index'] is int
? rawEncryptedSession['first_message_index']
: null;
final forwardedCount = rawEncryptedSession['forwarded_count'] is int
? rawEncryptedSession['forwarded_count']
: null;
final isVerified = rawEncryptedSession['is_verified'] is bool
? rawEncryptedSession['is_verified']
: null;
final sessionData = rawEncryptedSession['session_data'];
if (firstMessageIndex == null || forwardedCount == null || isVerified == null || !(sessionData is Map)) {
if (firstMessageIndex == null ||
forwardedCount == null ||
isVerified == null ||
!(sessionData is Map)) {
continue;
}
final senderKey = sessionData['sender_key'];
Map<String, dynamic> decrypted;
try {
decrypted = json.decode(decryption.decrypt(sessionData['ephemeral'], sessionData['mac'], sessionData['ciphertext']));
decrypted = json.decode(decryption.decrypt(sessionData['ephemeral'],
sessionData['mac'], sessionData['ciphertext']));
} catch (err) {
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);
final room =
client.getRoomById(roomId) ?? Room(id: roomId, client: client);
room.setInboundGroupSession(sessionId, decrypted, forwarded: true);
}
}
@ -105,7 +120,8 @@ class KeyManager {
final info = await getRoomKeysInfo();
final ret = await client.jsonRequest(
type: HTTPType.GET,
action: '/client/r0/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}?version=${info['version']}',
action:
'/client/r0/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}?version=${info['version']}',
);
await loadFromResponse({
'rooms': {

View file

@ -72,9 +72,7 @@ class SSSS {
final keys = deriveKeys(key, name);
final cipher = base64.decode(data.ciphertext);
final hmac = base64
.encode(Hmac(sha256, keys.hmacKey)
.convert(cipher)
.bytes)
.encode(Hmac(sha256, keys.hmacKey).convert(cipher).bytes)
.replaceAll(RegExp(r'=+$'), '');
if (hmac != data.mac.replaceAll(RegExp(r'=+$'), '')) {
throw 'Bad MAC';
@ -322,7 +320,8 @@ class SSSS {
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))) {
if (_validators.containsKey(request.type) &&
!(await _validators[request.type](secret))) {
print('[SSSS] The received secret was invalid');
return; // didn't pass the validator
}
@ -335,8 +334,8 @@ class SSSS {
if (client.database != null) {
final keyId = keyIdFromType(request.type);
if (keyId != null) {
await client.database.storeSSSSCache(
client.id, request.type, keyId, secret);
await client.database
.storeSSSSCache(client.id, request.type, keyId, secret);
}
}
}

View file

@ -29,8 +29,8 @@ class DeviceKeysList {
return null;
}
CrossSigningKey getCrossSigningKey(String type) =>
crossSigningKeys.values.firstWhere((k) => k.usage.contains(type), orElse: () => null);
CrossSigningKey getCrossSigningKey(String type) => crossSigningKeys.values
.firstWhere((k) => k.usage.contains(type), orElse: () => null);
CrossSigningKey get masterKey => getCrossSigningKey('master');
CrossSigningKey get selfSigningKey => getCrossSigningKey('self_signing');

View file

@ -52,6 +52,8 @@ enum KeyVerificationState {
error
}
enum KeyVerificationMethod { emoji, numbers }
List<String> _intersect(List<String> a, List<dynamic> b) {
final res = <String>[];
for (final v in a) {
@ -82,8 +84,6 @@ List<int> _bytesToInt(Uint8List bytes, int totalBits) {
return ret;
}
final VERIFICATION_METHODS = ['m.sas.v1'];
_KeyVerificationMethod _makeVerificationMethod(
String type, KeyVerification request) {
if (type == 'm.sas.v1') {
@ -134,9 +134,18 @@ class KeyVerification {
: null);
}
List<String> get knownVerificationMethods {
final methods = <String>[];
if (client.verificationMethods.contains(KeyVerificationMethod.numbers) ||
client.verificationMethods.contains(KeyVerificationMethod.emoji)) {
methods.add('m.sas.v1');
}
return methods;
}
Future<void> sendStart() async {
await send('m.key.verification.request', {
'methods': VERIFICATION_METHODS,
'methods': knownVerificationMethods,
if (room == null) 'timestamp': DateTime.now().millisecondsSinceEpoch,
});
startedVerification = true;
@ -178,7 +187,7 @@ class KeyVerification {
}
// verify it has a method we can use
possibleMethods =
_intersect(VERIFICATION_METHODS, payload['methods']);
_intersect(knownVerificationMethods, payload['methods']);
if (possibleMethods.isEmpty) {
// reject it outright
await cancel('m.unknown_method');
@ -189,7 +198,7 @@ class KeyVerification {
case 'm.key.verification.ready':
_deviceId ??= payload['from_device'];
possibleMethods =
_intersect(VERIFICATION_METHODS, payload['methods']);
_intersect(knownVerificationMethods, payload['methods']);
if (possibleMethods.isEmpty) {
// reject it outright
await cancel('m.unknown_method');
@ -233,7 +242,7 @@ class KeyVerification {
if (!(await verifyLastStep(['m.key.verification.request', null]))) {
return; // abort
}
if (!VERIFICATION_METHODS.contains(payload['method'])) {
if (!knownVerificationMethods.contains(payload['method'])) {
await cancel('m.unknown_method');
return;
}
@ -525,7 +534,6 @@ abstract class _KeyVerificationMethod {
const KNOWN_KEY_AGREEMENT_PROTOCOLS = ['curve25519-hkdf-sha256', 'curve25519'];
const KNOWN_HASHES = ['sha256'];
const KNOWN_MESSAGE_AUTHENTIFICATION_CODES = ['hkdf-hmac-sha256'];
const KNOWN_AUTHENTICATION_TYPES = ['emoji', 'decimal'];
class _KeyVerificationMethodSas extends _KeyVerificationMethod {
_KeyVerificationMethodSas({KeyVerification request})
@ -549,6 +557,19 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
sas?.free();
}
List<String> get knownAuthentificationTypes {
final types = <String>[];
if (request.client.verificationMethods
.contains(KeyVerificationMethod.emoji)) {
types.add('emoji');
}
if (request.client.verificationMethods
.contains(KeyVerificationMethod.numbers)) {
types.add('decimal');
}
return types;
}
@override
Future<void> handlePayload(String type, Map<String, dynamic> payload) async {
try {
@ -630,7 +651,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
'key_agreement_protocols': KNOWN_KEY_AGREEMENT_PROTOCOLS,
'hashes': KNOWN_HASHES,
'message_authentication_codes': KNOWN_MESSAGE_AUTHENTIFICATION_CODES,
'short_authentication_string': KNOWN_AUTHENTICATION_TYPES,
'short_authentication_string': knownAuthentificationTypes,
};
request.makePayload(payload);
// We just store the canonical json in here for later verification
@ -662,7 +683,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
}
messageAuthenticationCode = possibleMessageAuthenticationCodes.first;
final possibleAuthenticationTypes = _intersect(
KNOWN_AUTHENTICATION_TYPES, payload['short_authentication_string']);
knownAuthentificationTypes, payload['short_authentication_string']);
if (possibleAuthenticationTypes.isEmpty) {
return false;
}
@ -700,7 +721,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
}
messageAuthenticationCode = payload['message_authentication_code'];
final possibleAuthenticationTypes = _intersect(
KNOWN_AUTHENTICATION_TYPES, payload['short_authentication_string']);
knownAuthentificationTypes, payload['short_authentication_string']);
if (possibleAuthenticationTypes.isEmpty) {
return false;
}

View file

@ -410,11 +410,9 @@ packages:
olm:
dependency: "direct main"
description:
path: "."
ref: "0c612a525511652a7760126b058de8c924fe8900"
resolved-ref: "0c612a525511652a7760126b058de8c924fe8900"
url: "https://gitlab.com/famedly/libraries/dart-olm.git"
source: git
path: "/home/sorunome/repos/famedly/dart-olm"
relative: false
source: path
version: "1.1.1"
package_config:
dependency: transitive