diff --git a/lib/src/helpers.dart b/lib/src/helpers.dart index 2d0a706..5679716 100644 --- a/lib/src/helpers.dart +++ b/lib/src/helpers.dart @@ -34,3 +34,8 @@ List generateRandomBytes(int length) { return bytes; } + +/// Generate a random number between 0 inclusive and 2**32 exclusive (2**32 - 1 inclusive). +int generateRandom32BitNumber() { + return Random.secure().nextInt(4294967295 /*pow(2, 32) - 1*/); +} diff --git a/lib/src/omemo/device.dart b/lib/src/omemo/device.dart new file mode 100644 index 0000000..f56dabd --- /dev/null +++ b/lib/src/omemo/device.dart @@ -0,0 +1,101 @@ +import 'dart:convert'; +import 'package:cryptography/cryptography.dart'; +import 'package:meta/meta.dart'; +import 'package:omemo_dart/src/helpers.dart'; +import 'package:omemo_dart/src/keys.dart'; +import 'package:omemo_dart/src/omemo/bundle.dart'; +import 'package:omemo_dart/src/x3dh/x3dh.dart'; + +/// This class represents an OmemoBundle but with all keypairs belonging to the keys +@immutable +class Device { + + const Device(this.id, this.ik, this.spk, this.spkId, this.spkSignature, this.opks); + + /// Generate a completely new device, i.e. cryptographic identity. + static Future generateNewDevice({ int opkAmount = 100 }) async { + final id = generateRandom32BitNumber(); + final ik = await OmemoKeyPair.generateNewPair(KeyPairType.ed25519); + final spk = await OmemoKeyPair.generateNewPair(KeyPairType.x25519); + final spkId = generateRandom32BitNumber(); + final signature = await sig(ik, await spk.pk.getBytes()); + + final opks = {}; + for (var i = 0; i < opkAmount; i++) { + opks[i.toString()] = await OmemoKeyPair.generateNewPair(KeyPairType.x25519); + } + + return Device(id.toString(), ik, spk, spkId.toString(), signature, opks); + } + + /// The device Id + final String id; + + /// The identity key + final OmemoKeyPair ik; + + /// The signed prekey... + final OmemoKeyPair spk; + /// ...its Id, ... + final String spkId; + /// ...and its signature + final List spkSignature; + + /// Map of an id to the associated Onetime-Prekey + final Map opks; + + /// This replaces the Onetime-Prekey with id [id] with a completely new one. Returns + /// a new Device object that copies over everything but replaces said key. + Future replaceOnetimePrekey(String id) async { + final newOpk = await OmemoKeyPair.generateNewPair(KeyPairType.x25519); + + return Device( + id, + ik, + spk, + spkId, + spkSignature, + opks.map((keyId, opk) { + if (keyId == id) { + return MapEntry(id, newOpk); + } + + return MapEntry(id, opk); + }), + ); + } + + /// This replaces the Signed-Prekey with a completely new one. Returns a new Device object + /// that copies over everything but replaces the Signed-Prekey and its signature. + Future replaceSignedPrekey() async { + final newSpk = await OmemoKeyPair.generateNewPair(KeyPairType.x25519); + final newSpkId = generateRandom32BitNumber(); + final newSignature = await sig(ik, await newSpk.pk.getBytes()); + + return Device( + id, + ik, + newSpk, + newSpkId.toString(), + newSignature, + opks, + ); + } + + Future toBundle() async { + final encodedOpks = {}; + + for (final opkKey in opks.keys) { + encodedOpks[opkKey] = base64.encode(await opks[opkKey]!.pk.getBytes()); + } + + return OmemoBundle( + id, + base64.encode(await spk.pk.getBytes()), + spkId, + base64.encode(spkSignature), + base64.encode(await ik.pk.getBytes()), + encodedOpks, + ); + } +} diff --git a/lib/src/omemo/sessionmanager.dart b/lib/src/omemo/sessionmanager.dart index f8fb31b..866b72e 100644 --- a/lib/src/omemo/sessionmanager.dart +++ b/lib/src/omemo/sessionmanager.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:omemo_dart/src/crypto.dart'; import 'package:omemo_dart/src/double_ratchet/double_ratchet.dart'; import 'package:omemo_dart/src/helpers.dart'; +import 'package:omemo_dart/src/omemo/device.dart'; import 'package:synchronized/synchronized.dart'; /// The info used for when encrypting the AES key for the actual payload. @@ -21,8 +22,15 @@ class EncryptionResult { class OmemoSessionManager { - OmemoSessionManager() : _ratchetMap = {}, _deviceMap = {}, _lock = Lock(); + OmemoSessionManager(this.device) : _ratchetMap = {}, _deviceMap = {}, _lock = Lock(); + /// Generate a new cryptographic identity. + static Future generateNewIdentity({ int opkAmount = 100 }) async { + final device = await Device.generateNewDevice(opkAmount: opkAmount); + + return OmemoSessionManager(device); + } + /// Lock for _ratchetMap and _bundleMap final Lock _lock; @@ -32,6 +40,9 @@ class OmemoSessionManager { /// Mapping of a bare Jid to its Device Ids final Map> _deviceMap; + /// Our own keys + Device device; + /// Add a session [ratchet] with the [deviceId] to the internal tracking state. Future addSession(String jid, String deviceId, OmemoDoubleRatchet ratchet) async { await _lock.synchronized(() async {