feat: Allow serialising Device to Json

This commit is contained in:
PapaTutuWawa 2022-08-05 17:32:59 +02:00
parent 3a7489a9c3
commit cd77996db4
4 changed files with 146 additions and 9 deletions

View File

@ -6,5 +6,6 @@ export 'src/events.dart';
export 'src/helpers.dart'; export 'src/helpers.dart';
export 'src/keys.dart'; export 'src/keys.dart';
export 'src/omemo/bundle.dart'; export 'src/omemo/bundle.dart';
export 'src/omemo/device.dart';
export 'src/omemo/sessionmanager.dart'; export 'src/omemo/sessionmanager.dart';
export 'src/x3dh/x3dh.dart'; export 'src/x3dh/x3dh.dart';

View File

@ -1,5 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:cryptography/cryptography.dart'; import 'package:cryptography/cryptography.dart';
import 'package:meta/meta.dart';
import 'package:omemo_dart/src/helpers.dart';
import 'package:pinenacl/api.dart'; import 'package:pinenacl/api.dart';
import 'package:pinenacl/tweetnacl.dart'; import 'package:pinenacl/tweetnacl.dart';
@ -43,11 +45,13 @@ class OmemoPublicKey {
SimplePublicKey asPublicKey() => _pubkey; SimplePublicKey asPublicKey() => _pubkey;
/// Convert the public key into a [SimpleKeyPairData] with a stub private key. Useful @visibleForTesting
/// for when cryptography calls for a KeyPair, but only uses the public key. Future<bool> equals(OmemoPublicKey key) async {
//SimpleKeyPairData asPseudoKeypair() { return type == key.type && listsEqual(
// await getBytes(),
//} await key.getBytes(),
);
}
} }
class OmemoPrivateKey { class OmemoPrivateKey {
@ -69,16 +73,39 @@ class OmemoPrivateKey {
return OmemoPrivateKey(List<int>.from(skc), KeyPairType.x25519); return OmemoPrivateKey(List<int>.from(skc), KeyPairType.x25519);
} }
@visibleForTesting
Future<bool> equals(OmemoPrivateKey key) async {
return type == key.type && listsEqual(
await getBytes(),
await key.getBytes(),
);
}
} }
/// A generic wrapper class for both Ed25519 and X25519 keypairs /// A generic wrapper class for both Ed25519 and X25519 keypairs
class OmemoKeyPair { class OmemoKeyPair {
const OmemoKeyPair(this.pk, this.sk, this.type); const OmemoKeyPair(this.pk, this.sk, this.type);
final KeyPairType type;
final OmemoPublicKey pk;
final OmemoPrivateKey sk;
/// Create an OmemoKeyPair just from a [type] and the bytes of the private and public
/// key.
factory OmemoKeyPair.fromBytes(List<int> publicKey, List<int> privateKey, KeyPairType type) {
return OmemoKeyPair(
OmemoPublicKey.fromBytes(
publicKey,
type,
),
OmemoPrivateKey(
privateKey,
type,
),
type,
);
}
/// Generate a completely new random OmemoKeyPair of type [type]. [type] must be either
/// KeyPairType.ed25519 or KeyPairType.x25519.
static Future<OmemoKeyPair> generateNewPair(KeyPairType type) async { static Future<OmemoKeyPair> generateNewPair(KeyPairType type) async {
assert(type == KeyPairType.ed25519 || type == KeyPairType.x25519, 'Keypair must be either Ed25519 or X25519'); assert(type == KeyPairType.ed25519 || type == KeyPairType.x25519, 'Keypair must be either Ed25519 or X25519');
@ -103,6 +130,10 @@ class OmemoKeyPair {
); );
} }
final KeyPairType type;
final OmemoPublicKey pk;
final OmemoPrivateKey sk;
/// Return the bytes that comprise the public key. /// Return the bytes that comprise the public key.
Future<OmemoKeyPair> toCurve25519() async { Future<OmemoKeyPair> toCurve25519() async {
assert(type == KeyPairType.ed25519, 'Cannot convert non-Ed25519 keypair to X25519'); assert(type == KeyPairType.ed25519, 'Cannot convert non-Ed25519 keypair to X25519');
@ -121,4 +152,11 @@ class OmemoKeyPair {
type: type, type: type,
); );
} }
@visibleForTesting
Future<bool> equals(OmemoKeyPair pair) async {
return type == pair.type &&
await pk.equals(pair.pk) &&
await sk.equals(pair.sk);
}
} }

View File

@ -12,6 +12,56 @@ class Device {
const Device(this.id, this.ik, this.spk, this.spkId, this.spkSignature, this.opks); const Device(this.id, this.ik, this.spk, this.spkId, this.spkSignature, this.opks);
/// Deserialize the Device
factory Device.fromJson(Map<String, dynamic> data) {
// NOTE: We use the way OpenSSH names their keys, meaning that ik is the Identity
// Keypair's private key, while ik_pub refers to the Identity Keypair's public
// key.
/*
{
'id': 123,
'ik': 'base/64/encoded',
'ik_pub': 'base/64/encoded',
'spk': 'base/64/encoded',
'spk_pub': 'base/64/encoded',
'spk_id': 123,
'spk_sig': 'base/64/encoded',
'opks': [
{
'id': 0,
'public': 'base/64/encoded',
'private': 'base/64/encoded'
}, ...
]
}
*/
final opks = <int, OmemoKeyPair>{};
for (final opk in data['opks']! as List<Map<String, dynamic>>) {
opks[opk['id']! as int] = OmemoKeyPair.fromBytes(
base64.decode(opk['public']! as String),
base64.decode(opk['private']! as String),
KeyPairType.x25519,
);
}
return Device(
data['id']! as int,
OmemoKeyPair.fromBytes(
base64.decode(data['ik_pub']! as String),
base64.decode(data['ik']! as String),
KeyPairType.ed25519,
),
OmemoKeyPair.fromBytes(
base64.decode(data['spk_pub']! as String),
base64.decode(data['spk']! as String),
KeyPairType.x25519,
),
data['spk_id']! as int,
base64.decode(data['spk_sig']! as String),
opks,
);
}
/// Generate a completely new device, i.e. cryptographic identity. /// Generate a completely new device, i.e. cryptographic identity.
static Future<Device> generateNewDevice({ int opkAmount = 100 }) async { static Future<Device> generateNewDevice({ int opkAmount = 100 }) async {
final id = generateRandom32BitNumber(); final id = generateRandom32BitNumber();
@ -92,4 +142,28 @@ class Device {
encodedOpks, encodedOpks,
); );
} }
/// Serialise the device information.
Future<Map<String, dynamic>> toJson() async {
/// Serialise the OPKs
final serialisedOpks = List<Map<String, dynamic>>.empty(growable: true);
for (final entry in opks.entries) {
serialisedOpks.add({
'id': entry.key,
'public': base64.encode(await entry.value.pk.getBytes()),
'private': base64.encode(await entry.value.sk.getBytes()),
});
}
return {
'id': id,
'ik': base64.encode(await ik.sk.getBytes()),
'ik_pub': base64.encode(await ik.pk.getBytes()),
'spk': base64.encode(await spk.sk.getBytes()),
'spk_pub': base64.encode(await spk.pk.getBytes()),
'spk_id': spkId,
'spk_sig': base64.encode(spkSignature),
'opks': serialisedOpks,
};
}
} }

View File

@ -0,0 +1,24 @@
import 'package:omemo_dart/omemo_dart.dart';
import 'package:test/test.dart';
void main() {
test('Test serialising and deserialising Device', () async {
// Generate a random session
final oldSession = await OmemoSessionManager.generateNewIdentity(opkAmount: 1);
final oldDevice = await oldSession.getDevice();
final serialised = await oldDevice.toJson();
final newDevice = Device.fromJson(serialised);
expect(oldDevice.id, newDevice.id);
expect(await oldDevice.ik.equals(newDevice.ik), true);
expect(await oldDevice.spk.equals(newDevice.spk), true);
expect(listsEqual(oldDevice.spkSignature, newDevice.spkSignature), true);
expect(oldDevice.spkId, newDevice.spkId);
// Check the Ontime-Prekeys
expect(oldDevice.opks.length, newDevice.opks.length);
for (final entry in oldDevice.opks.entries) {
expect(await newDevice.opks[entry.key]!.equals(entry.value), true);
}
});
}