From 0bd75257570c97077e3304283c7c85275a501fe5 Mon Sep 17 00:00:00 2001 From: Julien Bouquillon Date: Wed, 31 May 2023 08:17:56 +0200 Subject: [PATCH 01/10] docs: add migration guide --- docs/docs/use-cases/migrate-to-e2esdk.md | 129 +++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 docs/docs/use-cases/migrate-to-e2esdk.md diff --git a/docs/docs/use-cases/migrate-to-e2esdk.md b/docs/docs/use-cases/migrate-to-e2esdk.md new file mode 100644 index 0000000..b5ca58f --- /dev/null +++ b/docs/docs/use-cases/migrate-to-e2esdk.md @@ -0,0 +1,129 @@ +# Encrypt an existing database + +## Users identity + +If your application has a existing users, you have to explicitly login the `application user` to the `e2esdk user` **on the client**. + +```js +import { useE2ESDKClient } from '@socialgouv/e2esdk-react' + +const client = useE2ESDKClient() + +// some user uuid from your application +const uuid = session.user.id + +// signup the user to e2esdk +await client.signup(uuid) + +// login existing user +await client.login(uuid) +``` + +The client is password-less thanks to the `localStorage` and once logged-in you can start encrypt/decrypt your payloads. + +> The `uuid` must be stable for a given user, not an email because it could be changed by the user. + +## Database impacts + +If you e2e encrypt your database fields, **your server won't be able to do any processing** on these. Filtering, sorting, grouping will only be available client-side in your frontend UI, once data is decrypted. + +The main challenge regarding converting clear-text data to e2e encrypted in an existing application is that **you cannot migrate data server-side** so you have to implement it client-side and encrypt progressively. + +- we recommend adding a `[fieldname]_encrypted` field to your DB +- on the client side: + - `read`: if the `[fieldname]_encrypted` field is filled, decrypt it and use it. if not, use `[fieldname]` + - `write`: when user updates data, encrypt it and POST the `[fieldname]_encrypted` field +- regularly cleanup cleartext data + +## Read encrypted data + +This is how you could read and decrypt a `firstName` field coming from your API and for which you have a decryption key. + +```js +import { useE2ESDKClient } from '@socialgouv/e2esdk-react' +import { decrypt } from '@socialgouv/e2esdk-crypto' + +const client = useE2ESDKClient() +const sodium = client.sodium // TODO +const customers = await fetch('/api/customers', { + method: 'GET', +}) + .then(r => r.json()) + .then(collection => { + // decrypt the field in each row + return collection.data.map(async customer => { + // if data have been encrypted, decrypt it. if not, use cleartext value if any + const firstName = await (customer.firstName_encrypted + ? decrypt(sodium, input, cipher) + : customer.firstName) + return { + ...customer, + firstName, + } + }) + }) + +console.log(customers) +``` + +> :bulb: In real life, you'll add TypeScript, Zod validation and error management (see examples) + +## Write encrypted data + +This is how you could encrypt some of your fields and POST encrypted data to your API: + +```js +import { useE2ESDKClient } from '@socialgouv/e2esdk-react' + +const client = useE2ESDKClient() + +const firstName = 'Uma' +const lastName = 'Thurman' + +// the encryption key fingerpint, ex: secret-database +const keychainFingerprint = 'secret-database' + +// encrypt values +const firstName_encrypted = await client.encrypt(firstName, keychainFingerprint) +const lastName_encrypted = await client.encrypt(firstName, keychainFingerprint) + +// POST encrypted data to your API +await fetch(API_URL, { + method: 'POST', + body: JSON.stringify({ + firstName_encrypted, + lastName_encrypted, + }), +}) +``` + +Be aware that if other users needs to decrypt the data, you need to share them the key (see below) + +## Key sharing + +As `e2esdk` only act on your client-side application, if you want to share encrypted data with other users, you need to explicitly send them a shared decryption key in the SDK context. + +```js +const client = useE2ESDKClient() + +// this key name, ex: secret-database +const keychainFingerprint = 'secret-database' + +// fetch recipient identity +const recipientIdentity = await client.getUserIdentity('some-user-uuid') + +// share key to recipient +await client.shareKey(keychainFingerprint, recipientIdentity) +``` + +There is no delay or confirmation, if the user is connected, its client-side application will receive the key and will be able to encrypt/decrypt instantly. + +> Both users must have already called `client.signup` to be able to exchange keys. + +## Wipe the clear-text + +Regulary wipe clear-text data from your DB, ex: + +```sql +UPDATE table SET firstName=NULL WHERE firstName_encrypted is not NULL; +``` From a797494fe59c599d4a4cc111e1d620376722f87d Mon Sep 17 00:00:00 2001 From: Julien Bouquillon Date: Wed, 31 May 2023 08:18:40 +0200 Subject: [PATCH 02/10] fix --- docs/docs/use-cases/migrate-to-e2esdk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/use-cases/migrate-to-e2esdk.md b/docs/docs/use-cases/migrate-to-e2esdk.md index b5ca58f..9088c6d 100644 --- a/docs/docs/use-cases/migrate-to-e2esdk.md +++ b/docs/docs/use-cases/migrate-to-e2esdk.md @@ -2,7 +2,7 @@ ## Users identity -If your application has a existing users, you have to explicitly login the `application user` to the `e2esdk user` **on the client**. +If your application has existing users, you have to explicitly login the `application user` to the `e2esdk user` **on the client**. ```js import { useE2ESDKClient } from '@socialgouv/e2esdk-react' From be7e8c5c70ce0836d3483960d0367ed7442bf88c Mon Sep 17 00:00:00 2001 From: Julien Bouquillon Date: Wed, 7 Jun 2023 16:35:41 +0200 Subject: [PATCH 03/10] Update docs/docs/use-cases/migrate-to-e2esdk.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: François Best --- docs/docs/use-cases/migrate-to-e2esdk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/use-cases/migrate-to-e2esdk.md b/docs/docs/use-cases/migrate-to-e2esdk.md index 9088c6d..7365f4a 100644 --- a/docs/docs/use-cases/migrate-to-e2esdk.md +++ b/docs/docs/use-cases/migrate-to-e2esdk.md @@ -2,7 +2,7 @@ ## Users identity -If your application has existing users, you have to explicitly login the `application user` to the `e2esdk user` **on the client**. +If your application has existing users, you have to explicitly onboard the `application user` to let them create a cryptographic identity (the `e2esdk user`). This is done **on the client**. ```js import { useE2ESDKClient } from '@socialgouv/e2esdk-react' From 740b52ea8f4902449ee46c03928c8937b16631a1 Mon Sep 17 00:00:00 2001 From: Julien Bouquillon Date: Wed, 7 Jun 2023 16:35:51 +0200 Subject: [PATCH 04/10] Update docs/docs/use-cases/migrate-to-e2esdk.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: François Best --- docs/docs/use-cases/migrate-to-e2esdk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/use-cases/migrate-to-e2esdk.md b/docs/docs/use-cases/migrate-to-e2esdk.md index 7365f4a..994f5ad 100644 --- a/docs/docs/use-cases/migrate-to-e2esdk.md +++ b/docs/docs/use-cases/migrate-to-e2esdk.md @@ -19,7 +19,7 @@ await client.signup(uuid) await client.login(uuid) ``` -The client is password-less thanks to the `localStorage` and once logged-in you can start encrypt/decrypt your payloads. +The client is password-less thanks to devices storing their secrets in `localStorage`, and once logged-in you can start encrypting/decrypting your payloads. > The `uuid` must be stable for a given user, not an email because it could be changed by the user. From 44a6456c2e0b79c7f195db4f40d1fa330844f69e Mon Sep 17 00:00:00 2001 From: Julien Bouquillon Date: Wed, 7 Jun 2023 16:36:32 +0200 Subject: [PATCH 05/10] Update docs/docs/use-cases/migrate-to-e2esdk.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: François Best --- docs/docs/use-cases/migrate-to-e2esdk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/use-cases/migrate-to-e2esdk.md b/docs/docs/use-cases/migrate-to-e2esdk.md index 994f5ad..3263a95 100644 --- a/docs/docs/use-cases/migrate-to-e2esdk.md +++ b/docs/docs/use-cases/migrate-to-e2esdk.md @@ -85,7 +85,7 @@ const keychainFingerprint = 'secret-database' // encrypt values const firstName_encrypted = await client.encrypt(firstName, keychainFingerprint) -const lastName_encrypted = await client.encrypt(firstName, keychainFingerprint) +const lastName_encrypted = await client.encrypt(lastName, keychainFingerprint) // POST encrypted data to your API await fetch(API_URL, { From ac77ee5e102489725b721450d91f9486cadd870f Mon Sep 17 00:00:00 2001 From: Julien Bouquillon Date: Wed, 7 Jun 2023 16:38:21 +0200 Subject: [PATCH 06/10] Update migrate-to-e2esdk.md --- docs/docs/use-cases/migrate-to-e2esdk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/use-cases/migrate-to-e2esdk.md b/docs/docs/use-cases/migrate-to-e2esdk.md index 3263a95..411950b 100644 --- a/docs/docs/use-cases/migrate-to-e2esdk.md +++ b/docs/docs/use-cases/migrate-to-e2esdk.md @@ -33,7 +33,7 @@ The main challenge regarding converting clear-text data to e2e encrypted in an e - on the client side: - `read`: if the `[fieldname]_encrypted` field is filled, decrypt it and use it. if not, use `[fieldname]` - `write`: when user updates data, encrypt it and POST the `[fieldname]_encrypted` field -- regularly cleanup cleartext data +- progressively cleanup cleartext data (at runtime, when `[fieldname]_encrypted` is provided, or via some cronjob) ## Read encrypted data From 92ebe3cac35c8cf54b0b1b4e74fbe5e5b7eb245e Mon Sep 17 00:00:00 2001 From: Julien Bouquillon Date: Wed, 7 Jun 2023 16:41:50 +0200 Subject: [PATCH 07/10] Update migrate-to-e2esdk.md --- docs/docs/use-cases/migrate-to-e2esdk.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/docs/use-cases/migrate-to-e2esdk.md b/docs/docs/use-cases/migrate-to-e2esdk.md index 411950b..06410aa 100644 --- a/docs/docs/use-cases/migrate-to-e2esdk.md +++ b/docs/docs/use-cases/migrate-to-e2esdk.md @@ -41,10 +41,12 @@ This is how you could read and decrypt a `firstName` field coming from your API ```js import { useE2ESDKClient } from '@socialgouv/e2esdk-react' -import { decrypt } from '@socialgouv/e2esdk-crypto' const client = useE2ESDKClient() -const sodium = client.sodium // TODO + +// the encryption key fingerpint, ex: secret-database +const keychainFingerprint = 'secret-database' + const customers = await fetch('/api/customers', { method: 'GET', }) @@ -54,7 +56,7 @@ const customers = await fetch('/api/customers', { return collection.data.map(async customer => { // if data have been encrypted, decrypt it. if not, use cleartext value if any const firstName = await (customer.firstName_encrypted - ? decrypt(sodium, input, cipher) + ? client.decrypt(customer.firstName_encrypted, keychainFingerprint) : customer.firstName) return { ...customer, From 43b4c7cef32604e198ada47d2cbf33da645cd398 Mon Sep 17 00:00:00 2001 From: Julien Bouquillon Date: Wed, 7 Jun 2023 16:50:58 +0200 Subject: [PATCH 08/10] Update docs/docs/use-cases/migrate-to-e2esdk.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: François Best --- docs/docs/use-cases/migrate-to-e2esdk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/use-cases/migrate-to-e2esdk.md b/docs/docs/use-cases/migrate-to-e2esdk.md index 06410aa..100f2ff 100644 --- a/docs/docs/use-cases/migrate-to-e2esdk.md +++ b/docs/docs/use-cases/migrate-to-e2esdk.md @@ -109,7 +109,7 @@ As `e2esdk` only act on your client-side application, if you want to share encry const client = useE2ESDKClient() // this key name, ex: secret-database -const keychainFingerprint = 'secret-database' +const keychainFingerprint = await client.findKeyByPurpose('secret-database')?.keychainFingerprint // fetch recipient identity const recipientIdentity = await client.getUserIdentity('some-user-uuid') From ae48873e3b031063b86cbe9d7a6ba923b8279518 Mon Sep 17 00:00:00 2001 From: Julien Bouquillon Date: Wed, 7 Jun 2023 16:51:15 +0200 Subject: [PATCH 09/10] Update migrate-to-e2esdk.md --- docs/docs/use-cases/migrate-to-e2esdk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/use-cases/migrate-to-e2esdk.md b/docs/docs/use-cases/migrate-to-e2esdk.md index 100f2ff..32288db 100644 --- a/docs/docs/use-cases/migrate-to-e2esdk.md +++ b/docs/docs/use-cases/migrate-to-e2esdk.md @@ -45,7 +45,7 @@ import { useE2ESDKClient } from '@socialgouv/e2esdk-react' const client = useE2ESDKClient() // the encryption key fingerpint, ex: secret-database -const keychainFingerprint = 'secret-database' +const keychainFingerprint = await client.findKeyByPurpose('secret-database')?.keychainFingerprint const customers = await fetch('/api/customers', { method: 'GET', From 38d507a40858b2986cf59c53dc0df2df35cc996a Mon Sep 17 00:00:00 2001 From: Julien Bouquillon Date: Fri, 9 Jun 2023 14:19:03 +0200 Subject: [PATCH 10/10] Update migrate-to-e2esdk.md --- docs/docs/use-cases/migrate-to-e2esdk.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/use-cases/migrate-to-e2esdk.md b/docs/docs/use-cases/migrate-to-e2esdk.md index 32288db..93d10dd 100644 --- a/docs/docs/use-cases/migrate-to-e2esdk.md +++ b/docs/docs/use-cases/migrate-to-e2esdk.md @@ -82,8 +82,8 @@ const client = useE2ESDKClient() const firstName = 'Uma' const lastName = 'Thurman' -// the encryption key fingerpint, ex: secret-database -const keychainFingerprint = 'secret-database' +// this key name, ex: secret-database +const keychainFingerprint = await client.findKeyByPurpose('secret-database')?.keychainFingerprint // encrypt values const firstName_encrypted = await client.encrypt(firstName, keychainFingerprint)