diff --git a/lib/src/x3dh/x3dh.dart b/lib/src/x3dh/x3dh.dart new file mode 100644 index 0000000..8dad5af --- /dev/null +++ b/lib/src/x3dh/x3dh.dart @@ -0,0 +1,107 @@ +import 'dart:convert'; +import 'package:cryptography/cryptography.dart'; +import 'package:cryptography/dart.dart'; + +/// The overarching assumption is that we use Ed25519 keys for the identity keys + +class X3DHRun { + + const X3DHRun(this.epk, this.sharedSecret); + final SimpleKeyPair epk; + final List sharedSecret; +} + +SimpleKeyPairData fromPublicKey(SimplePublicKey pk) { + return SimpleKeyPairData([], publicKey: pk, type: KeyPairType.x25519); +} + +Future> sig(SimpleKeyPair keyPair, List message) async { + final signature = await Ed25519().sign( + message, + keyPair: keyPair, + ); + + return signature.bytes; +} + +/// Performs X25519 with [pk1] and [pk2]. If [identityKey] is set, then +/// it indicates which of [pk1] ([identityKey] == 1) or [pk2] ([identityKey] == 2) +/// is the identity key. +Future> dh(SimpleKeyPair kp, SimplePublicKey pk, int identityKey) async { + var ckp = kp; + var cpk = pk; + + /* + if (identityKey == 1) { + final pkc = await DartEd25519.publicKeyToCurve25519(kp); + final skc = await DartEd25519.privateKeyToCurve25519(kp); + ckp = SimpleKeyPairData(skc, publicKey: pkc, type: KeyPairType.x25519); + } else if (identityKey == 2) { + cpk = await DartEd25519.publicKeyToCurve25519(fromPublicKey(pk)); + } + */ + + final shared = await Cryptography.instance.x25519().sharedSecretKey( + keyPair: ckp, + remotePublicKey: cpk, + ); + + return shared.extractBytes(); +} + +Future> kdf(List km) async { + final f = List.filled(32, 0xFF); + final input = List.empty(growable: true); + input + ..addAll(f) + ..addAll(km); + + final algorithm = Hkdf( + hmac: Hmac(Sha256()), + outputLength: 32, + ); + final output = await algorithm.deriveKey( + secretKey: SecretKey(input), + // TODO: Fix + nonce: List.filled(32, 0x00), + info: utf8.encode('OMEMO X3DH'), + ); + + return output.extractBytes(); +} + +List concat(List> inputs) { + final tmp = List.empty(growable: true); + for (final input in inputs) { + tmp.addAll(input); + } + + return tmp; +} + +// Alice -> Bob +Future x3dhFromPrekeyBundle(SimplePublicKey ikb, SimplePublicKey spkb, SimplePublicKey opkb, SimpleKeyPair ika) async { + // Generate EPK + final epk = await Cryptography.instance.x25519().newKeyPair(); + + final dh1 = await dh(ika, spkb, 1); + final dh2 = await dh(epk, ikb, 2); + final dh3 = await dh(epk, spkb, 0); + final dh4 = await dh(epk, opkb, 0); + + final sk = await kdf(concat([dh1, dh2, dh3, dh4])); + + return X3DHRun( + epk, + sk, + ); +} + +Future> x3dhFromInitialMessage(SimplePublicKey ika, SimplePublicKey epk, SimpleKeyPair opkb, SimpleKeyPair spk, SimpleKeyPair ikb) async { + final dh1 = await dh(spk, ika, 2); + final dh2 = await dh(ikb, epk, 1); + final dh3 = await dh(spk, epk, 0); + final dh4 = await dh(opkb, epk, 0); + + return kdf(concat([dh1, dh2, dh3, dh4])); +} diff --git a/pubspec.yaml b/pubspec.yaml index ea09273..2ec3f5d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,14 +1,15 @@ name: omemo_dart -description: A starting point for Dart libraries or applications. -version: 1.0.0 -# homepage: https://www.example.com +description: An XMPP library independent OMEMO library +version: 0.1.0 environment: sdk: '>=2.15.1 <3.0.0' -# dependencies: -# path: ^1.8.0 +dependencies: + cryptography: + path: ../cryptography/cryptography + pinenacl: dev_dependencies: lints: ^1.0.0 diff --git a/test/omemo_dart_test.dart b/test/omemo_dart_test.dart index 7e298b5..18a78b7 100644 --- a/test/omemo_dart_test.dart +++ b/test/omemo_dart_test.dart @@ -1,16 +1,61 @@ -import 'package:omemo_dart/omemo_dart.dart'; +import 'package:cryptography/cryptography.dart'; +import 'package:cryptography/dart.dart'; import 'package:test/test.dart'; +import 'package:omemo_dart/src/x3dh/x3dh.dart'; + +Future> publicKeyBytes(SimpleKeyPair kp) async { + final pk = await kp.extractPublicKey(); + return pk.bytes; +} + +Future publicKey(SimpleKeyPair kp) async { + //return await DartEd25519.publicKeyToCurve25519(kp); + return kp.extractPublicKey(); +} + +Future toCurve(SimpleKeyPair kp) async { + //final pk = await DartEd25519.publicKeyToCurve25519(kp); + //final sk = await DartEd25519.privateKeyToCurve25519(kp); + //return SimpleKeyPairData(sk, publicKey: pk, type: KeyPairType.x25519); + return kp; +} void main() { - group('A group of tests', () { - final awesome = Awesome(); + test("X3DH", () async { + final ed = Ed25519(); + final x = Cryptography.instance.x25519(); - setUp(() { - // Additional setup goes here. - }); + // Generate IKs for Alice and Bob + final ikAlice = await x.newKeyPair(); + final ikBob = await x.newKeyPair(); + + // Generate SPKs for Alice and Bob + final spkAlice = await x.newKeyPair(); + final spkSigAlice = await sig(ikAlice, await publicKeyBytes(spkAlice)); + final spkBob = await x.newKeyPair(); + final spkSigBob = await sig(ikBob, await publicKeyBytes(spkBob)); - test('First Test', () { - expect(awesome.isAwesome, isTrue); - }); + // Generate an OPK for Alice and Bob + final opkAlice = await x.newKeyPair(); + final opkBob = await x.newKeyPair(); + + + // Perform X3DH + final aliceMessage = await x3dhFromPrekeyBundle( + await ikBob.extractPublicKey(), + await spkBob.extractPublicKey(), + await opkBob.extractPublicKey(), + ikAlice, + ); + + final bobDh = await x3dhFromInitialMessage( + await ikAlice.extractPublicKey(), + await aliceMessage.epk.extractPublicKey(), + opkBob, + spkBob, + ikBob, + ); + + expect(aliceMessage.sharedSecret, bobDh); }); }