feat: Migrate to vodozemac

This commit is contained in:
Christian Kußowski 2025-05-28 11:18:24 +02:00
parent 6cf6171b35
commit 31a32b0145
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652
25 changed files with 359 additions and 306 deletions

View File

@ -18,6 +18,11 @@ jobs:
- uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46
with:
sdk: ${{ env.dart_version }}
- uses: famedly/backend-build-workflows/.github/actions/rust-prepare@main
with:
gitlab_user: ${{ secrets.GITLAB_USER }}
gitlab_pass: ${{ secrets.GITLAB_PASS }}
gitlab_ssh: ${{ secrets.CI_SSH_PRIVATE_KEY}}
- name: Run tests
run: |
export HOMESERVER_IMPLEMENTATION=${{matrix.homeserver}}
@ -27,6 +32,7 @@ jobs:
source scripts/integration-create-environment-variables.sh
scripts/integration-prepare-homeserver.sh
scripts/prepare.sh
scripts/prepare_vodozemac.sh
scripts/test_driver.sh
coverage_without_olm:
@ -54,7 +60,7 @@ jobs:
coverage:
#runs-on: arm-ubuntu-latest-16core
runs-on: ubuntu-latest
timeout-minutes: 5
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- run: cat .github/workflows/versions.env >> $GITHUB_ENV
@ -62,9 +68,15 @@ jobs:
with:
sdk: ${{ env.dart_version }}
#architecture: "arm64"
- uses: famedly/backend-build-workflows/.github/actions/rust-prepare@main
with:
gitlab_user: ${{ secrets.GITLAB_USER }}
gitlab_pass: ${{ secrets.GITLAB_PASS }}
gitlab_ssh: ${{ secrets.CI_SSH_PRIVATE_KEY}}
- name: Run tests
run: |
sudo apt-get update && sudo apt-get install --no-install-recommends --no-install-suggests -y lcov libsqlite3-0 libsqlite3-dev libolm3 libssl3
./scripts/prepare_vodozemac.sh
./scripts/test.sh
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}

1
.gitignore vendored
View File

@ -18,6 +18,7 @@ coverage/
coverage_badge.svg
coverage.xml
TEST-report.*
rust
# IntelliJ related
*.iml

View File

@ -16,9 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import 'dart:convert';
import 'dart:typed_data';
import 'package:olm/olm.dart' as olm;
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/encryption.dart';
import 'package:matrix/encryption/ssss.dart';
@ -31,26 +32,22 @@ class CrossSigning {
CrossSigning(this.encryption) {
encryption.ssss.setValidator(EventTypes.CrossSigningSelfSigning,
(String secret) async {
final keyObj = olm.PkSigning();
try {
return keyObj.init_with_seed(base64decodeUnpadded(secret)) ==
final keyObj = vod.PkSigning.fromSecretKey(secret);
return keyObj.publicKey.toBase64() ==
client.userDeviceKeys[client.userID]!.selfSigningKey!.ed25519Key;
} catch (_) {
return false;
} finally {
keyObj.free();
}
});
encryption.ssss.setValidator(EventTypes.CrossSigningUserSigning,
(String secret) async {
final keyObj = olm.PkSigning();
try {
return keyObj.init_with_seed(base64decodeUnpadded(secret)) ==
final keyObj = vod.PkSigning.fromSecretKey(secret);
return keyObj.publicKey.toBase64() ==
client.userDeviceKeys[client.userID]!.userSigningKey!.ed25519Key;
} catch (_) {
return false;
} finally {
keyObj.free();
}
});
}
@ -92,14 +89,13 @@ class CrossSigning {
final masterPrivateKey = base64decodeUnpadded(
await handle.getStored(EventTypes.CrossSigningMasterKey),
);
final keyObj = olm.PkSigning();
String? masterPubkey;
try {
masterPubkey = keyObj.init_with_seed(masterPrivateKey);
masterPubkey = vod.PkSigning.fromSecretKey(base64Encode(masterPrivateKey))
.publicKey
.toBase64();
} catch (e) {
masterPubkey = null;
} finally {
keyObj.free();
}
final userDeviceKeys =
client.userDeviceKeys[client.userID]?.deviceKeys[client.deviceID];
@ -210,12 +206,7 @@ class CrossSigning {
}
String _sign(String canonicalJson, Uint8List key) {
final keyObj = olm.PkSigning();
try {
keyObj.init_with_seed(key);
return keyObj.sign(canonicalJson);
} finally {
keyObj.free();
}
final keyObj = vod.PkSigning.fromSecretKey(base64Encode(key));
return keyObj.sign(canonicalJson).toBase64();
}
}

View File

@ -21,6 +21,7 @@ import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:olm/olm.dart' as olm;
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/encryption.dart';
import 'package:matrix/encryption/utils/base64_unpadded.dart';
@ -45,19 +46,18 @@ class KeyManager {
KeyManager(this.encryption) {
encryption.ssss.setValidator(megolmKey, (String secret) async {
final keyObj = olm.PkDecryption();
try {
final keyObj = vod.PkDecryption.fromSecretKey(
vod.Curve25519PublicKey.fromBase64(secret),
);
final info = await getRoomKeysBackupInfo(false);
if (info.algorithm !=
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2) {
return false;
}
return keyObj.init_with_private_key(base64decodeUnpadded(secret)) ==
info.authData['public_key'];
return keyObj.publicKey == info.authData['public_key'];
} catch (_) {
return false;
} finally {
keyObj.free();
}
});
encryption.ssss.setCacheCallback(megolmKey, (String secret) {
@ -669,54 +669,57 @@ class KeyManager {
}
final privateKey =
base64decodeUnpadded((await encryption.ssss.getCached(megolmKey))!);
final decryption = olm.PkDecryption();
final info = await getRoomKeysBackupInfo();
String backupPubKey;
try {
backupPubKey = decryption.init_with_private_key(privateKey);
if (info.algorithm != BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 ||
info.authData['public_key'] != backupPubKey) {
return;
}
for (final roomEntry in keys.rooms.entries) {
final roomId = roomEntry.key;
for (final sessionEntry in roomEntry.value.sessions.entries) {
final sessionId = sessionEntry.key;
final session = sessionEntry.value;
final sessionData = session.sessionData;
Map<String, Object?>? decrypted;
try {
decrypted = json.decode(
decryption.decrypt(
sessionData['ephemeral'] as String,
sessionData['mac'] as String,
sessionData['ciphertext'] as String,
final decryption = vod.PkDecryption.fromSecretKey(
vod.Curve25519PublicKey.fromBytes(privateKey),
);
backupPubKey = decryption.publicKey;
if (info.algorithm != BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 ||
info.authData['public_key'] != backupPubKey) {
return;
}
for (final roomEntry in keys.rooms.entries) {
final roomId = roomEntry.key;
for (final sessionEntry in roomEntry.value.sessions.entries) {
final sessionId = sessionEntry.key;
final session = sessionEntry.value;
final sessionData = session.sessionData;
Map<String, Object?>? decrypted;
try {
decrypted = json.decode(
decryption.decrypt(
vod.PkMessage(
base64decodeUnpadded(sessionData['ciphertext'] as String),
base64decodeUnpadded(sessionData['mac'] as String),
vod.Curve25519PublicKey.fromBase64(
sessionData['ephemeral'] as String,
),
),
);
} catch (e, s) {
Logs().e('[LibOlm] Error decrypting room key', e, s);
}
final senderKey = decrypted?.tryGet<String>('sender_key');
if (decrypted != null && senderKey != null) {
decrypted['session_id'] = sessionId;
decrypted['room_id'] = roomId;
await setInboundGroupSession(
roomId,
sessionId,
senderKey,
decrypted,
forwarded: true,
senderClaimedKeys:
decrypted.tryGetMap<String, String>('sender_claimed_keys') ??
<String, String>{},
uploaded: true,
);
}
),
);
} catch (e, s) {
Logs().e('[LibOlm] Error decrypting room key', e, s);
}
final senderKey = decrypted?.tryGet<String>('sender_key');
if (decrypted != null && senderKey != null) {
decrypted['session_id'] = sessionId;
decrypted['room_id'] = roomId;
await setInboundGroupSession(
roomId,
sessionId,
senderKey,
decrypted,
forwarded: true,
senderClaimedKeys:
decrypted.tryGetMap<String, String>('sender_claimed_keys') ??
<String, String>{},
uploaded: true,
);
}
}
} finally {
decryption.free();
}
}
@ -875,57 +878,56 @@ class KeyManager {
final privateKey =
base64decodeUnpadded((await encryption.ssss.getCached(megolmKey))!);
// decryption is needed to calculate the public key and thus see if the claimed information is in fact valid
final decryption = olm.PkDecryption();
final info = await getRoomKeysBackupInfo(false);
String backupPubKey;
try {
backupPubKey = decryption.init_with_private_key(privateKey);
if (info.algorithm !=
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 ||
info.authData['public_key'] != backupPubKey) {
decryption.free();
return;
}
final args = GenerateUploadKeysArgs(
pubkey: backupPubKey,
dbSessions: <DbInboundGroupSessionBundle>[],
userId: userID,
final decryption = vod.PkDecryption.fromSecretKey(
vod.Curve25519PublicKey.fromBytes(privateKey),
);
backupPubKey = decryption.publicKey;
if (info.algorithm !=
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 ||
info.authData['public_key'] != backupPubKey) {
return;
}
final args = GenerateUploadKeysArgs(
pubkey: backupPubKey,
dbSessions: <DbInboundGroupSessionBundle>[],
userId: userID,
);
// we need to calculate verified beforehand, as else we pass a closure to an isolate
// with 500 keys they do, however, noticably block the UI, which is why we give brief async suspentions in here
// so that the event loop can progress
var i = 0;
for (final dbSession in dbSessions) {
final device =
client.getUserDeviceKeysByCurve25519Key(dbSession.senderKey);
args.dbSessions.add(
DbInboundGroupSessionBundle(
dbSession: dbSession,
verified: device?.verified ?? false,
),
);
// we need to calculate verified beforehand, as else we pass a closure to an isolate
// with 500 keys they do, however, noticably block the UI, which is why we give brief async suspentions in here
// so that the event loop can progress
var i = 0;
for (final dbSession in dbSessions) {
final device =
client.getUserDeviceKeysByCurve25519Key(dbSession.senderKey);
args.dbSessions.add(
DbInboundGroupSessionBundle(
dbSession: dbSession,
verified: device?.verified ?? false,
),
);
i++;
if (i > 10) {
await Future.delayed(Duration(milliseconds: 1));
i = 0;
}
i++;
if (i > 10) {
await Future.delayed(Duration(milliseconds: 1));
i = 0;
}
final roomKeys =
await client.nativeImplementations.generateUploadKeys(args);
Logs().i('[Key Manager] Uploading ${dbSessions.length} room keys...');
// upload the payload...
await client.putRoomKeys(info.version, roomKeys);
// and now finally mark all the keys as uploaded
// no need to optimze this, as we only run it so seldomly and almost never with many keys at once
for (final dbSession in dbSessions) {
await database.markInboundGroupSessionAsUploaded(
dbSession.roomId,
dbSession.sessionId,
);
}
} finally {
decryption.free();
}
final roomKeys =
await client.nativeImplementations.generateUploadKeys(args);
Logs().i('[Key Manager] Uploading ${dbSessions.length} room keys...');
// upload the payload...
await client.putRoomKeys(info.version, roomKeys);
// and now finally mark all the keys as uploaded
// no need to optimze this, as we only run it so seldomly and almost never with many keys at once
for (final dbSession in dbSessions) {
await database.markInboundGroupSessionAsUploaded(
dbSession.roomId,
dbSession.sessionId,
);
}
} catch (e, s) {
Logs().e('[Key Manager] Error uploading room keys', e, s);
@ -1247,9 +1249,10 @@ class RoomKeyRequest extends ToDeviceEvent {
/// you would likely want to use [NativeImplementations] and
/// [Client.nativeImplementations] instead
RoomKeys generateUploadKeysImplementation(GenerateUploadKeysArgs args) {
final enc = olm.PkEncryption();
try {
enc.set_recipient_key(args.pubkey);
final enc = vod.PkEncryption.fromPublicKey(
vod.Curve25519PublicKey.fromBase64(args.pubkey),
);
// first we generate the payload to upload all the session keys in this chunk
final roomKeys = RoomKeys(rooms: {});
for (final dbSession in args.dbSessions) {
@ -1279,17 +1282,15 @@ RoomKeys generateUploadKeysImplementation(GenerateUploadKeysArgs args) {
forwardedCount: sess.forwardingCurve25519KeyChain.length,
isVerified: dbSession.verified, //device?.verified ?? false,
sessionData: {
'ephemeral': encrypted.ephemeral,
'ciphertext': encrypted.ciphertext,
'mac': encrypted.mac,
'ephemeral': encrypted.ephemeralKey.toBase64(),
'ciphertext': base64Encode(encrypted.ciphertext),
'mac': base64Encode(encrypted.mac),
},
);
}
enc.free();
return roomKeys;
} catch (e, s) {
Logs().e('[Key Manager] Error generating payload', e, s);
enc.free();
rethrow;
}
}

View File

@ -20,12 +20,11 @@ import 'dart:convert';
import 'dart:typed_data';
import 'package:canonical_json/canonical_json.dart';
import 'package:olm/olm.dart' as olm;
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/encryption.dart';
import 'package:matrix/encryption/key_manager.dart';
import 'package:matrix/encryption/ssss.dart';
import 'package:matrix/encryption/utils/base64_unpadded.dart';
import 'package:matrix/matrix.dart';
enum BootstrapState {
@ -371,106 +370,82 @@ class Bootstrap {
}
final userID = client.userID!;
try {
Uint8List masterSigningKey;
String masterSigningKey;
final secretsToStore = <String, String>{};
MatrixCrossSigningKey? masterKey;
MatrixCrossSigningKey? selfSigningKey;
MatrixCrossSigningKey? userSigningKey;
String? masterPub;
if (setupMasterKey) {
final master = olm.PkSigning();
try {
masterSigningKey = master.generate_seed();
masterPub = master.init_with_seed(masterSigningKey);
final json = <String, dynamic>{
'user_id': userID,
'usage': ['master'],
'keys': <String, dynamic>{
'ed25519:$masterPub': masterPub,
},
};
masterKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[EventTypes.CrossSigningMasterKey] =
base64.encode(masterSigningKey);
} finally {
master.free();
}
final master = vod.PkSigning();
masterSigningKey = master.secretKey;
masterPub = master.publicKey.toBase64();
final json = <String, dynamic>{
'user_id': userID,
'usage': ['master'],
'keys': <String, dynamic>{
'ed25519:$masterPub': masterPub,
},
};
masterKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[EventTypes.CrossSigningMasterKey] = masterSigningKey;
} else {
Logs().v('Get stored key...');
masterSigningKey = base64decodeUnpadded(
await newSsssKey?.getStored(EventTypes.CrossSigningMasterKey) ?? '',
);
masterSigningKey =
await newSsssKey?.getStored(EventTypes.CrossSigningMasterKey) ?? '';
if (masterSigningKey.isEmpty) {
// no master signing key :(
throw BootstrapBadStateException('No master key');
}
final master = olm.PkSigning();
try {
masterPub = master.init_with_seed(masterSigningKey);
} finally {
master.free();
}
final master = vod.PkSigning.fromSecretKey(masterSigningKey);
masterPub = master.publicKey.toBase64();
}
String? sign(Map<String, dynamic> object) {
final keyObj = olm.PkSigning();
try {
keyObj.init_with_seed(masterSigningKey);
return keyObj
.sign(String.fromCharCodes(canonicalJson.encode(object)));
} finally {
keyObj.free();
}
final keyObj = vod.PkSigning.fromSecretKey(masterSigningKey);
return keyObj
.sign(String.fromCharCodes(canonicalJson.encode(object)))
.toBase64();
}
if (setupSelfSigningKey) {
final selfSigning = olm.PkSigning();
try {
final selfSigningPriv = selfSigning.generate_seed();
final selfSigningPub = selfSigning.init_with_seed(selfSigningPriv);
final json = <String, dynamic>{
'user_id': userID,
'usage': ['self_signing'],
'keys': <String, dynamic>{
'ed25519:$selfSigningPub': selfSigningPub,
},
};
final signature = sign(json);
json['signatures'] = <String, dynamic>{
userID: <String, dynamic>{
'ed25519:$masterPub': signature,
},
};
selfSigningKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[EventTypes.CrossSigningSelfSigning] =
base64.encode(selfSigningPriv);
} finally {
selfSigning.free();
}
final selfSigning = vod.PkSigning();
final selfSigningPriv = selfSigning.secretKey;
final selfSigningPub = selfSigning.publicKey.toBase64();
final json = <String, dynamic>{
'user_id': userID,
'usage': ['self_signing'],
'keys': <String, dynamic>{
'ed25519:$selfSigningPub': selfSigningPub,
},
};
final signature = sign(json);
json['signatures'] = <String, dynamic>{
userID: <String, dynamic>{
'ed25519:$masterPub': signature,
},
};
selfSigningKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[EventTypes.CrossSigningSelfSigning] = selfSigningPriv;
}
if (setupUserSigningKey) {
final userSigning = olm.PkSigning();
try {
final userSigningPriv = userSigning.generate_seed();
final userSigningPub = userSigning.init_with_seed(userSigningPriv);
final json = <String, dynamic>{
'user_id': userID,
'usage': ['user_signing'],
'keys': <String, dynamic>{
'ed25519:$userSigningPub': userSigningPub,
},
};
final signature = sign(json);
json['signatures'] = <String, dynamic>{
userID: <String, dynamic>{
'ed25519:$masterPub': signature,
},
};
userSigningKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[EventTypes.CrossSigningUserSigning] =
base64.encode(userSigningPriv);
} finally {
userSigning.free();
}
final userSigning = vod.PkSigning();
final userSigningPriv = userSigning.secretKey;
final userSigningPub = userSigning.publicKey.toBase64();
final json = <String, dynamic>{
'user_id': userID,
'usage': ['user_signing'],
'keys': <String, dynamic>{
'ed25519:$userSigningPub': userSigningPub,
},
};
final signature = sign(json);
json['signatures'] = <String, dynamic>{
userID: <String, dynamic>{
'ed25519:$masterPub': signature,
},
};
userSigningKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[EventTypes.CrossSigningUserSigning] = userSigningPriv;
}
// upload the keys!
state = BootstrapState.loading;
@ -561,15 +536,13 @@ class Bootstrap {
return;
}
try {
final keyObj = olm.PkDecryption();
final keyObj = vod.PkDecryption();
String pubKey;
Uint8List privKey;
try {
pubKey = keyObj.generate_key();
privKey = keyObj.get_private_key();
} finally {
keyObj.free();
}
pubKey = keyObj.publicKey;
privKey = keyObj.privateKey;
Logs().v('Create the new backup version...');
await client.postRoomKeysVersion(
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2,

View File

@ -17,7 +17,7 @@
*/
import 'package:canonical_json/canonical_json.dart';
import 'package:olm/olm.dart' as olm;
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
@ -37,15 +37,15 @@ extension JsonSignatureCheckExtension on Map<String, dynamic> {
final canonical = canonicalJson.encode(this);
final message = String.fromCharCodes(canonical);
var isValid = false;
final olmutil = olm.Utility();
try {
olmutil.ed25519_verify(key, message, signature);
vod.Ed25519PublicKey.fromBase64(key).verify(
message: message,
signature: vod.Ed25519Signature.fromBase64(signature),
);
isValid = true;
} catch (e, s) {
isValid = false;
Logs().w('[LibOlm] Signature check failed', e, s);
} finally {
olmutil.free();
}
return isValid;
}

View File

@ -21,8 +21,9 @@ import 'dart:convert';
import 'dart:typed_data';
import 'package:canonical_json/canonical_json.dart';
import 'package:olm/olm.dart' as olm;
import 'package:crypto/crypto.dart' as crypto;
import 'package:typed_data/typed_data.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/encryption.dart';
import 'package:matrix/encryption/utils/base64_unpadded.dart';
@ -1258,12 +1259,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
String? commitment;
late String theirPublicKey;
Map<String, dynamic>? macPayload;
olm.SAS? sas;
@override
void dispose() {
sas?.free();
}
vod.Sas? sas;
vod.EstablishedSas? establishedSas;
List<String> get knownAuthentificationTypes {
final types = <String>[];
@ -1322,7 +1319,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
await _sendKey();
} else {
// we already sent our key, time to verify the commitment being valid
if (!_validateCommitment()) {
if (await _validateCommitment() == false) {
await request.cancel('m.mismatched_commitment');
return;
}
@ -1415,8 +1412,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
}
Future<void> _sendAccept() async {
final sas = this.sas = olm.SAS();
commitment = _makeCommitment(sas.get_pubkey(), startCanonicalJson);
final sas = this.sas = vod.Sas();
commitment = await _makeCommitment(sas.publicKey, startCanonicalJson);
await request.send(EventTypes.KeyVerificationAccept, {
'method': type,
'key_agreement_protocol': keyAgreementProtocol,
@ -1451,31 +1448,31 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
}
authenticationTypes = possibleAuthenticationTypes;
commitment = payload['commitment'];
sas = olm.SAS();
sas = vod.Sas();
return true;
}
Future<void> _sendKey() async {
await request.send('m.key.verification.key', {
'key': sas!.get_pubkey(),
'key': sas!.publicKey,
});
}
void _handleKey(Map<String, dynamic> payload) {
theirPublicKey = payload['key'];
sas!.set_their_key(payload['key']);
establishedSas = sas!.establishSasSecret(payload['key']);
}
bool _validateCommitment() {
final checkCommitment = _makeCommitment(theirPublicKey, startCanonicalJson);
Future<bool> _validateCommitment() async {
final checkCommitment =
await _makeCommitment(theirPublicKey, startCanonicalJson);
return commitment == checkCommitment;
}
Uint8List makeSas(int bytes) {
var sasInfo = '';
if (keyAgreementProtocol == 'curve25519-hkdf-sha256') {
final ourInfo =
'${client.userID}|${client.deviceID}|${sas!.get_pubkey()}|';
final ourInfo = '${client.userID}|${client.deviceID}|${sas!.publicKey}|';
final theirInfo =
'${request.userId}|${request.deviceId}|$theirPublicKey|';
sasInfo =
@ -1488,7 +1485,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
} else {
throw Exception('Unknown key agreement protocol');
}
return sas!.generate_bytes(sasInfo, bytes);
return establishedSas!.generateBytes(sasInfo, bytes);
}
Future<void> _sendMac() async {
@ -1554,21 +1551,20 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
});
}
String _makeCommitment(String pubKey, String canonicalJson) {
Future<String> _makeCommitment(String pubKey, String canonicalJson) async {
if (hash == 'sha256') {
final olmutil = olm.Utility();
final ret = olmutil.sha256(pubKey + canonicalJson);
olmutil.free();
return ret;
final bytes = utf8.encode(pubKey + canonicalJson);
final digest = crypto.sha256.convert(bytes);
return base64.encode(digest.bytes);
}
throw Exception('Unknown hash method');
}
String _calculateMac(String input, String info) {
if (messageAuthenticationCode == 'hkdf-hmac-sha256.v2') {
return sas!.calculate_mac_fixed_base64(input, info);
return establishedSas!.calculateMac(input, info);
} else if (messageAuthenticationCode == 'hkdf-hmac-sha256') {
return sas!.calculate_mac(input, info);
return establishedSas!.calculateMacDeprecated(input, info);
} else {
throw Exception('Unknown message authentification code');
}

View File

@ -20,7 +20,7 @@ import 'dart:convert';
import 'package:canonical_json/canonical_json.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:olm/olm.dart' as olm;
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart';
@ -240,24 +240,17 @@ abstract class SignableKey extends MatrixSignableKey {
String signature, {
bool isSignatureWithoutLibolmValid = false,
}) {
olm.Utility olmutil;
try {
olmutil = olm.Utility();
} catch (e) {
// if no libolm is present we land in this catch block, and return the default
// set if no libolm is there. Some signatures should be assumed-valid while others
// should be assumed-invalid
return isSignatureWithoutLibolmValid;
}
var valid = false;
try {
olmutil.ed25519_verify(pubKey, signingContent, signature);
vod.Ed25519PublicKey.fromBase64(pubKey).verify(
message: signingContent,
signature: vod.Ed25519Signature.fromBase64(signature),
);
valid = true;
} catch (_) {
} catch (e) {
Logs().d('Invalid Ed25519 signature', e);
// bad signature
valid = false;
} finally {
olmutil.free();
}
return valid;
}

View File

@ -31,6 +31,11 @@ dependencies:
sqflite_common: ^2.4.5
sqlite3: ^2.1.0
typed_data: ^1.3.2
vodozemac:
git:
url: https://github.com/famedly/dart-vodozemac.git
path: dart
ref: main
webrtc_interface: ^1.2.0
dev_dependencies:

8
scripts/prepare_vodozemac.sh Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
git clone https://github.com/famedly/dart-vodozemac.git
mv ./dart-vodozemac/rust ./
rm -rf dart-vodozemac
cd ./rust
cargo build
cd ..

View File

@ -26,6 +26,7 @@ import 'package:collection/collection.dart';
import 'package:olm/olm.dart' as olm;
import 'package:path/path.dart' show join;
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import 'package:matrix/src/utils/client_init_exception.dart';
@ -67,11 +68,15 @@ void main() {
group('client mem', tags: 'olm', () {
late Client matrix;
Logs().level = Level.error;
Future? vodInit;
/// Check if all Elements get created
setUp(() async {
vodInit ??= vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await vodInit;
matrix = await getClient();
});

View File

@ -19,18 +19,26 @@
import 'dart:convert';
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import './fake_client.dart';
void main() {
void main() async {
/// All Tests related to device keys
group('Device keys', tags: 'olm', () {
Logs().level = Level.error;
late Client client;
Future? vodInit;
test('setupClient', () async {
vodInit ??= vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await vodInit;
client = await getClient();
await client.abortSync();
});

View File

@ -21,6 +21,7 @@ import 'dart:convert';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart';
@ -35,6 +36,10 @@ void main() {
late String origKeyId;
setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init();
olm.get_library_version();
client = await getClient();
@ -75,20 +80,16 @@ void main() {
// test all the x-signing keys match up
for (final keyType in {'master', 'user_signing', 'self_signing'}) {
final privateKey = base64
.decode(await defaultKey.getStored('m.cross_signing.$keyType'));
final keyObj = olm.PkSigning();
try {
final pubKey = keyObj.init_with_seed(privateKey);
expect(
pubKey,
client.userDeviceKeys[client.userID]
?.getCrossSigningKey(keyType)
?.publicKey,
);
} finally {
keyObj.free();
}
final privateKey =
await defaultKey.getStored('m.cross_signing.$keyType');
final keyObj = vod.PkSigning.fromSecretKey(privateKey);
final pubKey = keyObj.publicKey.toBase64();
expect(
pubKey,
client.userDeviceKeys[client.userID]
?.getCrossSigningKey(keyType)
?.publicKey,
);
}
await defaultKey.store('foxes', 'floof');
@ -133,20 +134,16 @@ void main() {
// test all the x-signing keys match up
for (final keyType in {'master', 'user_signing', 'self_signing'}) {
final privateKey = base64
.decode(await defaultKey.getStored('m.cross_signing.$keyType'));
final keyObj = olm.PkSigning();
try {
final pubKey = keyObj.init_with_seed(privateKey);
expect(
pubKey,
client.userDeviceKeys[client.userID]
?.getCrossSigningKey(keyType)
?.publicKey,
);
} finally {
keyObj.free();
}
final privateKey =
await defaultKey.getStored('m.cross_signing.$keyType');
final keyObj = vod.PkSigning.fromSecretKey(privateKey);
final pubKey = keyObj.publicKey.toBase64();
expect(
pubKey,
client.userDeviceKeys[client.userID]
?.getCrossSigningKey(keyType)
?.publicKey,
);
}
expect(await defaultKey.getStored('foxes'), 'floof');
@ -192,20 +189,16 @@ void main() {
// test all the x-signing keys match up
for (final keyType in {'master', 'user_signing', 'self_signing'}) {
final privateKey = base64
.decode(await defaultKey.getStored('m.cross_signing.$keyType'));
final keyObj = olm.PkSigning();
try {
final pubKey = keyObj.init_with_seed(privateKey);
expect(
pubKey,
client.userDeviceKeys[client.userID]
?.getCrossSigningKey(keyType)
?.publicKey,
);
} finally {
keyObj.free();
}
final privateKey =
await defaultKey.getStored('m.cross_signing.$keyType');
final keyObj = vod.PkSigning.fromSecretKey(privateKey);
final pubKey = keyObj.publicKey.toBase64();
expect(
pubKey,
client.userDeviceKeys[client.userID]
?.getCrossSigningKey(keyType)
?.publicKey,
);
}
expect(await defaultKey.getStored('foxes'), 'floof');

View File

@ -20,6 +20,7 @@ import 'dart:convert';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import '../fake_client.dart';
@ -31,6 +32,10 @@ void main() {
late Client client;
setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init();
olm.get_library_version();
client = await getClient();

View File

@ -18,6 +18,7 @@
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import '../fake_client.dart';
@ -33,6 +34,10 @@ void main() {
final now = DateTime.now();
setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init();
olm.get_library_version();
client = await getClient();

View File

@ -18,6 +18,7 @@
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import '../fake_client.dart';
@ -41,6 +42,10 @@ void main() async {
late Map<String, dynamic> payload;
setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init();
olm.get_library_version();
client = await getClient();

View File

@ -20,6 +20,7 @@ import 'dart:convert';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import '../fake_client.dart';
@ -30,6 +31,10 @@ void main() {
late Client client;
setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init();
olm.get_library_version();
client = await getClient();

View File

@ -20,6 +20,7 @@ import 'dart:convert';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import '../fake_client.dart';
@ -42,6 +43,10 @@ void main() {
Logs().level = Level.error;
setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init();
olm.get_library_version();
});

View File

@ -22,6 +22,7 @@ import 'dart:typed_data';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart';
@ -58,6 +59,10 @@ void main() async {
late Client client2;
setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init();
olm.get_library_version();
});

View File

@ -20,6 +20,7 @@ import 'dart:convert';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/utils/json_signature_check_extension.dart';
import 'package:matrix/matrix.dart';
@ -32,6 +33,10 @@ void main() {
late Client client;
setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init();
olm.get_library_version();
client = await getClient();

View File

@ -20,6 +20,7 @@ import 'dart:convert';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import '../fake_client.dart';
@ -35,6 +36,10 @@ void main() {
final senderKey = 'JBG7ZaPn54OBC7TuIEiylW3BZ+7WcGQhFBPB9pogbAg';
setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init();
olm.get_library_version();
client = await getClient();

View File

@ -20,6 +20,7 @@ import 'dart:async';
import 'dart:typed_data';
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart';
@ -69,7 +70,14 @@ void main() async {
late Client client1;
late Client client2;
Future? vodInit;
setUp(() async {
vodInit ??= vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await vodInit;
client1 = await getClient();
client2 = await getOtherClient();

View File

@ -22,6 +22,7 @@ import 'dart:typed_data';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart';
@ -54,6 +55,10 @@ void main() {
late Client client;
setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init();
olm.get_library_version();
client = await getClient();

View File

@ -22,6 +22,7 @@ import 'dart:math';
import 'dart:typed_data';
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import 'fake_client.dart';
@ -671,12 +672,20 @@ void main() {
expect(fetchedParticipants.length, newParticipants.length);
});
test('calcEncryptionHealthState', () async {
expect(
await room.calcEncryptionHealthState(),
EncryptionHealthState.unverifiedDevices,
);
});
test(
'calcEncryptionHealthState',
() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
expect(
await room.calcEncryptionHealthState(),
EncryptionHealthState.allVerified,
);
},
tags: 'olm',
);
test('getEventByID', () async {
final event = await room.getEventById('1234');

View File

@ -20,6 +20,7 @@ import 'dart:io';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import '../test/fake_database.dart';
@ -39,6 +40,10 @@ void main() => group(
Client? testClientA, testClientB;
try {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init();
olm.Account();
Logs().i('[LibOlm] Enabled');