???: Move code around
This commit is contained in:
parent
d3c8d813a9
commit
4e3e20f08c
@ -1,6 +1,8 @@
|
|||||||
library omemo_dart;
|
library omemo_dart;
|
||||||
|
|
||||||
export 'src/bundle.dart';
|
export 'src/bundle.dart';
|
||||||
|
export 'src/double_ratchet.dart';
|
||||||
export 'src/errors.dart';
|
export 'src/errors.dart';
|
||||||
|
export 'src/helpers.dart';
|
||||||
export 'src/key.dart';
|
export 'src/key.dart';
|
||||||
export 'src/x3dh.dart';
|
export 'src/x3dh.dart';
|
||||||
|
@ -1,281 +1,196 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:cryptography/cryptography.dart';
|
import 'package:cryptography/cryptography.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
import 'package:omemo_dart/protobuf/schema.pb.dart';
|
import 'package:omemo_dart/protobuf/schema.pb.dart';
|
||||||
import 'package:omemo_dart/src/bundle.dart';
|
import 'package:omemo_dart/src/double_ratchet/crypto.dart';
|
||||||
|
import 'package:omemo_dart/src/double_ratchet/kdf.dart';
|
||||||
import 'package:omemo_dart/src/helpers.dart';
|
import 'package:omemo_dart/src/helpers.dart';
|
||||||
import 'package:omemo_dart/src/key.dart';
|
import 'package:omemo_dart/src/key.dart';
|
||||||
import 'package:omemo_dart/src/x3dh.dart';
|
import 'package:omemo_dart/src/x3dh.dart';
|
||||||
|
|
||||||
class OmemoRatchetStepResult {
|
/// Amount of messages we may skip per session
|
||||||
|
const maxSkip = 1000;
|
||||||
|
|
||||||
const OmemoRatchetStepResult(this.header, this.cipherText);
|
class RatchetStep {
|
||||||
final List<int> header;
|
|
||||||
final List<int> cipherText;
|
const RatchetStep(this.header, this.ciphertext);
|
||||||
|
final OMEMOMessage header;
|
||||||
|
final List<int> ciphertext;
|
||||||
}
|
}
|
||||||
|
|
||||||
class OmemoEncryptionResult {
|
@immutable
|
||||||
|
class SkippedKey {
|
||||||
|
|
||||||
const OmemoEncryptionResult(this.cipherText, this.keys);
|
const SkippedKey(this.dh, this.n);
|
||||||
/// The encrypted plaintext
|
final OmemoPublicKey dh;
|
||||||
final List<int> cipherText;
|
final int n;
|
||||||
/// Mapping between Device id and the key to decrypt cipherText;
|
|
||||||
final Map<String, List<int>> keys;
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is SkippedKey && other.dh == dh && other.n == n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => dh.hashCode ^ n.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The session state of one party
|
class OmemoDoubleRatchet {
|
||||||
class AliceOmemoSession {
|
|
||||||
|
|
||||||
AliceOmemoSession(
|
OmemoDoubleRatchet(
|
||||||
this.dhs,
|
this.dhs, // DHs
|
||||||
this.dhr,
|
this.dhr, // DHr
|
||||||
this.ek,
|
this.rk, // RK
|
||||||
this.rk,
|
this.cks, // CKs
|
||||||
this.cks,
|
this.ckr, // CKr
|
||||||
this.ckr,
|
this.ns, // Ns
|
||||||
this.ns,
|
this.nr, // Nr
|
||||||
this.nr,
|
this.pn, // Pn
|
||||||
this.pn,
|
this.sessionAd,
|
||||||
// this.skippedMessages,
|
|
||||||
this.ad,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/// The Diffie-Hellman sending key pair
|
/// Sending DH keypair
|
||||||
final OmemoKeyPair dhs;
|
OmemoKeyPair dhs;
|
||||||
|
|
||||||
/// The Diffie-Hellman receiving key pair
|
/// Receiving Public key
|
||||||
final OmemoPublicKey dhr;
|
OmemoPublicKey? dhr;
|
||||||
|
|
||||||
/// The EK used by X3DH
|
/// 32 byte Root Key
|
||||||
final OmemoKeyPair ek;
|
|
||||||
|
|
||||||
/// The Root Key
|
|
||||||
List<int> rk;
|
List<int> rk;
|
||||||
|
|
||||||
/// Sending Chain Key
|
/// Sending and receiving Chain Keys
|
||||||
List<int> cks;
|
List<int>? cks;
|
||||||
|
|
||||||
/// Receiving Chain Key
|
|
||||||
List<int>? ckr;
|
List<int>? ckr;
|
||||||
|
|
||||||
/// Message number for sending
|
/// Sending and receiving message numbers
|
||||||
int ns;
|
int ns;
|
||||||
|
|
||||||
/// Message number for receiving
|
|
||||||
int nr;
|
int nr;
|
||||||
|
|
||||||
/// Number of messages in the previous sending chain
|
/// Previous sending chain number
|
||||||
int pn;
|
int pn;
|
||||||
|
|
||||||
/// The associated data from the X3DH
|
|
||||||
final List<int> ad;
|
|
||||||
|
|
||||||
// TODO(PapaTutuWawa): Track skipped over message keys
|
final List<int> sessionAd;
|
||||||
|
|
||||||
static Future<AliceOmemoSession> newSession(OmemoBundle bundle, OmemoKeyPair ik) async {
|
final Map<SkippedKey, List<int>> mkSkipped = {};
|
||||||
// TODO(PapaTutuWawa): Error handling
|
|
||||||
final x3dhResult = await x3dhFromBundle(bundle, ik);
|
/// This is performed by the initiating entity
|
||||||
|
static Future<OmemoDoubleRatchet> initiateNewSession(OmemoPublicKey spk, List<int> sk, List<int> ad) async {
|
||||||
final dhs = await OmemoKeyPair.generateNewPair(KeyPairType.x25519);
|
final dhs = await OmemoKeyPair.generateNewPair(KeyPairType.x25519);
|
||||||
final dhr = bundle.ik;
|
final dhr = spk;
|
||||||
final ek = x3dhResult.ek;
|
final rk = await kdfRk(sk, await dh(dhs, dhr, 0));
|
||||||
final sk = x3dhResult.sk;
|
final cks = rk;
|
||||||
final kdfRkResult = await kdfRk(sk, await dh(dhs, dhr, 2));
|
|
||||||
|
return OmemoDoubleRatchet(
|
||||||
return AliceOmemoSession(
|
|
||||||
dhs,
|
dhs,
|
||||||
dhr,
|
dhr,
|
||||||
ek,
|
rk,
|
||||||
kdfRkResult.rk,
|
cks,
|
||||||
kdfRkResult.ck,
|
|
||||||
null,
|
null,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
x3dhResult.ad,
|
ad,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The associated_data parameter is implicit as it belongs to the session
|
/// This is performed by the accepting entity
|
||||||
Future<List<int>> _encrypt(List<int> mk, List<int> plaintext, List<int> associatedData) async {
|
static Future<OmemoDoubleRatchet> acceptNewSession(OmemoKeyPair spk, List<int> sk, List<int> ad) async {
|
||||||
final algorithm = Hkdf(
|
final dhs = spk;
|
||||||
hmac: Hmac(Sha256()),
|
return OmemoDoubleRatchet(
|
||||||
outputLength: 80,
|
dhs,
|
||||||
|
null,
|
||||||
|
sk,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
ad,
|
||||||
);
|
);
|
||||||
final hkdfResult = await algorithm.deriveKey(
|
|
||||||
secretKey: SecretKey(mk),
|
|
||||||
nonce: List<int>.filled(32, 0x00),
|
|
||||||
info: utf8.encode(encryptHkdfInfoString),
|
|
||||||
);
|
|
||||||
final bytes = await hkdfResult.extractBytes();
|
|
||||||
|
|
||||||
final encKey = bytes.sublist(0, 32);
|
|
||||||
final authKey = bytes.sublist(32, 64);
|
|
||||||
final iv = bytes.sublist(64, 82);
|
|
||||||
|
|
||||||
// TODO(PapaTutuWawa): Remove once done
|
|
||||||
assert(encKey.length == 32);
|
|
||||||
assert(authKey.length == 32);
|
|
||||||
assert(iv.length == 16);
|
|
||||||
|
|
||||||
// 32 = 256 / 8
|
|
||||||
final encodedPlaintext = pkcs7padding(plaintext, 32);
|
|
||||||
|
|
||||||
final aesAlgorithm = AesCbc.with256bits(
|
|
||||||
macAlgorithm: Hmac.sha256(),
|
|
||||||
);
|
|
||||||
final secretBox = await aesAlgorithm.encrypt(
|
|
||||||
encodedPlaintext,
|
|
||||||
secretKey: SecretKey(encKey),
|
|
||||||
nonce: iv,
|
|
||||||
);
|
|
||||||
|
|
||||||
final ad_ = associatedData.sublist(0, ad.length);
|
|
||||||
final message = OMEMOMessage.fromBuffer(associatedData.sublist(ad.length))
|
|
||||||
..ciphertext = secretBox.cipherText;
|
|
||||||
final messageBytes = message.writeToBuffer();
|
|
||||||
|
|
||||||
final input = concat([ad_, messageBytes]);
|
|
||||||
final authBytes = (await Hmac.sha256().calculateMac(
|
|
||||||
input,
|
|
||||||
secretKey: SecretKey(authKey),
|
|
||||||
)).bytes.sublist(0, 16);
|
|
||||||
|
|
||||||
final authenticatedMessage = OMEMOAuthenticatedMessage()
|
|
||||||
..mac = authBytes
|
|
||||||
..message = messageBytes;
|
|
||||||
|
|
||||||
return authenticatedMessage.writeToBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<int>> ratchetStep(List<int> plaintext) async {
|
|
||||||
final kdfResult = await kdfCk(cks);
|
|
||||||
final message = OMEMOMessage()
|
|
||||||
..dhPub = await dhs.pk.getBytes()
|
|
||||||
..pn = pn
|
|
||||||
..n = ns;
|
|
||||||
final header = message.writeToBuffer();
|
|
||||||
|
|
||||||
cks = kdfResult.ck;
|
|
||||||
ns++;
|
|
||||||
|
|
||||||
return _encrypt(
|
|
||||||
kdfResult.mk,
|
|
||||||
plaintext,
|
|
||||||
concat([ad, header]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<OmemoEncryptionResult> encryptForSessions(List<AliceOmemoSession> sessions, String plaintext) async {
|
|
||||||
// TODO(PapaTutuWawa): Generate random data
|
|
||||||
final key = List<int>.filled(32, 0x0);
|
|
||||||
final algorithm = Hkdf(
|
|
||||||
hmac: Hmac(Sha256()),
|
|
||||||
outputLength: 80,
|
|
||||||
);
|
|
||||||
final result = await algorithm.deriveKey(
|
|
||||||
secretKey: SecretKey(key),
|
|
||||||
nonce: List<int>.filled(32, 0x0),
|
|
||||||
info: utf8.encode(encryptionHkdfInfoString),
|
|
||||||
);
|
|
||||||
final bytes = await result.extractBytes();
|
|
||||||
|
|
||||||
final encKey = bytes.sublist(0, 32);
|
|
||||||
final authKey = bytes.sublist(32, 64);
|
|
||||||
final iv = bytes.sublist(64, 80);
|
|
||||||
|
|
||||||
final encodedPlaintext = pkcs7padding(utf8.encode(plaintext), 32);
|
|
||||||
final aesAlgorithm = AesCbc.with256bits(
|
|
||||||
macAlgorithm: Hmac.sha256(),
|
|
||||||
);
|
|
||||||
final secretBox = await aesAlgorithm.encrypt(
|
|
||||||
encodedPlaintext,
|
|
||||||
secretKey: SecretKey(encKey),
|
|
||||||
nonce: iv,
|
|
||||||
);
|
|
||||||
final hmac = (await Hmac.sha256().calculateMac(
|
|
||||||
secretBox.cipherText,
|
|
||||||
secretKey: SecretKey(authKey),
|
|
||||||
)).bytes.sublist(0, 16);
|
|
||||||
|
|
||||||
final keyData = concat([encKey, hmac]);
|
|
||||||
|
|
||||||
final keyMap = <String, List<int>>{};
|
|
||||||
for (final session in sessions) {
|
|
||||||
final ratchetKey = await session.ratchetStep(keyData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return OmemoEncryptionResult(
|
Future<RatchetStep> ratchetEncrypt(List<int> plaintext) async {
|
||||||
secretBox.cipherText,
|
final newCks = await kdfCk(cks!, kdfCkNextChainKey);
|
||||||
keyMap,
|
final mk = await kdfCk(cks!, kdfCkNextMessageKey);
|
||||||
);
|
|
||||||
}
|
cks = newCks;
|
||||||
|
final header = OMEMOMessage()
|
||||||
/// Result of the KDF_RK function from the Double Ratchet spec.
|
..n = ns
|
||||||
class KdfRkResult {
|
..pn = pn
|
||||||
|
..dhPub = await dhs.pk.getBytes();
|
||||||
const KdfRkResult(this.rk, this.ck);
|
|
||||||
/// 32 byte Root Key
|
ns++;
|
||||||
final List<int> rk;
|
|
||||||
|
return RatchetStep(
|
||||||
/// 32 byte Chain Key
|
header,
|
||||||
final List<int> ck;
|
await encrypt(mk, plaintext, concat([sessionAd, header.writeToBuffer()]), sessionAd),
|
||||||
}
|
);
|
||||||
|
}
|
||||||
/// Result of the KDF_CK function from the Double Ratchet spec.
|
|
||||||
class KdfCkResult {
|
Future<List<int>?> trySkippedMessageKeys(OMEMOMessage header, List<int> ciphertext) async {
|
||||||
|
final key = SkippedKey(
|
||||||
const KdfCkResult(this.ck, this.mk);
|
// TODO(PapaTutuWawa): Is this correct
|
||||||
/// 32 byte Chain Key
|
OmemoPublicKey.fromBytes(header.dhPub, KeyPairType.ed25519),
|
||||||
final List<int> ck;
|
header.n,
|
||||||
|
);
|
||||||
/// 32 byte Message Key
|
if (mkSkipped.containsKey(key)) {
|
||||||
final List<int> mk;
|
final mk = mkSkipped[key]!;
|
||||||
}
|
mkSkipped.remove(key);
|
||||||
|
|
||||||
/// Amount of messages we may skip per session
|
return decrypt(mk, ciphertext, concat([sessionAd, header.writeToBuffer()]), sessionAd);
|
||||||
const maxSkip = 1000;
|
}
|
||||||
|
|
||||||
/// Info string for KDF_RK
|
return null;
|
||||||
const kdfRkInfoString = 'OMEMO Root Chain';
|
}
|
||||||
|
|
||||||
/// Info string for ENCRYPT
|
Future<void> skipMessageKeys(int until) async {
|
||||||
const encryptHkdfInfoString = 'OMEMO Message Key Material';
|
if (nr + maxSkip < until) {
|
||||||
|
// TODO(PapaTutuWawa): Custom exception
|
||||||
/// Info string for encrypting a message
|
throw Exception();
|
||||||
const encryptionHkdfInfoString = 'OMEMO Payload';
|
}
|
||||||
|
|
||||||
/// Flags for KDF_CK
|
if (ckr != null) {
|
||||||
const kdfCkNextMessageKey = 0x01;
|
while (nr < until) {
|
||||||
const kdfCkNextChainKey = 0x02;
|
final newCkr = await kdfCk(ckr!, kdfCkNextChainKey);
|
||||||
|
final mk = await kdfCk(ckr!, kdfCkNextMessageKey);
|
||||||
Future<KdfRkResult> kdfRk(List<int> rk, List<int> dhOut) async {
|
ckr = newCkr;
|
||||||
final algorithm = Hkdf(
|
mkSkipped[SkippedKey(dhr!, nr)] = mk;
|
||||||
hmac: Hmac(Sha256()),
|
nr++;
|
||||||
outputLength: 32,
|
}
|
||||||
);
|
}
|
||||||
final result = await algorithm.deriveKey(
|
}
|
||||||
secretKey: SecretKey(dhOut),
|
|
||||||
nonce: rk,
|
Future<void> dhRatchet(OMEMOMessage header) async {
|
||||||
info: utf8.encode(kdfRkInfoString),
|
pn = header.n;
|
||||||
);
|
ns = 0;
|
||||||
|
nr = 0;
|
||||||
// TODO(PapaTutuWawa): Does the rk in the tuple (rk, ck) refer to the input rk?
|
dhr = OmemoPublicKey.fromBytes(header.dhPub, KeyPairType.ed25519);
|
||||||
return KdfRkResult(rk, await result.extractBytes());
|
|
||||||
}
|
final newRk = await kdfRk(rk, await dh(dhs, dhr!, 2));
|
||||||
|
rk = newRk;
|
||||||
Future<KdfCkResult> kdfCk(List<int> ck) async {
|
ckr = newRk;
|
||||||
final hkdf = Hkdf(hmac: Hmac(Sha256()), outputLength: 32);
|
dhs = await OmemoKeyPair.generateNewPair(KeyPairType.x25519);
|
||||||
final newCk = await hkdf.deriveKey(
|
final newNewRk = await kdfRk(rk, await dh(dhs, dhr!, 2));
|
||||||
secretKey: SecretKey(ck),
|
rk = newNewRk;
|
||||||
nonce: [kdfCkNextChainKey],
|
cks = newNewRk;
|
||||||
);
|
}
|
||||||
final mk = await hkdf.deriveKey(
|
|
||||||
secretKey: SecretKey(ck),
|
Future<List<int>> ratchetDecrypt(OMEMOMessage header, List<int> ciphertext) async {
|
||||||
nonce: [kdfCkNextMessageKey],
|
// Check if we skipped too many messages
|
||||||
);
|
final plaintext = await trySkippedMessageKeys(header, ciphertext);
|
||||||
|
if (plaintext != null) {
|
||||||
return KdfCkResult(
|
return plaintext;
|
||||||
await newCk.extractBytes(),
|
}
|
||||||
await mk.extractBytes(),
|
|
||||||
);
|
if (header.dhPub != await dhr?.getBytes()) {
|
||||||
|
await skipMessageKeys(header.pn);
|
||||||
|
await dhRatchet(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
await skipMessageKeys(header.n);
|
||||||
|
final newCkr = await kdfCk(ckr!, kdfCkNextChainKey);
|
||||||
|
final mk = await kdfCk(ckr!, kdfCkNextMessageKey);
|
||||||
|
ckr = newCkr;
|
||||||
|
nr++;
|
||||||
|
|
||||||
|
return decrypt(mk, ciphertext, concat([sessionAd, header.writeToBuffer()]), sessionAd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
109
lib/src/double_ratchet/crypto.dart
Normal file
109
lib/src/double_ratchet/crypto.dart
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:cryptography/cryptography.dart';
|
||||||
|
import 'package:omemo_dart/protobuf/schema.pb.dart';
|
||||||
|
import 'package:omemo_dart/src/helpers.dart';
|
||||||
|
|
||||||
|
/// Info string for ENCRYPT
|
||||||
|
const encryptHkdfInfoString = 'OMEMO Message Key Material';
|
||||||
|
|
||||||
|
/// cryptography _really_ wants to check the MAC output from AES-256-CBC. Since
|
||||||
|
/// we don't have it, we need the MAC check to always "pass".
|
||||||
|
class NoMacSecretBox extends SecretBox {
|
||||||
|
NoMacSecretBox(super.cipherText, { required super.nonce }) : super(mac: Mac.empty);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> checkMac({
|
||||||
|
required MacAlgorithm macAlgorithm,
|
||||||
|
required SecretKey secretKey,
|
||||||
|
required List<int> aad,
|
||||||
|
}) async {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signals ENCRYPT function as specified by OMEMO 0.8.0.
|
||||||
|
/// Encrypt [plaintext] using the message key [mk], given associated_data [associatedData]
|
||||||
|
/// and the AD output from the X3DH [sessionAd].
|
||||||
|
Future<List<int>> encrypt(List<int> mk, List<int> plaintext, List<int> associatedData, List<int> sessionAd) async {
|
||||||
|
final hkdf = Hkdf(
|
||||||
|
hmac: Hmac(Sha256()),
|
||||||
|
outputLength: 80,
|
||||||
|
);
|
||||||
|
final hkdfResult = await hkdf.deriveKey(
|
||||||
|
secretKey: SecretKey(mk),
|
||||||
|
nonce: List<int>.filled(32, 0x0),
|
||||||
|
info: utf8.encode(encryptHkdfInfoString),
|
||||||
|
);
|
||||||
|
final hkdfBytes = await hkdfResult.extractBytes();
|
||||||
|
|
||||||
|
// Split hkdfBytes into encryption, authentication key and IV
|
||||||
|
final encryptionKey = hkdfBytes.sublist(0, 32);
|
||||||
|
final authenticationKey = hkdfBytes.sublist(32, 64);
|
||||||
|
final iv = hkdfBytes.sublist(64, 80);
|
||||||
|
|
||||||
|
final aesResult = await AesCbc.with256bits(
|
||||||
|
macAlgorithm: MacAlgorithm.empty,
|
||||||
|
).encrypt(
|
||||||
|
plaintext,
|
||||||
|
secretKey: SecretKey(encryptionKey),
|
||||||
|
nonce: iv,
|
||||||
|
);
|
||||||
|
|
||||||
|
final header = OMEMOMessage.fromBuffer(associatedData.sublist(sessionAd.length))
|
||||||
|
..ciphertext = aesResult.cipherText;
|
||||||
|
final headerBytes = header.writeToBuffer();
|
||||||
|
final hmacInput = concat([sessionAd, headerBytes]);
|
||||||
|
final hmacResult = (await Hmac.sha256().calculateMac(
|
||||||
|
hmacInput,
|
||||||
|
secretKey: SecretKey(authenticationKey),
|
||||||
|
)).bytes.sublist(0, 16);
|
||||||
|
|
||||||
|
final message = OMEMOAuthenticatedMessage()
|
||||||
|
..mac = hmacResult
|
||||||
|
..message = headerBytes;
|
||||||
|
return message.writeToBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signals DECRYPT function as specified by OMEMO 0.8.0.
|
||||||
|
/// Decrypt [ciphertext] with the message key [mk], given the associated_data [associatedData]
|
||||||
|
/// and the AD output from the X3DH.
|
||||||
|
Future<List<int>> decrypt(List<int> mk, List<int> ciphertext, List<int> associatedData, List<int> sessionAd) async {
|
||||||
|
// Generate the keys and iv from mk
|
||||||
|
final hkdf = Hkdf(
|
||||||
|
hmac: Hmac(Sha256()),
|
||||||
|
outputLength: 80,
|
||||||
|
);
|
||||||
|
final hkdfResult = await hkdf.deriveKey(
|
||||||
|
secretKey: SecretKey(mk),
|
||||||
|
nonce: List<int>.filled(32, 0x0),
|
||||||
|
info: utf8.encode(encryptHkdfInfoString),
|
||||||
|
);
|
||||||
|
final hkdfBytes = await hkdfResult.extractBytes();
|
||||||
|
|
||||||
|
// Split hkdfBytes into encryption, authentication key and IV
|
||||||
|
final encryptionKey = hkdfBytes.sublist(0, 32);
|
||||||
|
final authenticationKey = hkdfBytes.sublist(32, 64);
|
||||||
|
final iv = hkdfBytes.sublist(64, 80);
|
||||||
|
|
||||||
|
// Assumption ciphertext is a OMEMOAuthenticatedMessage
|
||||||
|
final message = OMEMOAuthenticatedMessage.fromBuffer(ciphertext);
|
||||||
|
final header = OMEMOMessage.fromBuffer(message.message);
|
||||||
|
|
||||||
|
final hmacInput = concat([sessionAd, header.writeToBuffer()]);
|
||||||
|
final hmacResult = (await Hmac.sha256().calculateMac(
|
||||||
|
hmacInput,
|
||||||
|
secretKey: SecretKey(authenticationKey),
|
||||||
|
)).bytes.sublist(0, 16);
|
||||||
|
|
||||||
|
// TODO(PapaTutuWawa): Check the HMAC result
|
||||||
|
|
||||||
|
final plaintext = await AesCbc.with256bits(
|
||||||
|
macAlgorithm: MacAlgorithm.empty,
|
||||||
|
).decrypt(
|
||||||
|
NoMacSecretBox(
|
||||||
|
header.ciphertext,
|
||||||
|
nonce: iv,
|
||||||
|
),
|
||||||
|
secretKey: SecretKey(encryptionKey),
|
||||||
|
);
|
||||||
|
|
||||||
|
return plaintext;
|
||||||
|
}
|
35
lib/src/double_ratchet/kdf.dart
Normal file
35
lib/src/double_ratchet/kdf.dart
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:cryptography/cryptography.dart';
|
||||||
|
|
||||||
|
/// Info string for KDF_RK
|
||||||
|
const kdfRkInfoString = 'OMEMO Root Chain';
|
||||||
|
|
||||||
|
/// Flags for KDF_CK
|
||||||
|
const kdfCkNextMessageKey = 0x01;
|
||||||
|
const kdfCkNextChainKey = 0x02;
|
||||||
|
|
||||||
|
/// Signals KDF_CK function as specified by OMEMO 0.8.0.
|
||||||
|
Future<List<int>> kdfCk(List<int> ck, int constant) async {
|
||||||
|
final hkdf = Hkdf(hmac: Hmac(Sha256()), outputLength: 32);
|
||||||
|
final result = await hkdf.deriveKey(
|
||||||
|
secretKey: SecretKey(ck),
|
||||||
|
nonce: [constant],
|
||||||
|
);
|
||||||
|
|
||||||
|
return result.extractBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signals KDF_RK function as specified by OMEMO 0.8.0.
|
||||||
|
Future<List<int>> kdfRk(List<int> rk, List<int> dhOut) async {
|
||||||
|
final algorithm = Hkdf(
|
||||||
|
hmac: Hmac(Sha256()),
|
||||||
|
outputLength: 32,
|
||||||
|
);
|
||||||
|
final result = await algorithm.deriveKey(
|
||||||
|
secretKey: SecretKey(dhOut),
|
||||||
|
nonce: rk,
|
||||||
|
info: utf8.encode(kdfRkInfoString),
|
||||||
|
);
|
||||||
|
|
||||||
|
return result.extractBytes();
|
||||||
|
}
|
@ -59,6 +59,9 @@ Future<List<int>> dh(OmemoKeyPair kp, OmemoPublicKey pk, int identityKey) async
|
|||||||
ckp = await kp.toCurve25519();
|
ckp = await kp.toCurve25519();
|
||||||
} else if (identityKey == 2) {
|
} else if (identityKey == 2) {
|
||||||
cpk = await pk.toCurve25519();
|
cpk = await pk.toCurve25519();
|
||||||
|
} else if (identityKey == 3) {
|
||||||
|
ckp = await kp.toCurve25519();
|
||||||
|
cpk = await pk.toCurve25519();
|
||||||
}
|
}
|
||||||
|
|
||||||
final shared = await Cryptography.instance.x25519().sharedSecretKey(
|
final shared = await Cryptography.instance.x25519().sharedSecretKey(
|
||||||
|
109
test/double_ratchet_test.dart
Normal file
109
test/double_ratchet_test.dart
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:cryptography/cryptography.dart';
|
||||||
|
import 'package:omemo_dart/omemo_dart.dart';
|
||||||
|
import 'package:omemo_dart/protobuf/schema.pb.dart';
|
||||||
|
import 'package:omemo_dart/src/double_ratchet/crypto.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('Test encrypting and decrypting', () async {
|
||||||
|
final sessionAd = List<int>.filled(32, 0x0);
|
||||||
|
final mk = List<int>.filled(32, 0x1);
|
||||||
|
final plaintext = utf8.encode('Hallo');
|
||||||
|
final header = OMEMOMessage()
|
||||||
|
..n = 0
|
||||||
|
..pn = 0
|
||||||
|
..dhPub = List<int>.empty();
|
||||||
|
final asd = concat([sessionAd, header.writeToBuffer()]);
|
||||||
|
|
||||||
|
final ciphertext = await encrypt(
|
||||||
|
mk,
|
||||||
|
plaintext,
|
||||||
|
asd,
|
||||||
|
sessionAd,
|
||||||
|
);
|
||||||
|
|
||||||
|
final decrypted = await decrypt(
|
||||||
|
mk,
|
||||||
|
ciphertext,
|
||||||
|
asd,
|
||||||
|
sessionAd,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(decrypted, plaintext);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
test('Test the Double Ratchet', () async {
|
||||||
|
// Generate keys
|
||||||
|
final ikAlice = await OmemoKeyPair.generateNewPair(KeyPairType.ed25519);
|
||||||
|
final ikBob = await OmemoKeyPair.generateNewPair(KeyPairType.ed25519);
|
||||||
|
final spkBob = await OmemoKeyPair.generateNewPair(KeyPairType.x25519);
|
||||||
|
final opkBob = await OmemoKeyPair.generateNewPair(KeyPairType.x25519);
|
||||||
|
final bundleBob = OmemoBundle(
|
||||||
|
'1',
|
||||||
|
await spkBob.pk.asBase64(),
|
||||||
|
'3',
|
||||||
|
base64Encode(
|
||||||
|
await sig(ikBob, await spkBob.pk.getBytes()),
|
||||||
|
),
|
||||||
|
//'Q5in+/L4kJixEX692h6mJkPMyp4I3SlQ84L0E7ipPzqfPHOMiraUlqG2vG/O8wvFjLsKYZpPBraga9IvwhqVDA==',
|
||||||
|
await ikBob.pk.asBase64(),
|
||||||
|
{
|
||||||
|
'2': await opkBob.pk.asBase64(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Alice does X3DH
|
||||||
|
final resultAlice = await x3dhFromBundle(bundleBob, ikAlice);
|
||||||
|
|
||||||
|
// Alice sends the inital message to Bob
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Bob does X3DH
|
||||||
|
final resultBob = await x3dhFromInitialMessage(
|
||||||
|
X3DHMessage(
|
||||||
|
ikAlice.pk,
|
||||||
|
resultAlice.ek.pk,
|
||||||
|
'2',
|
||||||
|
),
|
||||||
|
spkBob,
|
||||||
|
opkBob,
|
||||||
|
ikBob,
|
||||||
|
);
|
||||||
|
|
||||||
|
print('X3DH key exchange done');
|
||||||
|
|
||||||
|
// Alice and Bob now share sk as a common secret and ad
|
||||||
|
final alicesRatchet = await OmemoDoubleRatchet.initiateNewSession(
|
||||||
|
spkBob.pk,
|
||||||
|
resultAlice.sk,
|
||||||
|
resultAlice.ad,
|
||||||
|
);
|
||||||
|
final bobsRatchet = await OmemoDoubleRatchet.acceptNewSession(
|
||||||
|
spkBob,
|
||||||
|
resultBob.sk,
|
||||||
|
resultBob.ad,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(alicesRatchet.sessionAd, bobsRatchet.sessionAd);
|
||||||
|
//expect(await alicesRatchet.dhr.getBytes(), await ikBob.pk.getBytes());
|
||||||
|
|
||||||
|
// Alice encrypts a message
|
||||||
|
final aliceRatchetResult = await alicesRatchet.ratchetEncrypt(utf8.encode('Hello Bob'));
|
||||||
|
print('Alice sent the message');
|
||||||
|
|
||||||
|
// Alice sends it to Bob
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Bob tries to decrypt it
|
||||||
|
final bobRatchetResult = await bobsRatchet.ratchetDecrypt(
|
||||||
|
aliceRatchetResult.header,
|
||||||
|
aliceRatchetResult.ciphertext,
|
||||||
|
);
|
||||||
|
print('Bob decrypted the message');
|
||||||
|
|
||||||
|
expect(utf8.encode('Hello Bob'), bobRatchetResult);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user