feat: Make any SPK survive exactly one rotation

This commit is contained in:
PapaTutuWawa 2022-08-08 15:48:09 +02:00
parent b8b6bbf800
commit 1bcbf27c83
3 changed files with 58 additions and 4 deletions

View File

@ -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.';
}

View File

@ -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,

View File

@ -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);
});
} }