feat: Make any SPK survive exactly one rotation
This commit is contained in:
parent
b8b6bbf800
commit
1bcbf27c83
@ -24,3 +24,9 @@ class NotEncryptedForDeviceException implements Exception {
|
|||||||
class NoDecryptionKeyException implements Exception {
|
class NoDecryptionKeyException implements Exception {
|
||||||
String errMsg() => 'No key available for decrypting the message';
|
String errMsg() => 'No key available for decrypting the message';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Triggered by the Session Manager when the identifier of the used Signed Prekey
|
||||||
|
/// is neither the current SPK's identifier nor the old one's.
|
||||||
|
class UnknownSignedPrekeyException implements Exception {
|
||||||
|
String errMsg() => 'Unknown Signed Prekey used.';
|
||||||
|
}
|
||||||
|
@ -156,27 +156,40 @@ class OmemoSessionManager {
|
|||||||
|
|
||||||
return OmemoKeyExchange()
|
return OmemoKeyExchange()
|
||||||
..pkId = kexResult.opkId
|
..pkId = kexResult.opkId
|
||||||
..spkId = 0
|
..spkId = bundle.spkId
|
||||||
..ik = await device.ik.pk.getBytes()
|
..ik = await device.ik.pk.getBytes()
|
||||||
..ek = await kexResult.ek.pk.getBytes();
|
..ek = await kexResult.ek.pk.getBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a new session with the user at [jid] with the device [deviceId] using data
|
/// Build a new session with the user at [jid] with the device [deviceId] using data
|
||||||
/// from the key exchange [kex].
|
/// from the key exchange [kex]. In case [kex] contains an unknown Signed Prekey
|
||||||
|
/// identifier an UnknownSignedPrekeyException will be thrown.
|
||||||
Future<void> _addSessionFromKeyExchange(String jid, int deviceId, OmemoKeyExchange kex) async {
|
Future<void> _addSessionFromKeyExchange(String jid, int deviceId, OmemoKeyExchange kex) async {
|
||||||
|
// Pick the correct SPK
|
||||||
final device = await getDevice();
|
final device = await getDevice();
|
||||||
|
OmemoKeyPair? spk;
|
||||||
|
if (kex.spkId == device.spkId) {
|
||||||
|
spk = device.spk;
|
||||||
|
} else if (kex.spkId == device.oldSpkId) {
|
||||||
|
spk = device.oldSpk;
|
||||||
|
} else {
|
||||||
|
throw UnknownSignedPrekeyException();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(spk != null, 'The used SPK must be found');
|
||||||
|
|
||||||
final kexResult = await x3dhFromInitialMessage(
|
final kexResult = await x3dhFromInitialMessage(
|
||||||
X3DHMessage(
|
X3DHMessage(
|
||||||
OmemoPublicKey.fromBytes(kex.ik!, KeyPairType.ed25519),
|
OmemoPublicKey.fromBytes(kex.ik!, KeyPairType.ed25519),
|
||||||
OmemoPublicKey.fromBytes(kex.ek!, KeyPairType.x25519),
|
OmemoPublicKey.fromBytes(kex.ek!, KeyPairType.x25519),
|
||||||
kex.pkId!,
|
kex.pkId!,
|
||||||
),
|
),
|
||||||
device.spk,
|
spk!,
|
||||||
device.opks.values.elementAt(kex.pkId!),
|
device.opks.values.elementAt(kex.pkId!),
|
||||||
device.ik,
|
device.ik,
|
||||||
);
|
);
|
||||||
final ratchet = await OmemoDoubleRatchet.acceptNewSession(
|
final ratchet = await OmemoDoubleRatchet.acceptNewSession(
|
||||||
device.spk,
|
spk,
|
||||||
OmemoPublicKey.fromBytes(kex.ik!, KeyPairType.ed25519),
|
OmemoPublicKey.fromBytes(kex.ik!, KeyPairType.ed25519),
|
||||||
kexResult.sk,
|
kexResult.sk,
|
||||||
kexResult.ad,
|
kexResult.ad,
|
||||||
|
@ -249,4 +249,39 @@ void main() {
|
|||||||
expect(newDevice!.oldSpkId, oldDevice.spkId);
|
expect(newDevice!.oldSpkId, oldDevice.spkId);
|
||||||
expect(listsEqual(newDevice!.oldSpkSignature!, oldDevice.spkSignature), true);
|
expect(listsEqual(newDevice!.oldSpkSignature!, oldDevice.spkSignature), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Test accepting a session with an old SPK', () async {
|
||||||
|
const aliceJid = 'alice@server.example';
|
||||||
|
const bobJid = 'bob@other.server.example';
|
||||||
|
|
||||||
|
// Alice and Bob generate their sessions
|
||||||
|
final aliceSession = await OmemoSessionManager.generateNewIdentity(aliceJid, opkAmount: 1);
|
||||||
|
final bobSession = await OmemoSessionManager.generateNewIdentity(bobJid, opkAmount: 1);
|
||||||
|
|
||||||
|
// Alice encrypts a message for Bob
|
||||||
|
const messagePlaintext = 'Hello Bob!';
|
||||||
|
final aliceMessage = await aliceSession.encryptToJid(
|
||||||
|
bobJid,
|
||||||
|
messagePlaintext,
|
||||||
|
newSessions: [
|
||||||
|
await (await bobSession.getDevice()).toBundle(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expect(aliceMessage.encryptedKeys.length, 1);
|
||||||
|
|
||||||
|
// Alice loses her Internet connection. Bob rotates his SPK.
|
||||||
|
await bobSession.rotateSignedPrekey();
|
||||||
|
|
||||||
|
// Alice regains her Internet connection and sends the message to Bob
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Bob decrypts it
|
||||||
|
final bobMessage = await bobSession.decryptMessage(
|
||||||
|
aliceMessage.ciphertext,
|
||||||
|
aliceJid,
|
||||||
|
(await aliceSession.getDevice()).id,
|
||||||
|
aliceMessage.encryptedKeys,
|
||||||
|
);
|
||||||
|
expect(messagePlaintext, bobMessage);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user