feat: Allow serialising Device to Json
This commit is contained in:
		
							parent
							
								
									3a7489a9c3
								
							
						
					
					
						commit
						cd77996db4
					
				| @ -6,5 +6,6 @@ export 'src/events.dart'; | ||||
| export 'src/helpers.dart'; | ||||
| export 'src/keys.dart'; | ||||
| export 'src/omemo/bundle.dart'; | ||||
| export 'src/omemo/device.dart'; | ||||
| export 'src/omemo/sessionmanager.dart'; | ||||
| export 'src/x3dh/x3dh.dart'; | ||||
|  | ||||
| @ -1,5 +1,7 @@ | ||||
| import 'dart:convert'; | ||||
| 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/tweetnacl.dart'; | ||||
| 
 | ||||
| @ -42,12 +44,14 @@ class OmemoPublicKey { | ||||
|   } | ||||
| 
 | ||||
|   SimplePublicKey asPublicKey() => _pubkey; | ||||
|    | ||||
|   /// Convert the public key into a [SimpleKeyPairData] with a stub private key. Useful | ||||
|   /// for when cryptography calls for a KeyPair, but only uses the public key. | ||||
|   //SimpleKeyPairData asPseudoKeypair() { | ||||
|   // | ||||
|   //} | ||||
| 
 | ||||
|   @visibleForTesting | ||||
|   Future<bool> equals(OmemoPublicKey key) async { | ||||
|     return type == key.type && listsEqual( | ||||
|       await getBytes(), | ||||
|       await key.getBytes(), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class OmemoPrivateKey { | ||||
| @ -69,16 +73,39 @@ class OmemoPrivateKey { | ||||
| 
 | ||||
|     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 | ||||
| class OmemoKeyPair { | ||||
| 
 | ||||
|   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 { | ||||
|     assert(type == KeyPairType.ed25519 || type == KeyPairType.x25519, 'Keypair must be either Ed25519 or X25519'); | ||||
| 
 | ||||
| @ -102,6 +129,10 @@ class OmemoKeyPair { | ||||
|       type, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   final KeyPairType type; | ||||
|   final OmemoPublicKey pk; | ||||
|   final OmemoPrivateKey sk; | ||||
|    | ||||
|   /// Return the bytes that comprise the public key. | ||||
|   Future<OmemoKeyPair> toCurve25519() async { | ||||
| @ -121,4 +152,11 @@ class OmemoKeyPair { | ||||
|       type: type, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   @visibleForTesting | ||||
|   Future<bool> equals(OmemoKeyPair pair) async { | ||||
|     return type == pair.type && | ||||
|       await pk.equals(pair.pk) && | ||||
|       await sk.equals(pair.sk); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -12,6 +12,56 @@ class Device { | ||||
| 
 | ||||
|   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. | ||||
|   static Future<Device> generateNewDevice({ int opkAmount = 100 }) async { | ||||
|     final id = generateRandom32BitNumber(); | ||||
| @ -92,4 +142,28 @@ class Device { | ||||
|       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, | ||||
|     }; | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										24
									
								
								test/serialisation_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								test/serialisation_test.dart
									
									
									
									
									
										Normal 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); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user