@@ -4,17 +4,23 @@ import 'package:cryptography/cryptography.dart';
44import 'package:dio/dio.dart' ;
55import 'package:flutter/foundation.dart' ;
66import 'package:hushnet_frontend/models/pending_sessions.dart' ;
7+ import 'package:hushnet_frontend/models/session.dart' ;
78import 'package:hushnet_frontend/services/key_provider.dart' ;
89import 'package:hushnet_frontend/services/node_service.dart' ;
10+ import 'package:hushnet_frontend/services/secure_storage_service.dart' ;
911
1012class SessionService {
1113 final KeyProvider keyProvider = KeyProvider ();
1214 final Dio dio = Dio ();
1315 final NodeService nodeService = NodeService ();
16+ final SecureStorageService secureStorage = SecureStorageService ();
1417
1518 Future <List <PendingSession >> getPendingSessions () async {
1619 final String ? nodeUrl = await nodeService.getCurrentNodeUrl ();
17- final req = await keyProvider.sendSignedRequest ("GET" , "$nodeUrl /sessions/pending" );
20+ final req = await keyProvider.sendSignedRequest (
21+ "GET" ,
22+ "$nodeUrl /sessions/pending" ,
23+ );
1824 final data = req.data;
1925 final List sessions = (data is List ) ? data : (data['sessions' ] ?? []);
2026 return sessions.map ((s) => PendingSession .fromJson (s)).toList ();
@@ -62,21 +68,36 @@ class SessionService {
6268 type: KeyPairType .x25519,
6369 );
6470
65- final senderIkPub = SimplePublicKey (senderPrekeyPub, type: KeyPairType .x25519);
66- final ekAPub = SimplePublicKey (senderEphemeral, type: KeyPairType .x25519);
71+ final senderIkPub = SimplePublicKey (
72+ senderPrekeyPub,
73+ type: KeyPairType .x25519,
74+ );
75+ final ekAPub = SimplePublicKey (
76+ senderEphemeral,
77+ type: KeyPairType .x25519,
78+ );
6779 debugPrint ("Alice EK pub: ${base64Encode (senderEphemeral )}" );
6880 debugPrint ("Bob IK Priv: ${base64Encode (ikPriv )}" );
6981
7082 // DH1 = DH(SPK_B, IK_A)
71- final dh1 = await x25519.sharedSecretKey (keyPair: spkPair, remotePublicKey: senderIkPub);
83+ final dh1 = await x25519.sharedSecretKey (
84+ keyPair: spkPair,
85+ remotePublicKey: senderIkPub,
86+ );
7287 final dh1Bytes = await dh1.extractBytes ();
7388
7489 // DH2 = DH(IK_B, EK_A)
75- final dh2 = await x25519.sharedSecretKey (keyPair: ikPair, remotePublicKey: ekAPub);
90+ final dh2 = await x25519.sharedSecretKey (
91+ keyPair: ikPair,
92+ remotePublicKey: ekAPub,
93+ );
7694 final dh2Bytes = await dh2.extractBytes ();
7795
7896 // DH3 = DH(SPK_B, EK_A)
79- final dh3 = await x25519.sharedSecretKey (keyPair: spkPair, remotePublicKey: ekAPub);
97+ final dh3 = await x25519.sharedSecretKey (
98+ keyPair: spkPair,
99+ remotePublicKey: ekAPub,
100+ );
80101 final dh3Bytes = await dh3.extractBytes ();
81102
82103 // (optional) DH4 = DH(OPK_B, EK_A)
@@ -85,10 +106,16 @@ class SessionService {
85106 final opk = oneTimePreKeys.first;
86107 final opkPair = SimpleKeyPairData (
87108 opk['private' ]! ,
88- publicKey: SimplePublicKey (opk['public' ]! , type: KeyPairType .x25519),
109+ publicKey: SimplePublicKey (
110+ opk['public' ]! ,
111+ type: KeyPairType .x25519,
112+ ),
89113 type: KeyPairType .x25519,
90114 );
91- final dh4 = await x25519.sharedSecretKey (keyPair: opkPair, remotePublicKey: ekAPub);
115+ final dh4 = await x25519.sharedSecretKey (
116+ keyPair: opkPair,
117+ remotePublicKey: ekAPub,
118+ );
92119 dh4Bytes = await dh4.extractBytes ();
93120 }
94121
@@ -99,22 +126,19 @@ class SessionService {
99126 debugPrint ('DH4 length: ${dh4Bytes .length }' );
100127 debugPrint ('Combined length: ${combined .length }' );
101128 debugPrint ('DH1: ${base64Encode (dh1Bytes )}' );
102- debugPrint ('DH2: ${base64Encode (dh2Bytes )}' );
103- debugPrint ('DH3: ${base64Encode (dh3Bytes )}' );
104- debugPrint ('DH4: ${base64Encode (dh4Bytes )}' );
105-
106- final rootKey = await hkdf.deriveKey (
107- secretKey: SecretKey (combined),
108- nonce: utf8.encode ('HushNet-Salt' ), // facultatif mais recommandé
109- info: utf8.encode ('X3DH Root Key' ),
110- );
129+ debugPrint ('DH2: ${base64Encode (dh2Bytes )}' );
130+ debugPrint ('DH3: ${base64Encode (dh3Bytes )}' );
131+ debugPrint ('DH4: ${base64Encode (dh4Bytes )}' );
132+
133+ final rootKey = await hkdf.deriveKey (
134+ secretKey: SecretKey (combined),
135+ nonce: utf8.encode ('HushNet-Salt' ), // facultatif mais recommandé
136+ info: utf8.encode ('X3DH Root Key' ),
137+ );
111138
112139 final plaintext = await _decryptCiphertext (p.ciphertext, rootKey, aes);
113140 debugPrint ('✅ Decrypted pending session ${p .id }: $plaintext ' );
114-
115- // Delete pending session on server
116- // final String? nodeUrl = await nodeService.getCurrentNodeUrl();
117- // await dio.delete('$nodeUrl/sessions/${p.id}/complete');
141+ await initializeRatchetSession (p, rootKey);
118142 } catch (e) {
119143 debugPrint ('❌ Failed to process ${p .id }: $e ' );
120144 }
@@ -138,4 +162,90 @@ final rootKey = await hkdf.deriveKey(
138162 final clear = await aes.decrypt (box, secretKey: rootKey);
139163 return utf8.decode (clear);
140164 }
165+
166+ Future <void > uploadSession (Session session, PendingSession pending) async {
167+ final String ? nodeUrl = await nodeService.getCurrentNodeUrl ();
168+ final req = await keyProvider.sendSignedRequest (
169+ "POST" ,
170+ "$nodeUrl /sessions/confirm" ,
171+ payload: {
172+ "pending_session_id" : pending.id,
173+ ...session.toConfirmJson (),
174+ },
175+ );
176+ debugPrint ('Uploaded session: ${req .statusCode }' );
177+ debugPrint ('Response data: ${req .data }' );
178+ }
179+
180+ Future <void > initializeRatchetSession (
181+ PendingSession pending,
182+ SecretKey rootKey,
183+ ) async {
184+ // derive send/recv chain keys
185+ try {
186+ final hkdf = Hkdf (hmac: Hmac .sha256 (), outputLength: 32 );
187+ final rootBytes = await rootKey.extractBytes ();
188+
189+ final sendChainKey = await hkdf.deriveKey (
190+ secretKey: SecretKeyData (rootBytes),
191+ nonce: utf8.encode ('HushNet-Salt' ),
192+ info: utf8.encode ('HushNet-Send-Chain' ),
193+ );
194+
195+ final recvChainKey = await hkdf.deriveKey (
196+ secretKey: SecretKeyData (rootBytes),
197+ nonce: utf8.encode ('HushNet-Salt' ),
198+ info: utf8.encode ('HushNet-Recv-Chain' ),
199+ );
200+
201+ final ratchetAlgo = X25519 ();
202+ final ratchetPair = await ratchetAlgo.newKeyPair ();
203+ final ratchetPub = await ratchetPair.extractPublicKey ();
204+ final ratchetPriv = await ratchetPair.extractPrivateKeyBytes ();
205+
206+ final rootKeyB64 = base64Encode (rootBytes);
207+ final sendChainB64 = base64Encode (await sendChainKey.extractBytes ());
208+ final recvChainB64 = base64Encode (await recvChainKey.extractBytes ());
209+ final ratchetPubB64 = base64Encode (ratchetPub.bytes);
210+ final ratchetPrivB64 = base64Encode (ratchetPriv);
211+
212+ await keyProvider.secureStorage.write (
213+ key: "session_${pending .senderDeviceId }_root" ,
214+ value: rootKeyB64,
215+ );
216+ await keyProvider.secureStorage.write (
217+ key: "session_${pending .senderDeviceId }_send_chain" ,
218+ value: sendChainB64,
219+ );
220+ await keyProvider.secureStorage.write (
221+ key: "session_${pending .senderDeviceId }_recv_chain" ,
222+ value: recvChainB64,
223+ );
224+ await keyProvider.secureStorage.write (
225+ key: "session_${pending .senderDeviceId }_ratchet_pub" ,
226+ value: ratchetPubB64,
227+ );
228+ await keyProvider.secureStorage.write (
229+ key: "session_${pending .senderDeviceId }_ratchet_priv" ,
230+ value: ratchetPrivB64,
231+ );
232+ debugPrint (
233+ '✅ Initialized ratchet session with ${pending .senderDeviceId }' ,
234+ );
235+ final session = Session (
236+ senderDeviceId: pending.senderDeviceId,
237+ receiverDeviceId: pending.recipientDeviceId,
238+ rootKey: Uint8List .fromList (rootBytes),
239+ ratchetPub: Uint8List .fromList (ratchetPub.bytes),
240+ lastRemotePub: Uint8List .fromList (
241+ base64Decode (pending.ephemeralPubkey),
242+ ),
243+ createdAt: DateTime .now (),
244+ updatedAt: DateTime .now (),
245+ );
246+ await uploadSession (session, pending);
247+ } catch (e) {
248+ debugPrint ('Error initializing ratchet session: $e ' );
249+ }
250+ }
141251}
0 commit comments