Merge pull request #2100 from famedly/krille/vodozemac
feat: Migrate to vodozemac
This commit is contained in:
commit
a881249126
|
|
@ -18,15 +18,21 @@ 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}}
|
||||
export HOMESERVER="localhost:80"
|
||||
scripts/integration-server-${{matrix.homeserver}}.sh 2>&1 > /dev/null &
|
||||
sudo apt-get update && sudo apt-get install --no-install-recommends --no-install-suggests -y libolm3 libssl3 sqlite3 libsqlite3-dev
|
||||
sudo apt-get update && sudo apt-get install --no-install-recommends --no-install-suggests -y libssl3 sqlite3 libsqlite3-dev
|
||||
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
|
||||
sudo apt-get update && sudo apt-get install --no-install-recommends --no-install-suggests -y lcov libsqlite3-0 libsqlite3-dev libssl3
|
||||
./scripts/prepare_vodozemac.sh
|
||||
./scripts/test.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ coverage/
|
|||
coverage_badge.svg
|
||||
coverage.xml
|
||||
TEST-report.*
|
||||
rust
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
|
|
|
|||
|
|
@ -4,15 +4,17 @@ Matrix (matrix.org) SDK written in dart.
|
|||
|
||||
## Native libraries
|
||||
|
||||
For E2EE, libolm must be provided.
|
||||
For E2EE, vodozemac must be provided.
|
||||
|
||||
Additionally, OpenSSL (libcrypto) must be provided on native platforms for E2EE.
|
||||
|
||||
For flutter apps you can easily import it with the [flutter_olm](https://pub.dev/packages/flutter_olm) and the [flutter_openssl_crypto](https://pub.dev/packages/flutter_openssl_crypto) packages.
|
||||
For flutter apps you can easily import it with the [flutter_vodozemac](https://pub.dev/packages/flutter_vodozemac) and the [flutter_openssl_crypto](https://pub.dev/packages/flutter_openssl_crypto) packages.
|
||||
|
||||
```sh
|
||||
flutter pub add matrix
|
||||
flutter pub add flutter_olm
|
||||
|
||||
# Optional: For end to end encryption:
|
||||
flutter pub add flutter_vodozemac
|
||||
flutter pub add flutter_openssl_crypto
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
To enable end to end encryption you need to setup [Vodozemac](https://pub.dev/packages/vodozemac). For this you need Rust installed locally: [rust-lang.org/tools/install](https://www.rust-lang.org/tools/install)
|
||||
|
||||
For Flutter you can use [flutter_vodozemac](https://pub.dev/packages/flutter_vodozemac).
|
||||
|
||||
```sh
|
||||
flutter pub add flutter_vodozemac
|
||||
```
|
||||
|
||||
You also need [flutter_openssl_crypto](https://pub.dev/packages/flutter_openssl_crypto).
|
||||
|
||||
```sh
|
||||
flutter pub add flutter_openssl_crypto
|
||||
```
|
||||
|
||||
Now before you create your `Client`, init vodozemac:
|
||||
|
||||
```dart
|
||||
import 'package:flutter_vodozemac/flutter_vodozemac' as vod;
|
||||
|
||||
// ...
|
||||
|
||||
await vod.init();
|
||||
|
||||
final client = Client(/*...*/);
|
||||
```
|
||||
|
||||
This should work on Android, iOS, macOS, Linux and Windows.
|
||||
|
||||
For web you need to compile vodozemac to wasm. [Please refer to the Vodozemac bindings documentation](https://pub.dev/packages/vodozemac#build-for-web).
|
||||
|
|
@ -6,10 +6,12 @@ In your `pubspec.yaml` file add the following dependencies:
|
|||
|
||||
```yaml
|
||||
matrix: <latest-version>
|
||||
# If you plan to use the SDK in a Flutter application on IO:
|
||||
# (Optional) If you plan to use the SDK in a Flutter application on IO
|
||||
# you need sqflite or sqflite_ffi:
|
||||
sqflite: <latest-version>
|
||||
# For end to end encryption:
|
||||
flutter_olm: <latest-version>
|
||||
# (Optional) For end to end encryption, please head on the
|
||||
# encryption guide and add these dependencies:
|
||||
flutter_vodozemac: <latest-version>
|
||||
flutter_openssl_crypto: <latest-version>
|
||||
```
|
||||
|
||||
|
|
|
|||
25
doc/web.md
25
doc/web.md
|
|
@ -1,25 +0,0 @@
|
|||
To use end to end encryption in web you have to download the olm javascript/wasm library:
|
||||
|
||||
```sh
|
||||
#!/bin/sh -ve
|
||||
rm -r assets/js/package
|
||||
|
||||
OLM_VERSION=$(cat pubspec.yaml | yq .dependencies.flutter_olm)
|
||||
DOWNLOAD_PATH="https://github.com/famedly/olm/releases/download/v$OLM_VERSION/olm.zip"
|
||||
|
||||
curl -L $DOWNLOAD_PATH > olm.zip
|
||||
unzip olm.zip
|
||||
rm olm.zip
|
||||
```
|
||||
|
||||
...and import it in your `index.html`:
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
...
|
||||
<script src="path/to/assets/olm.js"></script>
|
||||
</head>
|
||||
...
|
||||
</html>
|
||||
```
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
|
||||
import 'package:matrix/encryption/cross_signing.dart';
|
||||
import 'package:matrix/encryption/key_manager.dart';
|
||||
import 'package:matrix/encryption/key_verification_manager.dart';
|
||||
|
|
@ -87,18 +85,6 @@ class Encryption {
|
|||
if (!isDehydratedDevice) keyManager.startAutoUploadKeys();
|
||||
}
|
||||
|
||||
bool isMinOlmVersion(int major, int minor, int patch) {
|
||||
try {
|
||||
final version = olm.get_library_version();
|
||||
return version[0] > major ||
|
||||
(version[0] == major &&
|
||||
(version[1] > minor ||
|
||||
(version[1] == minor && version[2] >= patch)));
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Bootstrap bootstrap({void Function(Bootstrap)? onUpdate}) => Bootstrap(
|
||||
encryption: this,
|
||||
onUpdate: onUpdate,
|
||||
|
|
@ -178,7 +164,7 @@ class Encryption {
|
|||
return await olmManager.decryptToDeviceEvent(event);
|
||||
} catch (e, s) {
|
||||
Logs().w(
|
||||
'[LibOlm] Could not decrypt to device event from ${event.sender} with content: ${event.content}',
|
||||
'[Vodozemac] Could not decrypt to device event from ${event.sender} with content: ${event.content}',
|
||||
e,
|
||||
s,
|
||||
);
|
||||
|
|
@ -227,7 +213,7 @@ class Encryption {
|
|||
canRequestSession = false;
|
||||
|
||||
// we can't have the key be an int, else json-serializing will fail, thus we need it to be a string
|
||||
final messageIndexKey = 'key-${decryptResult.message_index}';
|
||||
final messageIndexKey = 'key-${decryptResult.messageIndex}';
|
||||
final messageIndexValue =
|
||||
'${event.eventId}|${event.originServerTs.millisecondsSinceEpoch}';
|
||||
final haveIndex =
|
||||
|
|
@ -256,12 +242,13 @@ class Encryption {
|
|||
}
|
||||
decryptedPayload = json.decode(decryptResult.plaintext);
|
||||
} catch (exception) {
|
||||
Logs().d('Could not decrypt event', exception);
|
||||
// alright, if this was actually by our own outbound group session, we might as well clear it
|
||||
if (exception.toString() != DecryptException.unknownSession &&
|
||||
(keyManager
|
||||
.getOutboundGroupSession(event.room.id)
|
||||
?.outboundGroupSession
|
||||
?.session_id() ??
|
||||
?.sessionId ??
|
||||
'') ==
|
||||
content.sessionId) {
|
||||
runInRoot(
|
||||
|
|
@ -409,7 +396,7 @@ class Encryption {
|
|||
// they're deprecated. Just left here for compatibility
|
||||
'device_id': client.deviceID,
|
||||
'sender_key': identityKey,
|
||||
'session_id': sess.outboundGroupSession!.session_id(),
|
||||
'session_id': sess.outboundGroupSession!.sessionId,
|
||||
if (mRelatesTo != null) 'm.relates_to': mRelatesTo,
|
||||
};
|
||||
await keyManager.storeOutboundGroupSession(roomId, sess);
|
||||
|
|
|
|||
|
|
@ -20,11 +20,12 @@ import 'dart:async';
|
|||
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';
|
||||
import 'package:matrix/encryption/utils/outbound_group_session.dart';
|
||||
import 'package:matrix/encryption/utils/pickle_key.dart';
|
||||
import 'package:matrix/encryption/utils/session_key.dart';
|
||||
import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
|
||||
import 'package:matrix/matrix.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) {
|
||||
|
|
@ -118,17 +118,16 @@ class KeyManager {
|
|||
if (content['algorithm'] != AlgorithmTypes.megolmV1AesSha2) {
|
||||
return;
|
||||
}
|
||||
late olm.InboundGroupSession inboundGroupSession;
|
||||
late vod.InboundGroupSession inboundGroupSession;
|
||||
try {
|
||||
inboundGroupSession = olm.InboundGroupSession();
|
||||
if (forwarded) {
|
||||
inboundGroupSession.import_session(content['session_key']);
|
||||
inboundGroupSession =
|
||||
vod.InboundGroupSession.import(content['session_key']);
|
||||
} else {
|
||||
inboundGroupSession.create(content['session_key']);
|
||||
inboundGroupSession = vod.InboundGroupSession(content['session_key']);
|
||||
}
|
||||
} catch (e, s) {
|
||||
inboundGroupSession.free();
|
||||
Logs().e('[LibOlm] Could not create new InboundGroupSession', e, s);
|
||||
Logs().e('[Vodozemac] Could not create new InboundGroupSession', e, s);
|
||||
return Future.value();
|
||||
}
|
||||
final newSession = SessionKey(
|
||||
|
|
@ -142,19 +141,16 @@ class KeyManager {
|
|||
senderClaimedKeys: senderClaimedKeys_,
|
||||
allowedAtIndex: allowedAtIndex_,
|
||||
);
|
||||
final oldFirstIndex =
|
||||
oldSession?.inboundGroupSession?.first_known_index() ?? 0;
|
||||
final newFirstIndex = newSession.inboundGroupSession!.first_known_index();
|
||||
final oldFirstIndex = oldSession?.inboundGroupSession?.firstKnownIndex ?? 0;
|
||||
final newFirstIndex = newSession.inboundGroupSession!.firstKnownIndex;
|
||||
if (oldSession == null ||
|
||||
newFirstIndex < oldFirstIndex ||
|
||||
(oldFirstIndex == newFirstIndex &&
|
||||
newSession.forwardingCurve25519KeyChain.length <
|
||||
oldSession.forwardingCurve25519KeyChain.length)) {
|
||||
// use new session
|
||||
oldSession?.dispose();
|
||||
} else {
|
||||
// we are gonna keep our old session
|
||||
newSession.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -169,7 +165,7 @@ class KeyManager {
|
|||
.storeInboundGroupSession(
|
||||
roomId,
|
||||
sessionId,
|
||||
inboundGroupSession.pickle(userId),
|
||||
inboundGroupSession.toPickleEncrypted(userId.toPickleKey()),
|
||||
json.encode(content),
|
||||
json.encode({}),
|
||||
json.encode(allowedAtIndex_),
|
||||
|
|
@ -344,7 +340,7 @@ class KeyManager {
|
|||
|
||||
final inboundSess = await loadInboundGroupSession(
|
||||
room.id,
|
||||
sess.outboundGroupSession!.session_id(),
|
||||
sess.outboundGroupSession!.sessionId,
|
||||
);
|
||||
if (inboundSess == null) {
|
||||
Logs().w('No inbound megolm session found for outbound session!');
|
||||
|
|
@ -436,8 +432,8 @@ class KeyManager {
|
|||
final rawSession = <String, dynamic>{
|
||||
'algorithm': AlgorithmTypes.megolmV1AesSha2,
|
||||
'room_id': room.id,
|
||||
'session_id': sess.outboundGroupSession!.session_id(),
|
||||
'session_key': sess.outboundGroupSession!.session_key(),
|
||||
'session_id': sess.outboundGroupSession!.sessionId,
|
||||
'session_key': sess.outboundGroupSession!.sessionKey,
|
||||
};
|
||||
try {
|
||||
devicesToReceive.removeWhere((k) => !k.encryptToDevice);
|
||||
|
|
@ -449,16 +445,16 @@ class KeyManager {
|
|||
.containsKey(device.curve25519Key) ||
|
||||
inboundSess.allowedAtIndex[device.userId]![
|
||||
device.curve25519Key]! >
|
||||
sess.outboundGroupSession!.message_index()) {
|
||||
sess.outboundGroupSession!.messageIndex) {
|
||||
inboundSess
|
||||
.allowedAtIndex[device.userId]![device.curve25519Key!] =
|
||||
sess.outboundGroupSession!.message_index();
|
||||
sess.outboundGroupSession!.messageIndex;
|
||||
}
|
||||
}
|
||||
await client.database.updateInboundGroupSessionAllowedAtIndex(
|
||||
json.encode(inboundSess!.allowedAtIndex),
|
||||
room.id,
|
||||
sess.outboundGroupSession!.session_id(),
|
||||
sess.outboundGroupSession!.sessionId,
|
||||
);
|
||||
// send out the key
|
||||
await client.sendToDeviceEncryptedChunked(
|
||||
|
|
@ -469,7 +465,7 @@ class KeyManager {
|
|||
}
|
||||
} catch (e, s) {
|
||||
Logs().e(
|
||||
'[LibOlm] Unable to re-send the session key at later index to new devices',
|
||||
'[Vodozemac] Unable to re-send the session key at later index to new devices',
|
||||
e,
|
||||
s,
|
||||
);
|
||||
|
|
@ -477,7 +473,6 @@ class KeyManager {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
sess.dispose();
|
||||
_outboundGroupSessions.remove(roomId);
|
||||
await client.database.removeOutboundGroupSession(roomId);
|
||||
return true;
|
||||
|
|
@ -492,7 +487,7 @@ class KeyManager {
|
|||
if (userID == null) return;
|
||||
await client.database.storeOutboundGroupSession(
|
||||
roomId,
|
||||
sess.outboundGroupSession!.pickle(userID),
|
||||
sess.outboundGroupSession!.toPickleEncrypted(userID.toPickleKey()),
|
||||
json.encode(sess.devices),
|
||||
sess.creationTime.millisecondsSinceEpoch,
|
||||
);
|
||||
|
|
@ -553,19 +548,13 @@ class KeyManager {
|
|||
final deviceKeys = await room.getUserDeviceKeys();
|
||||
final deviceKeyIds = _getDeviceKeyIdMap(deviceKeys);
|
||||
deviceKeys.removeWhere((k) => !k.encryptToDevice);
|
||||
final outboundGroupSession = olm.OutboundGroupSession();
|
||||
try {
|
||||
outboundGroupSession.create();
|
||||
} catch (e, s) {
|
||||
outboundGroupSession.free();
|
||||
Logs().e('[LibOlm] Unable to create new outboundGroupSession', e, s);
|
||||
rethrow;
|
||||
}
|
||||
final outboundGroupSession = vod.GroupSession();
|
||||
|
||||
final rawSession = <String, dynamic>{
|
||||
'algorithm': AlgorithmTypes.megolmV1AesSha2,
|
||||
'room_id': room.id,
|
||||
'session_id': outboundGroupSession.session_id(),
|
||||
'session_key': outboundGroupSession.session_key(),
|
||||
'session_id': outboundGroupSession.sessionId,
|
||||
'session_key': outboundGroupSession.sessionKey,
|
||||
};
|
||||
final allowedAtIndex = <String, Map<String, int>>{};
|
||||
for (final device in deviceKeys) {
|
||||
|
|
@ -575,7 +564,7 @@ class KeyManager {
|
|||
}
|
||||
allowedAtIndex[device.userId] ??= <String, int>{};
|
||||
allowedAtIndex[device.userId]![device.curve25519Key!] =
|
||||
outboundGroupSession.message_index();
|
||||
outboundGroupSession.messageIndex;
|
||||
}
|
||||
await setInboundGroupSession(
|
||||
roomId,
|
||||
|
|
@ -600,11 +589,10 @@ class KeyManager {
|
|||
_outboundGroupSessions[roomId] = sess;
|
||||
} catch (e, s) {
|
||||
Logs().e(
|
||||
'[LibOlm] Unable to send the session key to the participating devices',
|
||||
'[Vodozemac] Unable to send the session key to the participating devices',
|
||||
e,
|
||||
s,
|
||||
);
|
||||
sess.dispose();
|
||||
rethrow;
|
||||
}
|
||||
return sess;
|
||||
|
|
@ -669,54 +657,55 @@ 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.fromBase64(
|
||||
ciphertext: sessionData['ciphertext'] as String,
|
||||
mac: sessionData['mac'] as String,
|
||||
ephemeralKey: 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('[Vodozemac] 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 +864,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);
|
||||
|
|
@ -1161,14 +1149,6 @@ class KeyManager {
|
|||
void dispose() {
|
||||
// ignore: discarded_futures
|
||||
_uploadKeysOnSync?.cancel();
|
||||
for (final sess in _outboundGroupSessions.values) {
|
||||
sess.dispose();
|
||||
}
|
||||
for (final entries in _inboundGroupSessions.values) {
|
||||
for (final sess in entries.values) {
|
||||
sess.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1231,8 +1211,8 @@ class RoomKeyRequest extends ToDeviceEvent {
|
|||
(session.forwardingCurve25519KeyChain.isEmpty
|
||||
? keyManager.encryption.fingerprintKey
|
||||
: null);
|
||||
message['session_key'] = session.inboundGroupSession!.export_session(
|
||||
index ?? session.inboundGroupSession!.first_known_index(),
|
||||
message['session_key'] = session.inboundGroupSession!.exportAt(
|
||||
index ?? session.inboundGroupSession!.firstKnownIndex,
|
||||
);
|
||||
// send the actual reply of the key back to the requester
|
||||
await keyManager.client.sendToDeviceEncrypted(
|
||||
|
|
@ -1247,9 +1227,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) {
|
||||
|
|
@ -1266,30 +1247,28 @@ RoomKeys generateUploadKeysImplementation(GenerateUploadKeysArgs args) {
|
|||
'forwarding_curve25519_key_chain': sess.forwardingCurve25519KeyChain,
|
||||
'sender_key': sess.senderKey,
|
||||
'sender_claimed_keys': sess.senderClaimedKeys,
|
||||
'session_key': sess.inboundGroupSession!
|
||||
.export_session(sess.inboundGroupSession!.first_known_index()),
|
||||
'session_key': sess.inboundGroupSession!.exportAtFirstKnownIndex(),
|
||||
};
|
||||
// encrypt the content
|
||||
final encrypted = enc.encrypt(json.encode(payload));
|
||||
// fetch the device, if available...
|
||||
//final device = args.client.getUserDeviceKeysByCurve25519Key(sess.senderKey);
|
||||
// aaaand finally add the session key to our payload
|
||||
final (ciphertext, mac, ephemeral) = encrypted.toBase64();
|
||||
roomKeyBackup.sessions[sess.sessionId] = KeyBackupData(
|
||||
firstMessageIndex: sess.inboundGroupSession!.first_known_index(),
|
||||
firstMessageIndex: sess.inboundGroupSession!.firstKnownIndex,
|
||||
forwardedCount: sess.forwardingCurve25519KeyChain.length,
|
||||
isVerified: dbSession.verified, //device?.verified ?? false,
|
||||
sessionData: {
|
||||
'ephemeral': encrypted.ephemeral,
|
||||
'ciphertext': encrypted.ciphertext,
|
||||
'mac': encrypted.mac,
|
||||
'ephemeral': ephemeral,
|
||||
'ciphertext': ciphertext,
|
||||
'mac': mac,
|
||||
},
|
||||
);
|
||||
}
|
||||
enc.free();
|
||||
return roomKeys;
|
||||
} catch (e, s) {
|
||||
Logs().e('[Key Manager] Error generating payload', e, s);
|
||||
enc.free();
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,12 @@ import 'dart:convert';
|
|||
import 'package:async/async.dart';
|
||||
import 'package:canonical_json/canonical_json.dart';
|
||||
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/json_signature_check_extension.dart';
|
||||
import 'package:matrix/encryption/utils/olm_session.dart';
|
||||
import 'package:matrix/encryption/utils/pickle_key.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:matrix/msc_extensions/msc_3814_dehydrated_devices/api.dart';
|
||||
import 'package:matrix/src/utils/run_benchmarked.dart';
|
||||
|
|
@ -34,20 +35,24 @@ import 'package:matrix/src/utils/run_in_root.dart';
|
|||
class OlmManager {
|
||||
final Encryption encryption;
|
||||
Client get client => encryption.client;
|
||||
olm.Account? _olmAccount;
|
||||
vod.Account? _olmAccount;
|
||||
String? ourDeviceId;
|
||||
|
||||
/// Returns the base64 encoded keys to store them in a store.
|
||||
/// This String should **never** leave the device!
|
||||
String? get pickledOlmAccount =>
|
||||
enabled ? _olmAccount!.pickle(client.userID!) : null;
|
||||
String? get pickledOlmAccount {
|
||||
return enabled
|
||||
? _olmAccount!.toPickleEncrypted(client.userID!.toPickleKey())
|
||||
: null;
|
||||
}
|
||||
|
||||
String? get fingerprintKey =>
|
||||
enabled ? json.decode(_olmAccount!.identity_keys())['ed25519'] : null;
|
||||
enabled ? _olmAccount!.identityKeys.ed25519.toBase64() : null;
|
||||
String? get identityKey =>
|
||||
enabled ? json.decode(_olmAccount!.identity_keys())['curve25519'] : null;
|
||||
enabled ? _olmAccount!.identityKeys.curve25519.toBase64() : null;
|
||||
|
||||
String? pickleOlmAccountWithKey(String key) =>
|
||||
enabled ? _olmAccount!.pickle(key) : null;
|
||||
enabled ? _olmAccount!.toPickleEncrypted(key.toPickleKey()) : null;
|
||||
|
||||
bool get enabled => _olmAccount != null;
|
||||
|
||||
|
|
@ -66,33 +71,31 @@ class OlmManager {
|
|||
}) async {
|
||||
ourDeviceId = deviceId;
|
||||
if (olmAccount == null) {
|
||||
try {
|
||||
await olm.init();
|
||||
_olmAccount = olm.Account();
|
||||
_olmAccount!.create();
|
||||
if (!await uploadKeys(
|
||||
uploadDeviceKeys: true,
|
||||
updateDatabase: false,
|
||||
dehydratedDeviceAlgorithm: dehydratedDeviceAlgorithm,
|
||||
dehydratedDevicePickleKey:
|
||||
dehydratedDeviceAlgorithm != null ? pickleKey : null,
|
||||
)) {
|
||||
throw ('Upload key failed');
|
||||
}
|
||||
} catch (_) {
|
||||
_olmAccount?.free();
|
||||
_olmAccount = null;
|
||||
rethrow;
|
||||
_olmAccount = vod.Account();
|
||||
if (!await uploadKeys(
|
||||
uploadDeviceKeys: true,
|
||||
updateDatabase: false,
|
||||
dehydratedDeviceAlgorithm: dehydratedDeviceAlgorithm,
|
||||
dehydratedDevicePickleKey:
|
||||
dehydratedDeviceAlgorithm != null ? pickleKey : null,
|
||||
)) {
|
||||
throw ('Upload key failed');
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await olm.init();
|
||||
_olmAccount = olm.Account();
|
||||
_olmAccount!.unpickle(pickleKey ?? client.userID!, olmAccount);
|
||||
} catch (_) {
|
||||
_olmAccount?.free();
|
||||
_olmAccount = null;
|
||||
rethrow;
|
||||
_olmAccount = vod.Account.fromPickleEncrypted(
|
||||
pickle: olmAccount,
|
||||
pickleKey: (pickleKey ?? client.userID!).toPickleKey(),
|
||||
);
|
||||
} catch (e) {
|
||||
Logs().d(
|
||||
'Unable to unpickle account in vodozemac format. Trying Olm format...',
|
||||
e,
|
||||
);
|
||||
_olmAccount = vod.Account.fromOlmPickleEncrypted(
|
||||
pickle: olmAccount,
|
||||
pickleKey: utf8.encode(pickleKey ?? client.userID!),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -115,7 +118,8 @@ class OlmManager {
|
|||
if (!payload['signatures'].containsKey(client.userID)) {
|
||||
payload['signatures'][client.userID] = <String, dynamic>{};
|
||||
}
|
||||
payload['signatures'][client.userID]['ed25519:$ourDeviceId'] = signature;
|
||||
payload['signatures'][client.userID]['ed25519:$ourDeviceId'] =
|
||||
signature.toBase64();
|
||||
if (unsigned != null) {
|
||||
payload['unsigned'] = unsigned;
|
||||
}
|
||||
|
|
@ -123,13 +127,13 @@ class OlmManager {
|
|||
}
|
||||
|
||||
String signString(String s) {
|
||||
return _olmAccount!.sign(s);
|
||||
return _olmAccount!.sign(s).toBase64();
|
||||
}
|
||||
|
||||
bool _uploadKeysLock = false;
|
||||
CancelableOperation<Map<String, int>>? currentUpload;
|
||||
|
||||
int? get maxNumberOfOneTimeKeys => _olmAccount?.max_number_of_one_time_keys();
|
||||
int? get maxNumberOfOneTimeKeys => _olmAccount?.maxNumberOfOneTimeKeys;
|
||||
|
||||
/// Generates new one time keys, signs everything and upload it to the server.
|
||||
/// If `retry` is > 0, the request will be retried with new OTKs on upload failure.
|
||||
|
|
@ -158,26 +162,24 @@ class OlmManager {
|
|||
if (oldKeyCount != null) {
|
||||
// check if we have OTKs that still need uploading. If we do, we don't try to generate new ones,
|
||||
// instead we try to upload the old ones first
|
||||
final oldOTKsNeedingUpload = json
|
||||
.decode(olmAccount.one_time_keys())['curve25519']
|
||||
.entries
|
||||
.length as int;
|
||||
final oldOTKsNeedingUpload = olmAccount.oneTimeKeys.length;
|
||||
|
||||
// generate one-time keys
|
||||
// we generate 2/3rds of max, so that other keys people may still have can
|
||||
// still be used
|
||||
final oneTimeKeysCount =
|
||||
(olmAccount.max_number_of_one_time_keys() * 2 / 3).floor() -
|
||||
(olmAccount.maxNumberOfOneTimeKeys * 2 / 3).floor() -
|
||||
oldKeyCount -
|
||||
oldOTKsNeedingUpload;
|
||||
if (oneTimeKeysCount > 0) {
|
||||
olmAccount.generate_one_time_keys(oneTimeKeysCount);
|
||||
olmAccount.generateOneTimeKeys(oneTimeKeysCount);
|
||||
}
|
||||
uploadedOneTimeKeysCount = oneTimeKeysCount + oldOTKsNeedingUpload;
|
||||
}
|
||||
|
||||
if (encryption.isMinOlmVersion(3, 2, 7) && unusedFallbackKey == false) {
|
||||
if (unusedFallbackKey == false) {
|
||||
// we don't have an unused fallback key uploaded....so let's change that!
|
||||
olmAccount.generate_fallback_key();
|
||||
olmAccount.generateFallbackKey();
|
||||
}
|
||||
|
||||
// we save the generated OTKs into the database.
|
||||
|
|
@ -199,38 +201,32 @@ class OlmManager {
|
|||
};
|
||||
|
||||
if (uploadDeviceKeys) {
|
||||
final Map<String, dynamic> keys =
|
||||
json.decode(olmAccount.identity_keys());
|
||||
for (final entry in keys.entries) {
|
||||
final algorithm = entry.key;
|
||||
final value = entry.value;
|
||||
deviceKeys['keys']['$algorithm:$ourDeviceId'] = value;
|
||||
}
|
||||
final keys = olmAccount.identityKeys;
|
||||
deviceKeys['keys']['curve25519:$ourDeviceId'] =
|
||||
keys.curve25519.toBase64();
|
||||
deviceKeys['keys']['ed25519:$ourDeviceId'] = keys.ed25519.toBase64();
|
||||
deviceKeys = signJson(deviceKeys);
|
||||
}
|
||||
|
||||
// now sign all the one-time keys
|
||||
for (final entry
|
||||
in json.decode(olmAccount.one_time_keys())['curve25519'].entries) {
|
||||
for (final entry in olmAccount.oneTimeKeys.entries) {
|
||||
final key = entry.key;
|
||||
final value = entry.value;
|
||||
final value = entry.value.toBase64();
|
||||
signedOneTimeKeys['signed_curve25519:$key'] = signJson({
|
||||
'key': value,
|
||||
});
|
||||
}
|
||||
|
||||
final signedFallbackKeys = <String, dynamic>{};
|
||||
if (encryption.isMinOlmVersion(3, 2, 7)) {
|
||||
final fallbackKey = json.decode(olmAccount.unpublished_fallback_key());
|
||||
// now sign all the fallback keys
|
||||
for (final entry in fallbackKey['curve25519'].entries) {
|
||||
final key = entry.key;
|
||||
final value = entry.value;
|
||||
signedFallbackKeys['signed_curve25519:$key'] = signJson({
|
||||
'key': value,
|
||||
'fallback': true,
|
||||
});
|
||||
}
|
||||
final fallbackKey = olmAccount.fallbackKey;
|
||||
// now sign all the fallback keys
|
||||
for (final entry in fallbackKey.entries) {
|
||||
final key = entry.key;
|
||||
final value = entry.value.toBase64();
|
||||
signedFallbackKeys['signed_curve25519:$key'] = signJson({
|
||||
'key': value,
|
||||
'fallback': true,
|
||||
});
|
||||
}
|
||||
|
||||
if (signedFallbackKeys.isEmpty &&
|
||||
|
|
@ -281,7 +277,7 @@ class OlmManager {
|
|||
}
|
||||
|
||||
// mark the OTKs as published and save that to datbase
|
||||
olmAccount.mark_keys_as_published();
|
||||
olmAccount.markKeysAsPublished();
|
||||
if (updateDatabase) {
|
||||
await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!);
|
||||
}
|
||||
|
|
@ -300,18 +296,9 @@ class OlmManager {
|
|||
exception.error == MatrixError.M_UNKNOWN) {
|
||||
Logs().w('Rotating otks because upload failed', exception);
|
||||
for (final otk in signedOneTimeKeys.values) {
|
||||
// Keys can only be removed by creating a session...
|
||||
final session = olm.Session();
|
||||
try {
|
||||
final String identity =
|
||||
json.decode(olmAccount.identity_keys())['curve25519'];
|
||||
final key = otk.tryGet<String>('key');
|
||||
if (key != null) {
|
||||
session.create_outbound(_olmAccount!, identity, key);
|
||||
olmAccount.remove_one_time_keys(session);
|
||||
}
|
||||
} finally {
|
||||
session.free();
|
||||
final key = otk.tryGet<String>('key');
|
||||
if (key != null) {
|
||||
olmAccount.removeOneTimeKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -342,7 +329,6 @@ class OlmManager {
|
|||
|
||||
await _otkUpdateDedup.fetch(
|
||||
() => runBenchmarked('handleOtkUpdate', () async {
|
||||
final haveFallbackKeys = encryption.isMinOlmVersion(3, 2, 0);
|
||||
// Check if there are at least half of max_number_of_one_time_keys left on the server
|
||||
// and generate and upload more if not.
|
||||
|
||||
|
|
@ -357,7 +343,7 @@ class OlmManager {
|
|||
}
|
||||
|
||||
// fixup accidental too many uploads. We delete only one of them so that the server has time to update the counts and because we will get rate limited anyway.
|
||||
if (keyCount > _olmAccount!.max_number_of_one_time_keys()) {
|
||||
if (keyCount > _olmAccount!.maxNumberOfOneTimeKeys) {
|
||||
final requestingKeysFrom = {
|
||||
client.userID!: {ourDeviceId!: 'signed_curve25519'},
|
||||
};
|
||||
|
|
@ -365,14 +351,13 @@ class OlmManager {
|
|||
}
|
||||
|
||||
// Only upload keys if they are less than half of the max or we have no unused fallback key
|
||||
if (keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2) ||
|
||||
if (keyCount < (_olmAccount!.maxNumberOfOneTimeKeys / 2) ||
|
||||
!unusedFallbackKey) {
|
||||
await uploadKeys(
|
||||
oldKeyCount:
|
||||
keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2)
|
||||
? keyCount
|
||||
: null,
|
||||
unusedFallbackKey: haveFallbackKeys ? unusedFallbackKey : null,
|
||||
oldKeyCount: keyCount < (_olmAccount!.maxNumberOfOneTimeKeys / 2)
|
||||
? keyCount
|
||||
: null,
|
||||
unusedFallbackKey: unusedFallbackKey,
|
||||
);
|
||||
}
|
||||
}),
|
||||
|
|
@ -449,26 +434,16 @@ class OlmManager {
|
|||
if (session.session == null) {
|
||||
continue;
|
||||
}
|
||||
if (type == 0 && session.session!.matches_inbound(body)) {
|
||||
try {
|
||||
plaintext = session.session!.decrypt(type, body);
|
||||
} catch (e) {
|
||||
// The message was encrypted during this session, but is unable to decrypt
|
||||
throw DecryptException(
|
||||
DecryptException.decryptionFailed,
|
||||
e.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
plaintext = session.session!.decrypt(
|
||||
messageType: type,
|
||||
ciphertext: body,
|
||||
);
|
||||
await updateSessionUsage(session);
|
||||
break;
|
||||
} else if (type == 1) {
|
||||
try {
|
||||
plaintext = session.session!.decrypt(type, body);
|
||||
await updateSessionUsage(session);
|
||||
break;
|
||||
} catch (_) {
|
||||
plaintext = null;
|
||||
}
|
||||
} catch (_) {
|
||||
plaintext = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -477,26 +452,27 @@ class OlmManager {
|
|||
}
|
||||
|
||||
if (plaintext == null) {
|
||||
final newSession = olm.Session();
|
||||
try {
|
||||
newSession.create_inbound_from(_olmAccount!, senderKey, body);
|
||||
_olmAccount!.remove_one_time_keys(newSession);
|
||||
await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!);
|
||||
final result = _olmAccount!.createInboundSession(
|
||||
theirIdentityKey: vod.Curve25519PublicKey.fromBase64(senderKey),
|
||||
preKeyMessageBase64: body,
|
||||
);
|
||||
plaintext = result.plaintext;
|
||||
final newSession = result.session;
|
||||
|
||||
plaintext = newSession.decrypt(type, body);
|
||||
await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!);
|
||||
|
||||
await storeOlmSession(
|
||||
OlmSession(
|
||||
key: client.userID!,
|
||||
identityKey: senderKey,
|
||||
sessionId: newSession.session_id(),
|
||||
sessionId: newSession.sessionId,
|
||||
session: newSession,
|
||||
lastReceived: DateTime.now(),
|
||||
),
|
||||
);
|
||||
await updateSessionUsage();
|
||||
} catch (e) {
|
||||
newSession.free();
|
||||
throw DecryptException(DecryptException.decryptionFailed, e.toString());
|
||||
}
|
||||
}
|
||||
|
|
@ -660,27 +636,30 @@ class OlmManager {
|
|||
continue;
|
||||
}
|
||||
Logs().v('[OlmManager] Starting session with $userId:$deviceId');
|
||||
final session = olm.Session();
|
||||
try {
|
||||
session.create_outbound(
|
||||
_olmAccount!,
|
||||
identityKey,
|
||||
deviceKey.tryGet<String>('key')!,
|
||||
final session = _olmAccount!.createOutboundSession(
|
||||
identityKey: vod.Curve25519PublicKey.fromBase64(identityKey),
|
||||
oneTimeKey: vod.Curve25519PublicKey.fromBase64(
|
||||
deviceKey.tryGet<String>('key')!,
|
||||
),
|
||||
);
|
||||
|
||||
await storeOlmSession(
|
||||
OlmSession(
|
||||
key: client.userID!,
|
||||
identityKey: identityKey,
|
||||
sessionId: session.session_id(),
|
||||
sessionId: session.sessionId,
|
||||
session: session,
|
||||
lastReceived:
|
||||
DateTime.now(), // we want to use a newly created session
|
||||
),
|
||||
);
|
||||
} catch (e, s) {
|
||||
session.free();
|
||||
Logs()
|
||||
.e('[LibOlm] Could not create new outbound olm session', e, s);
|
||||
Logs().e(
|
||||
'[Vodozemac] Could not create new outbound olm session',
|
||||
e,
|
||||
s,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -733,8 +712,8 @@ class OlmManager {
|
|||
'ciphertext': <String, dynamic>{},
|
||||
};
|
||||
encryptedBody['ciphertext'][device.curve25519Key] = {
|
||||
'type': encryptResult.type,
|
||||
'body': encryptResult.body,
|
||||
'type': encryptResult.messageType,
|
||||
'body': encryptResult.ciphertext,
|
||||
};
|
||||
return encryptedBody;
|
||||
}
|
||||
|
|
@ -769,10 +748,10 @@ class OlmManager {
|
|||
getFromDb: false,
|
||||
);
|
||||
} on NoOlmSessionFoundException catch (e) {
|
||||
Logs().d('[LibOlm] Error encrypting to-device event', e);
|
||||
Logs().d('[Vodozemac] Error encrypting to-device event', e);
|
||||
continue;
|
||||
} catch (e, s) {
|
||||
Logs().wtf('[LibOlm] Error encrypting to-device event', e, s);
|
||||
Logs().wtf('[Vodozemac] Error encrypting to-device event', e, s);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -820,13 +799,6 @@ class OlmManager {
|
|||
|
||||
Future<void> dispose() async {
|
||||
await currentUpload?.cancel();
|
||||
for (final sessions in olmSessions.values) {
|
||||
for (final sess in sessions) {
|
||||
sess.dispose();
|
||||
}
|
||||
}
|
||||
_olmAccount?.free();
|
||||
_olmAccount = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
Logs().w('[Vodozemac] Signature check failed', e, s);
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,35 @@ 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']);
|
||||
final sas = this.sas;
|
||||
if (sas == null || sas.disposed) {
|
||||
throw Exception('SAS object is disposed');
|
||||
}
|
||||
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 +1489,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 +1555,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.encoder.convert(pubKey + canonicalJson);
|
||||
final digest = crypto.sha256.convert(bytes);
|
||||
return encodeBase64Unpadded(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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,17 +16,20 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:vodozemac/vodozemac.dart' as vod;
|
||||
|
||||
import 'package:matrix/encryption/utils/pickle_key.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class OlmSession {
|
||||
String identityKey;
|
||||
String? sessionId;
|
||||
olm.Session? session;
|
||||
vod.Session? session;
|
||||
DateTime? lastReceived;
|
||||
final String key;
|
||||
String? get pickledSession => session?.pickle(key);
|
||||
String? get pickledSession => session?.toPickleEncrypted(key.toPickleKey());
|
||||
|
||||
bool get isValid => session != null;
|
||||
|
||||
|
|
@ -40,21 +43,25 @@ class OlmSession {
|
|||
|
||||
OlmSession.fromJson(Map<String, dynamic> dbEntry, this.key)
|
||||
: identityKey = dbEntry['identity_key'] ?? '' {
|
||||
session = olm.Session();
|
||||
try {
|
||||
session!.unpickle(key, dbEntry['pickle']);
|
||||
try {
|
||||
session = vod.Session.fromPickleEncrypted(
|
||||
pickleKey: key.toPickleKey(),
|
||||
pickle: dbEntry['pickle'],
|
||||
);
|
||||
} catch (_) {
|
||||
Logs().d('Unable to unpickle Olm session. Try LibOlm format.');
|
||||
session = vod.Session.fromOlmPickleEncrypted(
|
||||
pickleKey: utf8.encode(key),
|
||||
pickle: dbEntry['pickle'],
|
||||
);
|
||||
}
|
||||
sessionId = dbEntry['session_id'];
|
||||
lastReceived =
|
||||
DateTime.fromMillisecondsSinceEpoch(dbEntry['last_received'] ?? 0);
|
||||
assert(sessionId == session!.session_id());
|
||||
assert(sessionId == session!.sessionId);
|
||||
} catch (e, s) {
|
||||
Logs().e('[LibOlm] Could not unpickle olm session', e, s);
|
||||
dispose();
|
||||
Logs().e('[Vodozemac] Could not unpickle olm session', e, s);
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
session?.free();
|
||||
session = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,9 @@
|
|||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
import 'package:vodozemac/vodozemac.dart' as vod;
|
||||
|
||||
import 'package:matrix/encryption/utils/pickle_key.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class OutboundGroupSession {
|
||||
|
|
@ -30,8 +31,8 @@ class OutboundGroupSession {
|
|||
Map<String, Map<String, bool>> devices = {};
|
||||
// Default to a date, that would get this session rotated in any case to make handling easier
|
||||
DateTime creationTime = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
olm.OutboundGroupSession? outboundGroupSession;
|
||||
int? get sentMessages => outboundGroupSession?.message_index();
|
||||
vod.GroupSession? outboundGroupSession;
|
||||
int? get sentMessages => outboundGroupSession?.messageIndex;
|
||||
bool get isValid => outboundGroupSession != null;
|
||||
final String key;
|
||||
|
||||
|
|
@ -54,19 +55,24 @@ class OutboundGroupSession {
|
|||
);
|
||||
return;
|
||||
}
|
||||
outboundGroupSession = olm.OutboundGroupSession();
|
||||
|
||||
creationTime =
|
||||
DateTime.fromMillisecondsSinceEpoch(dbEntry['creation_time']);
|
||||
|
||||
try {
|
||||
outboundGroupSession!.unpickle(key, dbEntry['pickle']);
|
||||
creationTime =
|
||||
DateTime.fromMillisecondsSinceEpoch(dbEntry['creation_time']);
|
||||
outboundGroupSession = vod.GroupSession.fromPickleEncrypted(
|
||||
pickleKey: key.toPickleKey(),
|
||||
pickle: dbEntry['pickle'],
|
||||
);
|
||||
} catch (e, s) {
|
||||
dispose();
|
||||
Logs().e('[LibOlm] Unable to unpickle outboundGroupSession', e, s);
|
||||
try {
|
||||
outboundGroupSession = vod.GroupSession.fromOlmPickleEncrypted(
|
||||
pickleKey: utf8.encode(key),
|
||||
pickle: dbEntry['pickle'],
|
||||
);
|
||||
} catch (_) {
|
||||
Logs().e('[Vodozemac] Unable to unpickle outboundGroupSession', e, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
outboundGroupSession?.free();
|
||||
outboundGroupSession = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
extension PickleKeyStringExtension on String {
|
||||
Uint8List toPickleKey() {
|
||||
final bytes = Uint8List.fromList(codeUnits);
|
||||
final missing = 32 - bytes.length;
|
||||
if (missing > 0) {
|
||||
return Uint8List.fromList([
|
||||
...bytes,
|
||||
...List.filled(missing, 0),
|
||||
]);
|
||||
}
|
||||
return Uint8List.fromList(bytes.getRange(0, 32).toList());
|
||||
}
|
||||
}
|
||||
|
|
@ -16,8 +16,11 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:vodozemac/vodozemac.dart' as vod;
|
||||
|
||||
import 'package:matrix/encryption/utils/pickle_key.dart';
|
||||
import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
|
|
@ -33,7 +36,7 @@ class SessionKey {
|
|||
Map<String, Map<String, int>> allowedAtIndex;
|
||||
|
||||
/// Underlying olm [InboundGroupSession] object
|
||||
olm.InboundGroupSession? inboundGroupSession;
|
||||
vod.InboundGroupSession? inboundGroupSession;
|
||||
|
||||
/// Key for libolm pickle / unpickle
|
||||
final String key;
|
||||
|
|
@ -81,8 +84,7 @@ class SessionKey {
|
|||
.catchMap((k, v) => MapEntry(k, Map<String, int>.from(v))),
|
||||
roomId = dbEntry.roomId,
|
||||
sessionId = dbEntry.sessionId,
|
||||
senderKey = dbEntry.senderKey,
|
||||
inboundGroupSession = olm.InboundGroupSession() {
|
||||
senderKey = dbEntry.senderKey {
|
||||
final parsedSenderClaimedKeys =
|
||||
Event.getMapFromPayload(dbEntry.senderClaimedKeys)
|
||||
.catchMap((k, v) => MapEntry<String, String>(k, v));
|
||||
|
|
@ -99,15 +101,21 @@ class SessionKey {
|
|||
: <String, String>{}));
|
||||
|
||||
try {
|
||||
inboundGroupSession!.unpickle(key, dbEntry.pickle);
|
||||
inboundGroupSession = vod.InboundGroupSession.fromPickleEncrypted(
|
||||
pickle: dbEntry.pickle,
|
||||
pickleKey: key.toPickleKey(),
|
||||
);
|
||||
} catch (e, s) {
|
||||
dispose();
|
||||
Logs().e('[LibOlm] Unable to unpickle inboundGroupSession', e, s);
|
||||
try {
|
||||
Logs().d('Unable to unpickle inboundGroupSession. Try LibOlm format.');
|
||||
inboundGroupSession = vod.InboundGroupSession.fromOlmPickleEncrypted(
|
||||
pickle: dbEntry.pickle,
|
||||
pickleKey: utf8.encode(key),
|
||||
);
|
||||
} catch (_) {
|
||||
Logs().e('[Vodozemac] Unable to unpickle inboundGroupSession', e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
inboundGroupSession?.free();
|
||||
inboundGroupSession = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ import 'package:async/async.dart';
|
|||
import 'package:collection/collection.dart' show IterableExtension;
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mime/mime.dart';
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
import 'package:random_string/random_string.dart';
|
||||
import 'package:vodozemac/vodozemac.dart' as vod;
|
||||
|
||||
import 'package:matrix/encryption.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
|
@ -36,7 +36,6 @@ import 'package:matrix/msc_extensions/msc_unpublished_custom_refresh_token_lifet
|
|||
import 'package:matrix/src/models/timeline_chunk.dart';
|
||||
import 'package:matrix/src/utils/cached_stream_controller.dart';
|
||||
import 'package:matrix/src/utils/client_init_exception.dart';
|
||||
import 'package:matrix/src/utils/compute_callback.dart';
|
||||
import 'package:matrix/src/utils/multilock.dart';
|
||||
import 'package:matrix/src/utils/run_benchmarked.dart';
|
||||
import 'package:matrix/src/utils/run_in_root.dart';
|
||||
|
|
@ -107,20 +106,6 @@ class Client extends MatrixApi {
|
|||
|
||||
final bool convertLinebreaksInFormatting;
|
||||
|
||||
final ComputeCallback? compute;
|
||||
|
||||
@Deprecated('Use [nativeImplementations] instead')
|
||||
Future<T> runInBackground<T, U>(
|
||||
FutureOr<T> Function(U arg) function,
|
||||
U arg,
|
||||
) async {
|
||||
final compute = this.compute;
|
||||
if (compute != null) {
|
||||
return await compute(function, arg);
|
||||
}
|
||||
return await function(arg);
|
||||
}
|
||||
|
||||
final Duration sendTimelineEventTimeout;
|
||||
|
||||
/// The timeout until a typing indicator gets removed automatically.
|
||||
|
|
@ -209,8 +194,7 @@ class Client extends MatrixApi {
|
|||
Set<String>? supportedLoginTypes,
|
||||
this.mxidLocalPartFallback = true,
|
||||
this.formatLocalpart = true,
|
||||
@Deprecated('Use [nativeImplementations] instead') this.compute,
|
||||
NativeImplementations nativeImplementations = NativeImplementations.dummy,
|
||||
this.nativeImplementations = NativeImplementations.dummy,
|
||||
Level? logLevel,
|
||||
Filter? syncFilter,
|
||||
Duration defaultNetworkRequestTimeout = const Duration(seconds: 35),
|
||||
|
|
@ -248,9 +232,6 @@ class Client extends MatrixApi {
|
|||
supportedLoginTypes =
|
||||
supportedLoginTypes ?? {AuthenticationTypes.password},
|
||||
verificationMethods = verificationMethods ?? <KeyVerificationMethod>{},
|
||||
nativeImplementations = compute != null
|
||||
? NativeImplementationsIsolate(compute)
|
||||
: nativeImplementations,
|
||||
super(
|
||||
httpClient: FixedTimeoutHttpClient(
|
||||
httpClient ?? http.Client(),
|
||||
|
|
@ -2101,15 +2082,14 @@ class Client extends MatrixApi {
|
|||
}
|
||||
|
||||
await encryption?.dispose();
|
||||
try {
|
||||
// make sure to throw an exception if libolm doesn't exist
|
||||
await olm.init();
|
||||
olm.get_library_version();
|
||||
_encryption = Encryption(client: this);
|
||||
} catch (e) {
|
||||
Logs().e('Error initializing encryption $e');
|
||||
await encryption?.dispose();
|
||||
_encryption = null;
|
||||
if (vod.isInitialized()) {
|
||||
try {
|
||||
_encryption = Encryption(client: this);
|
||||
} catch (e) {
|
||||
Logs().e('Error initializing encryption $e');
|
||||
await encryption?.dispose();
|
||||
_encryption = null;
|
||||
}
|
||||
}
|
||||
onInitStateChanged?.call(InitState.settingUpEncryption);
|
||||
await encryption?.init(olmAccount);
|
||||
|
|
@ -3431,7 +3411,7 @@ class Client extends MatrixApi {
|
|||
});
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logs().e('[LibOlm] Unable to update user device keys', e, s);
|
||||
Logs().e('[Vodozemac] Unable to update user device keys', e, s);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,12 +11,3 @@ typedef ComputeRunner = Future<T> Function<T, U>(
|
|||
FutureOr<T> Function(U arg) function,
|
||||
U arg,
|
||||
);
|
||||
|
||||
ComputeCallback computeCallbackFromRunInBackground(ComputeRunner runner) {
|
||||
return <U, T>(
|
||||
FutureOr<T> Function(U arg) callback,
|
||||
U arg, {
|
||||
String? debugLabel,
|
||||
}) =>
|
||||
runner.call(callback, arg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import 'package:image/image.dart';
|
|||
import 'package:mime/mime.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:matrix/src/utils/compute_callback.dart';
|
||||
|
||||
class MatrixFile {
|
||||
final Uint8List bytes;
|
||||
|
|
@ -112,13 +111,8 @@ class MatrixImageFile extends MatrixFile {
|
|||
required Uint8List bytes,
|
||||
required String name,
|
||||
String? mimeType,
|
||||
@Deprecated('Use [nativeImplementations] instead') ComputeRunner? compute,
|
||||
NativeImplementations nativeImplementations = NativeImplementations.dummy,
|
||||
}) async {
|
||||
if (compute != null) {
|
||||
nativeImplementations =
|
||||
NativeImplementationsIsolate.fromRunInBackground(compute);
|
||||
}
|
||||
final metaData = await nativeImplementations.calcImageMetadata(bytes);
|
||||
|
||||
return MatrixImageFile(
|
||||
|
|
@ -142,13 +136,8 @@ class MatrixImageFile extends MatrixFile {
|
|||
Future<MatrixImageFileResizedResponse?> Function(
|
||||
MatrixImageFileResizeArguments,
|
||||
)? customImageResizer,
|
||||
@Deprecated('Use [nativeImplementations] instead') ComputeRunner? compute,
|
||||
NativeImplementations nativeImplementations = NativeImplementations.dummy,
|
||||
}) async {
|
||||
if (compute != null) {
|
||||
nativeImplementations =
|
||||
NativeImplementationsIsolate.fromRunInBackground(compute);
|
||||
}
|
||||
final image = MatrixImageFile(name: name, mimeType: mimeType, bytes: bytes);
|
||||
|
||||
return await image.generateThumbnail(
|
||||
|
|
@ -196,13 +185,8 @@ class MatrixImageFile extends MatrixFile {
|
|||
Future<MatrixImageFileResizedResponse?> Function(
|
||||
MatrixImageFileResizeArguments,
|
||||
)? customImageResizer,
|
||||
@Deprecated('Use [nativeImplementations] instead') ComputeRunner? compute,
|
||||
NativeImplementations nativeImplementations = NativeImplementations.dummy,
|
||||
}) async {
|
||||
if (compute != null) {
|
||||
nativeImplementations =
|
||||
NativeImplementationsIsolate.fromRunInBackground(compute);
|
||||
}
|
||||
final arguments = MatrixImageFileResizeArguments(
|
||||
bytes: bytes,
|
||||
maxDimension: dimension,
|
||||
|
|
|
|||
|
|
@ -133,19 +133,13 @@ class NativeImplementationsDummy extends NativeImplementations {
|
|||
class NativeImplementationsIsolate extends NativeImplementations {
|
||||
/// pass by Flutter's compute function here
|
||||
final ComputeCallback compute;
|
||||
final Future<void> Function()? vodozemacInit;
|
||||
|
||||
NativeImplementationsIsolate(this.compute);
|
||||
|
||||
/// creates a [NativeImplementationsIsolate] based on a [ComputeRunner] as
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
/// known from [Client.runInBackground]
|
||||
factory NativeImplementationsIsolate.fromRunInBackground(
|
||||
ComputeRunner runInBackground,
|
||||
) {
|
||||
return NativeImplementationsIsolate(
|
||||
computeCallbackFromRunInBackground(runInBackground),
|
||||
);
|
||||
}
|
||||
NativeImplementationsIsolate(
|
||||
this.compute, {
|
||||
/// To generate upload keys, vodozemac needs to be initialized in the isolate.
|
||||
this.vodozemacInit,
|
||||
});
|
||||
|
||||
Future<T> runInBackground<T, U>(
|
||||
FutureOr<T> Function(U arg) function,
|
||||
|
|
@ -172,7 +166,10 @@ class NativeImplementationsIsolate extends NativeImplementations {
|
|||
bool retryInDummy = true,
|
||||
}) async {
|
||||
return runInBackground<RoomKeys, GenerateUploadKeysArgs>(
|
||||
NativeImplementations.dummy.generateUploadKeys,
|
||||
(GenerateUploadKeysArgs args) async {
|
||||
await vodozemacInit?.call();
|
||||
return NativeImplementations.dummy.generateUploadKeys(args);
|
||||
},
|
||||
args,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,13 +24,13 @@ dependencies:
|
|||
js: ^0.6.3
|
||||
markdown: ^7.1.1
|
||||
mime: ">=1.0.0 <3.0.0"
|
||||
olm: ^3.1.0
|
||||
random_string: ^2.3.1
|
||||
sdp_transform: ^0.3.2
|
||||
slugify: ^2.0.0
|
||||
sqflite_common: ^2.4.5
|
||||
sqlite3: ^2.1.0
|
||||
typed_data: ^1.3.2
|
||||
vodozemac: ^0.2.0
|
||||
webrtc_interface: ^1.2.0
|
||||
|
||||
dev_dependencies:
|
||||
|
|
|
|||
|
|
@ -1,35 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
ENTRYPOINT="$(pwd)"
|
||||
|
||||
mkdir js
|
||||
cd js
|
||||
|
||||
curl -O 'https://packages.matrix.org/npm/olm/olm-3.1.4.tgz'
|
||||
tar xaf olm-3.1.4.tgz
|
||||
|
||||
cd ..
|
||||
|
||||
if [ -f /usr/lib/x86_64-linux-gnu/libolm.so.3 ]; then
|
||||
mkdir -p ffi/olm/
|
||||
ln -sf /usr/lib/x86_64-linux-gnu/libolm.so.3 ffi/olm/libolm.so
|
||||
# alpine specific location
|
||||
elif [ -f /usr/lib/libolm.so.3 ]; then
|
||||
mkdir -p ffi/olm
|
||||
ln -sf /usr/lib/libolm.so.3 ffi/olm/libolm.so
|
||||
else
|
||||
mkdir ffi
|
||||
cd ffi
|
||||
cd ..
|
||||
git clone --depth 1 https://gitlab.matrix.org/matrix-org/olm.git
|
||||
cd olm
|
||||
cmake -DCMAKE_BUILD_TYPE=Release .
|
||||
cmake --build .
|
||||
cd ..
|
||||
fi
|
||||
|
||||
cd "$ENTRYPOINT"
|
||||
|
||||
if which flutter >/dev/null; then
|
||||
flutter pub get
|
||||
else
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
rm -rf rust
|
||||
git clone https://github.com/famedly/dart-vodozemac.git
|
||||
mv ./dart-vodozemac/rust ./
|
||||
rm -rf dart-vodozemac
|
||||
cd ./rust
|
||||
cargo build
|
||||
cd ..
|
||||
|
|
@ -23,9 +23,9 @@ import 'dart:typed_data';
|
|||
|
||||
import 'package:canonical_json/canonical_json.dart';
|
||||
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';
|
||||
|
|
@ -35,7 +35,7 @@ import 'fake_database.dart';
|
|||
void main() {
|
||||
// key @test:fakeServer.notExisting
|
||||
const pickledOlmAccount =
|
||||
'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw';
|
||||
'huxcPifHlyiQsX7cZeMMITbka3hLeUT3ss6DLL6dV7knaD4wgAYK6gcWknkixnX8C5KMIyxzytxiNqAOhDFRE5NsET8hr2dQ8OvXX7M95eQ7/3dPi7FkPUIbvneTSGgJYNDxJdHsDJ8OBHZ3BoqUJFDbTzFfVJjEzN4G9XQwPDafZ2p5WyerOK8Twj/rvk5N+ERmkt1XgVLQl66we/BO1ugTeM3YpDHm5lTzFUitJGTIuuONsKG9mmzdAmVUJ9YIrSxwmOBdegbGA+LAl5acg5VOol3KxRgZUMJQRQ58zpBAs72oauHizv1QVoQ7uIUiCUeb9lym+TEjmApvhru/1CPHU90K5jHNZ57wb/4V9VsqBWuoNibzDWG35YTFLcx0o+1lrCIjm1QjuC0777G+L1HNw5wnppV3z/k0YujjuPS3wvOA30TjHg';
|
||||
const identityKey = '7rvl3jORJkBiK4XX1e5TnGnqz068XfYJ0W++Ml63rgk';
|
||||
const fingerprintKey = 'gjL//fyaFHADt9KBADGag8g7F8Up78B/K1zXeiEPLJo';
|
||||
|
||||
|
|
@ -67,10 +67,7 @@ void main() {
|
|||
group('client mem', tags: 'olm', () {
|
||||
late Client matrix;
|
||||
|
||||
Logs().level = Level.error;
|
||||
|
||||
/// Check if all Elements get created
|
||||
|
||||
setUp(() async {
|
||||
matrix = await getClient();
|
||||
});
|
||||
|
|
@ -1072,9 +1069,8 @@ void main() {
|
|||
|
||||
final deviceKeys = <DeviceKeys>[];
|
||||
for (var i = 0; i < 30; i++) {
|
||||
final account = olm.Account();
|
||||
account.create();
|
||||
final keys = json.decode(account.identity_keys());
|
||||
final account = vod.Account();
|
||||
final keys = account.identityKeys;
|
||||
final userId = '@testuser:example.org';
|
||||
final deviceId = 'DEVICE$i';
|
||||
final keyObj = {
|
||||
|
|
@ -1085,18 +1081,17 @@ void main() {
|
|||
'm.megolm.v1.aes-sha2',
|
||||
],
|
||||
'keys': {
|
||||
'curve25519:$deviceId': keys['curve25519'],
|
||||
'ed25519:$deviceId': keys['ed25519'],
|
||||
'curve25519:$deviceId': keys.curve25519.toBase64(),
|
||||
'ed25519:$deviceId': keys.ed25519.toBase64(),
|
||||
},
|
||||
};
|
||||
final signature =
|
||||
account.sign(String.fromCharCodes(canonicalJson.encode(keyObj)));
|
||||
keyObj['signatures'] = {
|
||||
userId: {
|
||||
'ed25519:$deviceId': signature,
|
||||
'ed25519:$deviceId': signature.toBase64(),
|
||||
},
|
||||
};
|
||||
account.free();
|
||||
deviceKeys.add(DeviceKeys.fromJson(keyObj, matrix));
|
||||
}
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
|
|
@ -1754,7 +1749,10 @@ void main() {
|
|||
expect(error.homeserver, Uri.parse('https://test.server'));
|
||||
expect(error.olmAccount, 'abcd');
|
||||
expect(error.userId, '@user:server');
|
||||
expect(error.toString(), 'Exception: BAD_ACCOUNT_KEY');
|
||||
expect(
|
||||
error.originalException.runtimeType.toString(),
|
||||
'AnyhowException',
|
||||
);
|
||||
}
|
||||
await customClient.dispose(closeDatabase: true);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import 'dart:convert';
|
|||
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';
|
||||
|
|
@ -47,6 +48,10 @@ void main() {
|
|||
}
|
||||
|
||||
test('setupClient', () async {
|
||||
await vod.init(
|
||||
wasmPath: './pkg/',
|
||||
libraryPath: './rust/target/debug/',
|
||||
);
|
||||
client = await getClient();
|
||||
room = Room(id: '!1234:fakeServer.notExisting', client: client);
|
||||
room.setState(
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
import 'dart:async';
|
||||
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,8 +35,11 @@ void main() {
|
|||
late String origKeyId;
|
||||
|
||||
setUpAll(() async {
|
||||
await olm.init();
|
||||
olm.get_library_version();
|
||||
await vod.init(
|
||||
wasmPath: './pkg/',
|
||||
libraryPath: './rust/target/debug/',
|
||||
);
|
||||
|
||||
client = await getClient();
|
||||
});
|
||||
|
||||
|
|
@ -75,20 +78,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 +132,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 +187,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');
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@
|
|||
|
||||
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,8 +31,11 @@ void main() {
|
|||
late Client client;
|
||||
|
||||
setUpAll(() async {
|
||||
await olm.init();
|
||||
olm.get_library_version();
|
||||
await vod.init(
|
||||
wasmPath: './pkg/',
|
||||
libraryPath: './rust/target/debug/',
|
||||
);
|
||||
|
||||
client = await getClient();
|
||||
await client.abortSync();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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,8 +33,11 @@ void main() {
|
|||
final now = DateTime.now();
|
||||
|
||||
setUpAll(() async {
|
||||
await olm.init();
|
||||
olm.get_library_version();
|
||||
await vod.init(
|
||||
wasmPath: './pkg/',
|
||||
libraryPath: './rust/target/debug/',
|
||||
);
|
||||
|
||||
client = await getClient();
|
||||
room = client.getRoomById(roomId)!;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,18 +16,16 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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';
|
||||
import '../fake_database.dart';
|
||||
|
||||
void main() async {
|
||||
// key @othertest:fakeServer.notExisting
|
||||
const otherPickledOlmAccount =
|
||||
'VWhVApbkcilKAEGppsPDf9nNVjaK8/IxT3asSR0sYg0S5KgbfE8vXEPwoiKBX2cEvwX3OessOBOkk+ZE7TTbjlrh/KEd31p8Wo+47qj0AP+Ky+pabnhi+/rTBvZy+gfzTqUfCxZrkzfXI9Op4JnP6gYmy7dVX2lMYIIs9WCO1jcmIXiXum5jnfXu1WLfc7PZtO2hH+k9CDKosOFaXRBmsu8k/BGXPSoWqUpvu6WpEG9t5STk4FeAzA';
|
||||
final database = await getDatabase();
|
||||
|
||||
group('Encrypt/Decrypt to-device messages', tags: 'olm', () {
|
||||
Logs().level = Level.error;
|
||||
|
||||
|
|
@ -41,8 +39,11 @@ void main() async {
|
|||
late Map<String, dynamic> payload;
|
||||
|
||||
setUpAll(() async {
|
||||
await olm.init();
|
||||
olm.get_library_version();
|
||||
await vod.init(
|
||||
wasmPath: './pkg/',
|
||||
libraryPath: './rust/target/debug/',
|
||||
);
|
||||
|
||||
client = await getClient();
|
||||
});
|
||||
|
||||
|
|
@ -59,7 +60,6 @@ void main() async {
|
|||
newHomeserver: otherClient.homeserver,
|
||||
newDeviceName: 'Text Matrix Client',
|
||||
newDeviceID: 'FOXDEVICE',
|
||||
newOlmAccount: otherPickledOlmAccount,
|
||||
);
|
||||
await otherClient.abortSync();
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@
|
|||
|
||||
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,8 +30,11 @@ void main() {
|
|||
late Client client;
|
||||
|
||||
setUpAll(() async {
|
||||
await olm.init();
|
||||
olm.get_library_version();
|
||||
await vod.init(
|
||||
wasmPath: './pkg/',
|
||||
libraryPath: './rust/target/debug/',
|
||||
);
|
||||
|
||||
client = await getClient();
|
||||
});
|
||||
|
||||
|
|
@ -110,7 +113,7 @@ void main() {
|
|||
);
|
||||
var inbound = client.encryption!.keyManager.getInboundGroupSession(
|
||||
roomId,
|
||||
sess.outboundGroupSession!.session_id(),
|
||||
sess.outboundGroupSession!.sessionId,
|
||||
);
|
||||
expect(inbound != null, true);
|
||||
expect(
|
||||
|
|
@ -152,7 +155,7 @@ void main() {
|
|||
// lazy-create if it would rotate
|
||||
sess = await client.encryption!.keyManager
|
||||
.createOutboundGroupSession(roomId);
|
||||
final oldSessKey = sess.outboundGroupSession!.session_key();
|
||||
final oldSessKey = sess.outboundGroupSession!.sessionKey;
|
||||
client.userDeviceKeys['@alice:example.com']!.deviceKeys['JLAFKJWSCS']!
|
||||
.blocked = true;
|
||||
await client.encryption!.keyManager.prepareOutboundGroupSession(roomId);
|
||||
|
|
@ -164,7 +167,7 @@ void main() {
|
|||
client.encryption!.keyManager
|
||||
.getOutboundGroupSession(roomId)!
|
||||
.outboundGroupSession!
|
||||
.session_key() !=
|
||||
.sessionKey !=
|
||||
oldSessKey,
|
||||
true,
|
||||
);
|
||||
|
|
@ -236,7 +239,7 @@ void main() {
|
|||
);
|
||||
inbound = client.encryption!.keyManager.getInboundGroupSession(
|
||||
roomId,
|
||||
sess.outboundGroupSession!.session_id(),
|
||||
sess.outboundGroupSession!.sessionId,
|
||||
);
|
||||
expect(
|
||||
inbound!.allowedAtIndex['@alice:example.com']
|
||||
|
|
@ -353,13 +356,13 @@ void main() {
|
|||
});
|
||||
|
||||
test('setInboundGroupSession', () async {
|
||||
final session = olm.OutboundGroupSession();
|
||||
session.create();
|
||||
final inbound = olm.InboundGroupSession();
|
||||
inbound.create(session.session_key());
|
||||
final session = vod.GroupSession();
|
||||
|
||||
final inbound = vod.InboundGroupSession(session.sessionKey);
|
||||
|
||||
final senderKey = client.identityKey;
|
||||
final roomId = '!someroom:example.org';
|
||||
final sessionId = inbound.session_id();
|
||||
final sessionId = inbound.sessionId;
|
||||
final room = Room(id: roomId, client: client);
|
||||
final nextSyncUpdateFuture = client.onSync.stream
|
||||
.firstWhere((update) => update.rooms != null)
|
||||
|
|
@ -408,7 +411,7 @@ void main() {
|
|||
'room_id': roomId,
|
||||
'forwarding_curve25519_key_chain': [client.identityKey],
|
||||
'session_id': sessionId,
|
||||
'session_key': inbound.export_session(1),
|
||||
'session_key': inbound.exportAt(1),
|
||||
'sender_key': senderKey,
|
||||
'sender_claimed_ed25519_key': client.fingerprintKey,
|
||||
};
|
||||
|
|
@ -423,7 +426,7 @@ void main() {
|
|||
client.encryption!.keyManager
|
||||
.getInboundGroupSession(roomId, sessionId)
|
||||
?.inboundGroupSession
|
||||
?.first_known_index(),
|
||||
?.firstKnownIndex,
|
||||
1,
|
||||
);
|
||||
expect(
|
||||
|
|
@ -440,7 +443,7 @@ void main() {
|
|||
'room_id': roomId,
|
||||
'forwarding_curve25519_key_chain': [client.identityKey],
|
||||
'session_id': sessionId,
|
||||
'session_key': inbound.export_session(2),
|
||||
'session_key': inbound.exportAt(2),
|
||||
'sender_key': senderKey,
|
||||
'sender_claimed_ed25519_key': client.fingerprintKey,
|
||||
};
|
||||
|
|
@ -455,7 +458,7 @@ void main() {
|
|||
client.encryption!.keyManager
|
||||
.getInboundGroupSession(roomId, sessionId)
|
||||
?.inboundGroupSession
|
||||
?.first_known_index(),
|
||||
?.firstKnownIndex,
|
||||
1,
|
||||
);
|
||||
expect(
|
||||
|
|
@ -472,7 +475,7 @@ void main() {
|
|||
'room_id': roomId,
|
||||
'forwarding_curve25519_key_chain': [client.identityKey],
|
||||
'session_id': sessionId,
|
||||
'session_key': inbound.export_session(0),
|
||||
'session_key': inbound.exportAt(0),
|
||||
'sender_key': senderKey,
|
||||
'sender_claimed_ed25519_key': client.fingerprintKey,
|
||||
};
|
||||
|
|
@ -487,7 +490,7 @@ void main() {
|
|||
client.encryption!.keyManager
|
||||
.getInboundGroupSession(roomId, sessionId)
|
||||
?.inboundGroupSession
|
||||
?.first_known_index(),
|
||||
?.firstKnownIndex,
|
||||
0,
|
||||
);
|
||||
expect(
|
||||
|
|
@ -504,7 +507,7 @@ void main() {
|
|||
'room_id': roomId,
|
||||
'forwarding_curve25519_key_chain': [client.identityKey, 'beep'],
|
||||
'session_id': sessionId,
|
||||
'session_key': inbound.export_session(0),
|
||||
'session_key': inbound.exportAt(0),
|
||||
'sender_key': senderKey,
|
||||
'sender_claimed_ed25519_key': client.fingerprintKey,
|
||||
};
|
||||
|
|
@ -519,7 +522,7 @@ void main() {
|
|||
client.encryption!.keyManager
|
||||
.getInboundGroupSession(roomId, sessionId)
|
||||
?.inboundGroupSession
|
||||
?.first_known_index(),
|
||||
?.firstKnownIndex,
|
||||
0,
|
||||
);
|
||||
expect(
|
||||
|
|
@ -536,7 +539,7 @@ void main() {
|
|||
'room_id': roomId,
|
||||
'forwarding_curve25519_key_chain': [],
|
||||
'session_id': sessionId,
|
||||
'session_key': inbound.export_session(0),
|
||||
'session_key': inbound.exportAt(0),
|
||||
'sender_key': senderKey,
|
||||
'sender_claimed_ed25519_key': client.fingerprintKey,
|
||||
};
|
||||
|
|
@ -551,7 +554,7 @@ void main() {
|
|||
client.encryption!.keyManager
|
||||
.getInboundGroupSession(roomId, sessionId)
|
||||
?.inboundGroupSession
|
||||
?.first_known_index(),
|
||||
?.firstKnownIndex,
|
||||
0,
|
||||
);
|
||||
expect(
|
||||
|
|
@ -570,9 +573,6 @@ void main() {
|
|||
// decrypted last event
|
||||
final syncUpdate = await nextSyncUpdateFuture;
|
||||
expect(syncUpdate.rooms?.join?.containsKey(room.id), true);
|
||||
|
||||
inbound.free();
|
||||
session.free();
|
||||
});
|
||||
|
||||
test('Reused deviceID attack', () async {
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@
|
|||
|
||||
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,8 +42,10 @@ void main() {
|
|||
Logs().level = Level.error;
|
||||
|
||||
setUpAll(() async {
|
||||
await olm.init();
|
||||
olm.get_library_version();
|
||||
await vod.init(
|
||||
wasmPath: './pkg/',
|
||||
libraryPath: './rust/target/debug/',
|
||||
);
|
||||
});
|
||||
|
||||
final validSessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU';
|
||||
|
|
@ -309,8 +311,7 @@ void main() {
|
|||
|
||||
final session = (await matrix.encryption!.keyManager
|
||||
.loadInboundGroupSession(requestRoom.id, validSessionId))!;
|
||||
final sessionKey = session.inboundGroupSession!
|
||||
.export_session(session.inboundGroupSession!.first_known_index());
|
||||
final sessionKey = session.inboundGroupSession!.exportAtFirstKnownIndex();
|
||||
matrix.encryption!.keyManager.clearInboundGroupSessions();
|
||||
var event = ToDeviceEvent(
|
||||
sender: '@alice:example.com',
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
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';
|
||||
|
|
@ -52,14 +52,16 @@ void main() async {
|
|||
|
||||
// key @othertest:fakeServer.notExisting
|
||||
const otherPickledOlmAccount =
|
||||
'VWhVApbkcilKAEGppsPDf9nNVjaK8/IxT3asSR0sYg0S5KgbfE8vXEPwoiKBX2cEvwX3OessOBOkk+ZE7TTbjlrh/KEd31p8Wo+47qj0AP+Ky+pabnhi+/rTBvZy+gfzTqUfCxZrkzfXI9Op4JnP6gYmy7dVX2lMYIIs9WCO1jcmIXiXum5jnfXu1WLfc7PZtO2hH+k9CDKosOFaXRBmsu8k/BGXPSoWqUpvu6WpEG9t5STk4FeAzA';
|
||||
'0aFMkSgJhj0kVLxVnactRpl3L2kgIR8bAqICFtDkvp/mkinITZjr1Vh6Jy9FmJzvhLfFUtjU2j/2bqrFn61CSrvRbRaLP6rCFegGJHNGpVfw+c24NthCwGF/SN10aPjPo6yQ3er9bc42I6AmJz5HgyfU6C4bE+LdWrML93C0iEnmQN/SYHnS1KHPXNl6NpFGITggbZQ9jwHOFILWo8wzJ4iqlJtMrNaOOLAAB7By7Fbxl4xoNz2K+w';
|
||||
|
||||
late Client client1;
|
||||
late Client client2;
|
||||
|
||||
setUpAll(() async {
|
||||
await olm.init();
|
||||
olm.get_library_version();
|
||||
await vod.init(
|
||||
wasmPath: './pkg/',
|
||||
libraryPath: './rust/target/debug/',
|
||||
);
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@
|
|||
|
||||
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,8 +32,11 @@ void main() {
|
|||
late Client client;
|
||||
|
||||
setUpAll(() async {
|
||||
await olm.init();
|
||||
olm.get_library_version();
|
||||
await vod.init(
|
||||
wasmPath: './pkg/',
|
||||
libraryPath: './rust/target/debug/',
|
||||
);
|
||||
|
||||
client = await getClient();
|
||||
});
|
||||
|
||||
|
|
@ -62,7 +65,7 @@ void main() {
|
|||
);
|
||||
expect(sent['device_keys'] != null, true);
|
||||
expect(sent['one_time_keys'] != null, true);
|
||||
expect(sent['one_time_keys'].keys.length, 66);
|
||||
expect(sent['one_time_keys'].keys.length, 33);
|
||||
expect(sent['fallback_keys'] != null, true);
|
||||
expect(sent['fallback_keys'].keys.length, 1);
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
|
|
@ -78,7 +81,7 @@ void main() {
|
|||
sent = json.decode(
|
||||
FakeMatrixApi.calledEndpoints['/client/v3/keys/upload']!.first,
|
||||
);
|
||||
expect(sent['one_time_keys'].keys.length, 46);
|
||||
expect(sent['one_time_keys'].keys.length, 13);
|
||||
expect(sent['fallback_keys'].keys.length, 0);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@
|
|||
|
||||
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,8 +35,11 @@ void main() {
|
|||
final senderKey = 'JBG7ZaPn54OBC7TuIEiylW3BZ+7WcGQhFBPB9pogbAg';
|
||||
|
||||
setUpAll(() async {
|
||||
await olm.init();
|
||||
olm.get_library_version();
|
||||
await vod.init(
|
||||
wasmPath: './pkg/',
|
||||
libraryPath: './rust/target/debug/',
|
||||
);
|
||||
|
||||
client = await getClient();
|
||||
});
|
||||
|
||||
|
|
@ -86,20 +89,19 @@ void main() {
|
|||
});
|
||||
|
||||
test('upload key', () async {
|
||||
final session = olm.OutboundGroupSession();
|
||||
session.create();
|
||||
final inbound = olm.InboundGroupSession();
|
||||
inbound.create(session.session_key());
|
||||
final session = vod.GroupSession();
|
||||
final inbound = vod.InboundGroupSession(session.sessionKey);
|
||||
|
||||
final senderKey = client.identityKey;
|
||||
final roomId = '!someroom:example.org';
|
||||
final sessionId = inbound.session_id();
|
||||
final sessionId = inbound.sessionId;
|
||||
// set a payload...
|
||||
final sessionPayload = <String, dynamic>{
|
||||
'algorithm': AlgorithmTypes.megolmV1AesSha2,
|
||||
'room_id': roomId,
|
||||
'forwarding_curve25519_key_chain': [client.identityKey],
|
||||
'session_id': sessionId,
|
||||
'session_key': inbound.export_session(1),
|
||||
'session_key': inbound.exportAt(1),
|
||||
'sender_key': senderKey,
|
||||
'sender_claimed_ed25519_key': client.fingerprintKey,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ import 'dart:convert';
|
|||
import 'dart:math';
|
||||
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,8 +54,11 @@ void main() {
|
|||
late Client client;
|
||||
|
||||
setUpAll(() async {
|
||||
await olm.init();
|
||||
olm.get_library_version();
|
||||
await vod.init(
|
||||
wasmPath: './pkg/',
|
||||
libraryPath: './rust/target/debug/',
|
||||
);
|
||||
|
||||
client = await getClient();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@
|
|||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'package:matrix/encryption/utils/base64_unpadded.dart';
|
||||
import 'package:matrix/encryption/utils/pickle_key.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
void main() {
|
||||
|
|
@ -84,4 +86,27 @@ void main() {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('toPickleKey', () {
|
||||
test('toPickleKey', () {
|
||||
const shortKey = 'abcd';
|
||||
var pickleKey = shortKey.toPickleKey();
|
||||
expect(pickleKey.length, 32, reason: 'Pickle key should be 32 bytes');
|
||||
expect(
|
||||
shortKey,
|
||||
String.fromCharCodes(pickleKey.take(4)),
|
||||
reason: 'Pickle key should match the first 32 bytes of the input',
|
||||
);
|
||||
|
||||
const longKey =
|
||||
'abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890';
|
||||
pickleKey = longKey.toPickleKey();
|
||||
expect(pickleKey.length, 32, reason: 'Pickle key should be 32 bytes');
|
||||
expect(
|
||||
pickleKey,
|
||||
Uint8List.fromList(longKey.codeUnits.take(32).toList()),
|
||||
reason: 'Pickle key should match the first 32 bytes of the input',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import 'dart:convert';
|
|||
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';
|
||||
|
|
@ -276,6 +277,15 @@ void main() async {
|
|||
});
|
||||
|
||||
test('sendAgain', () async {
|
||||
try {
|
||||
vodInit ??= vod.init(
|
||||
wasmPath: './pkg/',
|
||||
libraryPath: './rust/target/debug/',
|
||||
);
|
||||
await vodInit;
|
||||
} catch (_) {
|
||||
Logs().d('Encryption via Vodozemac not enabled');
|
||||
}
|
||||
final matrix = Client(
|
||||
'testclient',
|
||||
httpClient: FakeMatrixApi(),
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:vodozemac/vodozemac.dart' as vod;
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'fake_database.dart';
|
||||
|
||||
|
|
@ -24,13 +26,24 @@ const ssssKey = 'EsT9 RzbW VhPW yqNp cC7j ViiW 5TZB LuY4 ryyv 9guN Ysmr WDPH';
|
|||
|
||||
// key @test:fakeServer.notExisting
|
||||
const pickledOlmAccount =
|
||||
'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw';
|
||||
'huxcPifHlyiQsX7cZeMMITbka3hLeUT3ss6DLL6dV7knaD4wgAYK6gcWknkixnX8C5KMIyxzytxiNqAOhDFRE5NsET8hr2dQ8OvXX7M95eQ7/3dPi7FkPUIbvneTSGgJYNDxJdHsDJ8OBHZ3BoqUJFDbTzFfVJjEzN4G9XQwPDafZ2p5WyerOK8Twj/rvk5N+ERmkt1XgVLQl66we/BO1ugTeM3YpDHm5lTzFUitJGTIuuONsKG9mmzdAmVUJ9YIrSxwmOBdegbGA+LAl5acg5VOol3KxRgZUMJQRQ58zpBAs72oauHizv1QVoQ7uIUiCUeb9lym+TEjmApvhru/1CPHU90K5jHNZ57wb/4V9VsqBWuoNibzDWG35YTFLcx0o+1lrCIjm1QjuC0777G+L1HNw5wnppV3z/k0YujjuPS3wvOA30TjHg';
|
||||
|
||||
Future? vodInit;
|
||||
|
||||
/// only use `path` if you explicitly if you need a db on path instead of in mem
|
||||
Future<Client> getClient({
|
||||
Duration sendTimelineEventTimeout = const Duration(minutes: 1),
|
||||
String? databasePath,
|
||||
}) async {
|
||||
try {
|
||||
vodInit ??= vod.init(
|
||||
wasmPath: './pkg/',
|
||||
libraryPath: './rust/target/debug/',
|
||||
);
|
||||
await vodInit;
|
||||
} catch (_) {
|
||||
Logs().d('Encryption via Vodozemac not enabled');
|
||||
}
|
||||
final client = Client(
|
||||
logLevel: Level.verbose,
|
||||
'testclient',
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ void main() async {
|
|||
final inboundSession =
|
||||
client.encryption!.keyManager.getInboundGroupSession(
|
||||
roomid,
|
||||
outboundSession!.outboundGroupSession!.session_id(),
|
||||
outboundSession!.outboundGroupSession!.sessionId,
|
||||
)!;
|
||||
|
||||
// ensure encryption is "enabled"
|
||||
|
|
|
|||
|
|
@ -671,12 +671,16 @@ void main() {
|
|||
expect(fetchedParticipants.length, newParticipants.length);
|
||||
});
|
||||
|
||||
test('calcEncryptionHealthState', () async {
|
||||
expect(
|
||||
await room.calcEncryptionHealthState(),
|
||||
EncryptionHealthState.unverifiedDevices,
|
||||
);
|
||||
});
|
||||
test(
|
||||
'calcEncryptionHealthState',
|
||||
() async {
|
||||
expect(
|
||||
await room.calcEncryptionHealthState(),
|
||||
EncryptionHealthState.unverifiedDevices,
|
||||
);
|
||||
},
|
||||
tags: 'olm',
|
||||
);
|
||||
|
||||
test('getEventByID', () async {
|
||||
final event = await room.getEventById('1234');
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vodozemac/vodozemac.dart' as vod;
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'fake_database.dart';
|
||||
|
|
@ -27,7 +28,17 @@ void main() async {
|
|||
late Client client;
|
||||
late Room room;
|
||||
late User user1, user2;
|
||||
Future? vodInit;
|
||||
setUp(() async {
|
||||
try {
|
||||
vodInit ??= vod.init(
|
||||
wasmPath: './pkg/',
|
||||
libraryPath: './rust/target/debug/',
|
||||
);
|
||||
await vodInit;
|
||||
} catch (_) {
|
||||
Logs().d('Encryption via Vodozemac not enabled');
|
||||
}
|
||||
client = Client(
|
||||
'testclient',
|
||||
httpClient: FakeMatrixApi(),
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@
|
|||
|
||||
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,9 +39,10 @@ void main() => group(
|
|||
Client? testClientA, testClientB;
|
||||
|
||||
try {
|
||||
await olm.init();
|
||||
olm.Account();
|
||||
Logs().i('[LibOlm] Enabled');
|
||||
await vod.init(
|
||||
wasmPath: './pkg/',
|
||||
libraryPath: './rust/target/debug/',
|
||||
);
|
||||
|
||||
final homeserverUri = Uri.parse(homeserver);
|
||||
Logs().i('++++ Using homeserver $homeserverUri ++++');
|
||||
|
|
@ -229,7 +230,7 @@ void main() => group(
|
|||
var currentSessionIdA = room.client.encryption!.keyManager
|
||||
.getOutboundGroupSession(room.id)!
|
||||
.outboundGroupSession!
|
||||
.session_id();
|
||||
.sessionId;
|
||||
/*expect(room.client.encryption.keyManager
|
||||
.getInboundGroupSession(room.id, currentSessionIdA, '') !=
|
||||
null);*/
|
||||
|
|
@ -284,7 +285,7 @@ void main() => group(
|
|||
room.client.encryption!.keyManager
|
||||
.getOutboundGroupSession(room.id)!
|
||||
.outboundGroupSession!
|
||||
.session_id(),
|
||||
.sessionId,
|
||||
currentSessionIdA,
|
||||
);
|
||||
/*expect(room.client.encryption.keyManager
|
||||
|
|
@ -315,7 +316,7 @@ void main() => group(
|
|||
room.client.encryption!.keyManager
|
||||
.getOutboundGroupSession(room.id)!
|
||||
.outboundGroupSession!
|
||||
.session_id(),
|
||||
.sessionId,
|
||||
currentSessionIdA,
|
||||
);
|
||||
final inviteRoomOutboundGroupSession = inviteRoom
|
||||
|
|
@ -325,12 +326,12 @@ void main() => group(
|
|||
expect(inviteRoomOutboundGroupSession.isValid, isTrue);
|
||||
/*expect(inviteRoom.client.encryption.keyManager.getInboundGroupSession(
|
||||
inviteRoom.id,
|
||||
inviteRoomOutboundGroupSession.outboundGroupSession.session_id(),
|
||||
inviteRoomOutboundGroupSession.outboundGroupSession.sessionId,
|
||||
'') !=
|
||||
null);
|
||||
expect(room.client.encryption.keyManager.getInboundGroupSession(
|
||||
room.id,
|
||||
inviteRoomOutboundGroupSession.outboundGroupSession.session_id(),
|
||||
inviteRoomOutboundGroupSession.outboundGroupSession.sessionId,
|
||||
'') !=
|
||||
null);*/
|
||||
expect(inviteRoom.lastEvent!.body, testMessage3);
|
||||
|
|
@ -392,7 +393,7 @@ void main() => group(
|
|||
room.client.encryption!.keyManager
|
||||
.getOutboundGroupSession(room.id)!
|
||||
.outboundGroupSession!
|
||||
.session_id(),
|
||||
.sessionId,
|
||||
currentSessionIdA,
|
||||
);
|
||||
/*expect(inviteRoom.client.encryption.keyManager
|
||||
|
|
@ -440,14 +441,14 @@ void main() => group(
|
|||
room.client.encryption!.keyManager
|
||||
.getOutboundGroupSession(room.id)!
|
||||
.outboundGroupSession!
|
||||
.session_id(),
|
||||
.sessionId,
|
||||
isNot(currentSessionIdA),
|
||||
);
|
||||
}
|
||||
currentSessionIdA = room.client.encryption!.keyManager
|
||||
.getOutboundGroupSession(room.id)!
|
||||
.outboundGroupSession!
|
||||
.session_id();
|
||||
.sessionId;
|
||||
/*expect(inviteRoom.client.encryption.keyManager
|
||||
.getInboundGroupSession(inviteRoom.id, currentSessionIdA, '') !=
|
||||
null);*/
|
||||
|
|
@ -485,10 +486,6 @@ void main() => group(
|
|||
Client? testClientA, testClientB;
|
||||
|
||||
try {
|
||||
await olm.init();
|
||||
olm.Account();
|
||||
Logs().i('[LibOlm] Enabled');
|
||||
|
||||
final homeserverUri = Uri.parse(homeserver);
|
||||
Logs().i('++++ Using homeserver $homeserverUri ++++');
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue