???: Move code around
This commit is contained in:
parent
d3c8d813a9
commit
4e3e20f08c
@ -1,6 +1,8 @@
|
||||
library omemo_dart;
|
||||
|
||||
export 'src/bundle.dart';
|
||||
export 'src/double_ratchet.dart';
|
||||
export 'src/errors.dart';
|
||||
export 'src/helpers.dart';
|
||||
export 'src/key.dart';
|
||||
export 'src/x3dh.dart';
|
||||
|
@ -1,281 +1,196 @@
|
||||
import 'dart:convert';
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:meta/meta.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/key.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);
|
||||
final List<int> header;
|
||||
final List<int> cipherText;
|
||||
class RatchetStep {
|
||||
|
||||
const RatchetStep(this.header, this.ciphertext);
|
||||
final OMEMOMessage header;
|
||||
final List<int> ciphertext;
|
||||
}
|
||||
|
||||
class OmemoEncryptionResult {
|
||||
@immutable
|
||||
class SkippedKey {
|
||||
|
||||
const OmemoEncryptionResult(this.cipherText, this.keys);
|
||||
/// The encrypted plaintext
|
||||
final List<int> cipherText;
|
||||
/// Mapping between Device id and the key to decrypt cipherText;
|
||||
final Map<String, List<int>> keys;
|
||||
const SkippedKey(this.dh, this.n);
|
||||
final OmemoPublicKey dh;
|
||||
final int n;
|
||||
|
||||
@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 AliceOmemoSession {
|
||||
class OmemoDoubleRatchet {
|
||||
|
||||
AliceOmemoSession(
|
||||
this.dhs,
|
||||
this.dhr,
|
||||
this.ek,
|
||||
this.rk,
|
||||
this.cks,
|
||||
this.ckr,
|
||||
this.ns,
|
||||
this.nr,
|
||||
this.pn,
|
||||
// this.skippedMessages,
|
||||
this.ad,
|
||||
OmemoDoubleRatchet(
|
||||
this.dhs, // DHs
|
||||
this.dhr, // DHr
|
||||
this.rk, // RK
|
||||
this.cks, // CKs
|
||||
this.ckr, // CKr
|
||||
this.ns, // Ns
|
||||
this.nr, // Nr
|
||||
this.pn, // Pn
|
||||
this.sessionAd,
|
||||
);
|
||||
|
||||
/// The Diffie-Hellman sending key pair
|
||||
final OmemoKeyPair dhs;
|
||||
/// Sending DH keypair
|
||||
OmemoKeyPair dhs;
|
||||
|
||||
/// The Diffie-Hellman receiving key pair
|
||||
final OmemoPublicKey dhr;
|
||||
/// Receiving Public key
|
||||
OmemoPublicKey? dhr;
|
||||
|
||||
/// The EK used by X3DH
|
||||
final OmemoKeyPair ek;
|
||||
|
||||
/// The Root Key
|
||||
/// 32 byte Root Key
|
||||
List<int> rk;
|
||||
|
||||
/// Sending Chain Key
|
||||
List<int> cks;
|
||||
|
||||
/// Receiving Chain Key
|
||||
/// Sending and receiving Chain Keys
|
||||
List<int>? cks;
|
||||
List<int>? ckr;
|
||||
|
||||
/// Message number for sending
|
||||
/// Sending and receiving message numbers
|
||||
int ns;
|
||||
|
||||
/// Message number for receiving
|
||||
int nr;
|
||||
|
||||
/// Number of messages in the previous sending chain
|
||||
/// Previous sending chain number
|
||||
int pn;
|
||||
|
||||
/// The associated data from the X3DH
|
||||
final List<int> ad;
|
||||
final List<int> sessionAd;
|
||||
|
||||
// TODO(PapaTutuWawa): Track skipped over message keys
|
||||
final Map<SkippedKey, List<int>> mkSkipped = {};
|
||||
|
||||
static Future<AliceOmemoSession> newSession(OmemoBundle bundle, OmemoKeyPair ik) async {
|
||||
// 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 dhr = bundle.ik;
|
||||
final ek = x3dhResult.ek;
|
||||
final sk = x3dhResult.sk;
|
||||
final kdfRkResult = await kdfRk(sk, await dh(dhs, dhr, 2));
|
||||
final dhr = spk;
|
||||
final rk = await kdfRk(sk, await dh(dhs, dhr, 0));
|
||||
final cks = rk;
|
||||
|
||||
return AliceOmemoSession(
|
||||
return OmemoDoubleRatchet(
|
||||
dhs,
|
||||
dhr,
|
||||
ek,
|
||||
kdfRkResult.rk,
|
||||
kdfRkResult.ck,
|
||||
rk,
|
||||
cks,
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
x3dhResult.ad,
|
||||
ad,
|
||||
);
|
||||
}
|
||||
|
||||
/// The associated_data parameter is implicit as it belongs to the session
|
||||
Future<List<int>> _encrypt(List<int> mk, List<int> plaintext, List<int> associatedData) async {
|
||||
final algorithm = Hkdf(
|
||||
hmac: Hmac(Sha256()),
|
||||
outputLength: 80,
|
||||
/// This is performed by the accepting entity
|
||||
static Future<OmemoDoubleRatchet> acceptNewSession(OmemoKeyPair spk, List<int> sk, List<int> ad) async {
|
||||
final dhs = spk;
|
||||
return OmemoDoubleRatchet(
|
||||
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()
|
||||
Future<RatchetStep> ratchetEncrypt(List<int> plaintext) async {
|
||||
final newCks = await kdfCk(cks!, kdfCkNextChainKey);
|
||||
final mk = await kdfCk(cks!, kdfCkNextMessageKey);
|
||||
|
||||
cks = newCks;
|
||||
final header = OMEMOMessage()
|
||||
..n = ns
|
||||
..pn = pn
|
||||
..n = ns;
|
||||
final header = message.writeToBuffer();
|
||||
..dhPub = await dhs.pk.getBytes();
|
||||
|
||||
cks = kdfResult.ck;
|
||||
ns++;
|
||||
|
||||
return _encrypt(
|
||||
kdfResult.mk,
|
||||
plaintext,
|
||||
concat([ad, header]),
|
||||
return RatchetStep(
|
||||
header,
|
||||
await encrypt(mk, plaintext, concat([sessionAd, header.writeToBuffer()]), sessionAd),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
Future<List<int>?> trySkippedMessageKeys(OMEMOMessage header, List<int> ciphertext) async {
|
||||
final key = SkippedKey(
|
||||
// TODO(PapaTutuWawa): Is this correct
|
||||
OmemoPublicKey.fromBytes(header.dhPub, KeyPairType.ed25519),
|
||||
header.n,
|
||||
);
|
||||
if (mkSkipped.containsKey(key)) {
|
||||
final mk = mkSkipped[key]!;
|
||||
mkSkipped.remove(key);
|
||||
|
||||
final encKey = bytes.sublist(0, 32);
|
||||
final authKey = bytes.sublist(32, 64);
|
||||
final iv = bytes.sublist(64, 80);
|
||||
return decrypt(mk, ciphertext, concat([sessionAd, header.writeToBuffer()]), sessionAd);
|
||||
}
|
||||
|
||||
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 null;
|
||||
}
|
||||
|
||||
return OmemoEncryptionResult(
|
||||
secretBox.cipherText,
|
||||
keyMap,
|
||||
);
|
||||
}
|
||||
|
||||
/// Result of the KDF_RK function from the Double Ratchet spec.
|
||||
class KdfRkResult {
|
||||
|
||||
const KdfRkResult(this.rk, this.ck);
|
||||
/// 32 byte Root Key
|
||||
final List<int> rk;
|
||||
|
||||
/// 32 byte Chain Key
|
||||
final List<int> ck;
|
||||
}
|
||||
|
||||
/// Result of the KDF_CK function from the Double Ratchet spec.
|
||||
class KdfCkResult {
|
||||
|
||||
const KdfCkResult(this.ck, this.mk);
|
||||
/// 32 byte Chain Key
|
||||
final List<int> ck;
|
||||
|
||||
/// 32 byte Message Key
|
||||
final List<int> mk;
|
||||
}
|
||||
|
||||
/// Amount of messages we may skip per session
|
||||
const maxSkip = 1000;
|
||||
|
||||
/// Info string for KDF_RK
|
||||
const kdfRkInfoString = 'OMEMO Root Chain';
|
||||
|
||||
/// Info string for ENCRYPT
|
||||
const encryptHkdfInfoString = 'OMEMO Message Key Material';
|
||||
|
||||
/// Info string for encrypting a message
|
||||
const encryptionHkdfInfoString = 'OMEMO Payload';
|
||||
|
||||
/// Flags for KDF_CK
|
||||
const kdfCkNextMessageKey = 0x01;
|
||||
const kdfCkNextChainKey = 0x02;
|
||||
|
||||
Future<KdfRkResult> 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),
|
||||
);
|
||||
|
||||
// TODO(PapaTutuWawa): Does the rk in the tuple (rk, ck) refer to the input rk?
|
||||
return KdfRkResult(rk, await result.extractBytes());
|
||||
}
|
||||
|
||||
Future<KdfCkResult> kdfCk(List<int> ck) async {
|
||||
final hkdf = Hkdf(hmac: Hmac(Sha256()), outputLength: 32);
|
||||
final newCk = await hkdf.deriveKey(
|
||||
secretKey: SecretKey(ck),
|
||||
nonce: [kdfCkNextChainKey],
|
||||
);
|
||||
final mk = await hkdf.deriveKey(
|
||||
secretKey: SecretKey(ck),
|
||||
nonce: [kdfCkNextMessageKey],
|
||||
);
|
||||
|
||||
return KdfCkResult(
|
||||
await newCk.extractBytes(),
|
||||
await mk.extractBytes(),
|
||||
);
|
||||
Future<void> skipMessageKeys(int until) async {
|
||||
if (nr + maxSkip < until) {
|
||||
// TODO(PapaTutuWawa): Custom exception
|
||||
throw Exception();
|
||||
}
|
||||
|
||||
if (ckr != null) {
|
||||
while (nr < until) {
|
||||
final newCkr = await kdfCk(ckr!, kdfCkNextChainKey);
|
||||
final mk = await kdfCk(ckr!, kdfCkNextMessageKey);
|
||||
ckr = newCkr;
|
||||
mkSkipped[SkippedKey(dhr!, nr)] = mk;
|
||||
nr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> dhRatchet(OMEMOMessage header) async {
|
||||
pn = header.n;
|
||||
ns = 0;
|
||||
nr = 0;
|
||||
dhr = OmemoPublicKey.fromBytes(header.dhPub, KeyPairType.ed25519);
|
||||
|
||||
final newRk = await kdfRk(rk, await dh(dhs, dhr!, 2));
|
||||
rk = newRk;
|
||||
ckr = newRk;
|
||||
dhs = await OmemoKeyPair.generateNewPair(KeyPairType.x25519);
|
||||
final newNewRk = await kdfRk(rk, await dh(dhs, dhr!, 2));
|
||||
rk = newNewRk;
|
||||
cks = newNewRk;
|
||||
}
|
||||
|
||||
Future<List<int>> ratchetDecrypt(OMEMOMessage header, List<int> ciphertext) async {
|
||||
// Check if we skipped too many messages
|
||||
final plaintext = await trySkippedMessageKeys(header, ciphertext);
|
||||
if (plaintext != null) {
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
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();
|
||||
} else if (identityKey == 2) {
|
||||
cpk = await pk.toCurve25519();
|
||||
} else if (identityKey == 3) {
|
||||
ckp = await kp.toCurve25519();
|
||||
cpk = await pk.toCurve25519();
|
||||
}
|
||||
|
||||
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