110 lines
6.4 KiB
Markdown
110 lines
6.4 KiB
Markdown
### Chiffrement des données de processus et déchiffrement (4NK)
|
||
|
||
Ce document décrit comment les données privées d’un processus sont chiffrées, stockées et déchiffrées côté client dans l’écosystème 4NK, en s’alignant sur les composants existants (`ihm_client`, service worker/IndexedDB, relays, wasm `sdk_client`).
|
||
|
||
### Principes
|
||
- **Séparation public/privé**: lors de la création/mise à jour d’un process, le client sépare explicitement les champs privés des champs publics.
|
||
- **Encodage**: les objets JSON simples et binaires sont encodés via le wasm (`encode_json`, `encode_binary`).
|
||
- **Chiffrement & Clés**: le wasm chiffre les champs privés et associe des clés par attribut dans `state.keys[attribute]` pour les membres autorisés (déterminés par `roles` + règles de validation).
|
||
- **Stockage**:
|
||
- Les données chiffrées sont référencées par un hash (`pcd_commitment[attribute]`) et stockées localement en IndexedDB (store `data`) via `handleApiReturn`.
|
||
- Les clés partagées (confirmées ou en attente) sont stockées dans `shared_secrets` / `unconfirmed_secrets` et injectées dans le wasm via `set_shared_secrets`.
|
||
- **Rôle du relay**: les pairs publient/échangent ciphers, diffs, clés via les connections relay. Au moins un « handshake » relay doit avoir eu lieu pour récupérer/échanger les clés.
|
||
|
||
### Séquence — Création d’un processus avec données privées
|
||
1) L’app sépare `privateData` et `publicData`.
|
||
2) Encodage des deux jeux de données via wasm.
|
||
3) `create_new_process` (wasm) produit `updated_process` avec:
|
||
- `current_process.states[*].pcd_commitment[attribute]` (hash → blob chiffré)
|
||
- `current_process.states[*].keys[attribute]` pour les membres autorisés
|
||
4) `handleApiReturn`:
|
||
- Sauvegarde des blobs dans IndexedDB (`data`), des diffs, et du process dans `processes`.
|
||
- Alimente `shared_secrets`/`unconfirmed_secrets` et `set_shared_secrets` → wasm mémorise les secrets.
|
||
5) `checkConnections` s’assure que les pairs nécessaires sont connectés (fonds « faucet » si besoin), puis `request_data` peut être utilisé pour obtenir des clés/manquants.
|
||
|
||
Pseudo-code (TypeScript, côté `ihm_client`)
|
||
```ts
|
||
const { jsonCompatibleData, binaryData } = splitData(privateData);
|
||
const encodedPrivate = {
|
||
...sdk.encode_json(jsonCompatibleData),
|
||
...sdk.encode_binary(binaryData)
|
||
};
|
||
const encodedPublic = encode(publicData);
|
||
|
||
const res = sdk.create_new_process(encodedPrivate, roles, encodedPublic, relayAddress, feeRate, services.getAllMembers());
|
||
await services.handleApiReturn(res); // stocke blobs + diffs + process + secrets
|
||
await services.checkConnections(res.updated_process.current_process);
|
||
```
|
||
|
||
### Séquence — Récupération et déchiffrement des données privées
|
||
Objectif: obtenir les champs déchiffrés d’un état particulier.
|
||
|
||
1) Préconditions:
|
||
- Appareil pairé (`is_paired()` et `services.isPaired()` → true)
|
||
- Au moins un handshake relay reçu (liste des membres non vide)
|
||
- `restoreSecretsFromDB()` exécuté (injecte les secrets en wasm)
|
||
2) Côté hôte, appeler l’iframe via `RETRIEVE_DATA { processId, stateId, accessToken }`.
|
||
3) Routeur `ihm_client` → `handleDecryptState`:
|
||
- Vérifie `validateToken(accessToken, origin)`
|
||
- Charge le `process` → `services.getProcess(processId)`
|
||
- Vérifie/établit les connexions (`checkConnections(process, stateId)`) pour récupérer clés manquantes
|
||
- Pour chaque attribut privé: `decryptAttribute(processId, state, attribute)`
|
||
4) `decryptAttribute`:
|
||
- Si clé absente mais rôle autorisé → `requestDataFromPeers(processId, [stateId], [roles])` puis retries
|
||
- Lit le blob chiffré en IndexedDB via `pcd_commitment[attribute]`
|
||
- Déchiffre avec `sdk.decrypt_data(key, cipher)` puis `sdk.decode_value(clear)`
|
||
5) Réponse `DATA_RETRIEVED` avec les champs déchiffrés.
|
||
|
||
Pseudo-code déchiffrement (extrait)
|
||
```ts
|
||
async function decryptAttribute(processId: string, state: ProcessState, attribute: string) {
|
||
const hash = state.pcd_commitment[attribute];
|
||
let key = state.keys[attribute];
|
||
if (!key && rolesContainUs(state.roles, getPairingProcessId())) {
|
||
await services.checkConnections(await services.getProcess(processId), state.state_id);
|
||
await services.requestDataFromPeers(processId, [state.state_id], [state.roles]);
|
||
await retryDelay();
|
||
key = state.keys[attribute];
|
||
}
|
||
if (!hash || !key) return null;
|
||
const blob = await services.getBlobFromDb(hash);
|
||
if (!blob) return null;
|
||
const cipher = new Uint8Array(await blob.arrayBuffer());
|
||
const clear = sdk.decrypt_data(hexToU8(key), cipher);
|
||
return sdk.decode_value(clear);
|
||
}
|
||
```
|
||
|
||
### Séquence — Mise à jour avec nouveaux champs privés
|
||
1) L’app calcule `privateData`/`publicData` du delta.
|
||
2) `update_process` (wasm) produit `updated_process` et, si nécessaire, de nouvelles clés par attribut.
|
||
3) `handleApiReturn` met à jour blobs/diffs/process/secrets.
|
||
4) Les pairs reçoivent ciphers/keys via relay; `request_data` sert à combler les manquants.
|
||
|
||
### Bonnes pratiques et diagnostics
|
||
- Toujours valider le token (`VALIDATE_TOKEN`) avant des opérations sensibles.
|
||
- Attendre un **handshake relay** au démarrage avant de tenter d’obtenir des clés.
|
||
- Vérifier que `rolesContainsUs(roles)` est vrai pour les attributs visés — sinon pas d’accès aux clés.
|
||
- Confirmer qu’IndexedDB contient:
|
||
- `data[hash]` (blob chiffré)
|
||
- `shared_secrets[...]` utiles → `restoreSecretsFromDB()` doit tourner à l’init
|
||
- En cas d’échec de déchiffrement:
|
||
- Absence de blob → `handleApiReturn` n’a pas été invoqué sur un `updated_process` contenant `encrypted_data`
|
||
- Absence de clé → relancer `checkConnections` + `requestDataFromPeers`, vérifier la connectivité relay/fonds
|
||
|
||
### Exemple de flux hôte ↔ iframe (postMessage)
|
||
```ts
|
||
// 1) L’hôte liste les processus (publique) puis demande les privés d’un état
|
||
postMessage({ type: 'GET_PROCESSES', messageId, accessToken });
|
||
// ... reçoit PROCESSES_RETRIEVED
|
||
postMessage({ type: 'RETRIEVE_DATA', messageId, processId, stateId, accessToken });
|
||
// ... reçoit DATA_RETRIEVED { data: { monChampPrive: '...' } }
|
||
```
|
||
|
||
### Résumé
|
||
- Les données privées ne sont jamais renvoyées en clair par `GET_PROCESSES`.
|
||
- Le déchiffrement s’effectue côté client via `RETRIEVE_DATA` et `decryptAttribute`, avec clés/ blobs provenant du relay + IndexedDB.
|
||
- La disponibilité des clés dépend des rôles, du pairing, de la connectivité et des secrets chargés.
|
||
|
||
|