Browse Source

feat(rtc): encrypt data for full-graph-upload

rcmerci 1 month ago
parent
commit
35f52a4193

+ 1 - 0
deps/db/src/logseq/db.cljs

@@ -543,6 +543,7 @@
 
 (defn get-key-value
   [db key-ident]
+  (assert (= "logseq.kv" (namespace key-ident)) key-ident)
   (:kv/value (d/entity db key-ident)))
 
 (def kv sqlite-util/kv)

+ 4 - 0
src/main/frontend/worker/rtc/const.cljs

@@ -45,3 +45,7 @@
   (into #{}
         (keep (fn [[kw config]] (when (get-in config [:rtc :rtc/ignore-entity-when-init-download]) kw)))
         kv-entity/kv-entities))
+
+(def encrypt-attr-set
+  "block attributes that need to be encrypted"
+  #{:block/title :block/name})

+ 49 - 4
src/main/frontend/worker/rtc/encrypt.cljs

@@ -1,6 +1,47 @@
 (ns frontend.worker.rtc.encrypt
   "rtc e2ee related"
-  (:require [promesa.core :as p]))
+  (:require ["/frontend/idbkv" :as idb-keyval]
+            [logseq.db :as ldb]
+            [promesa.core :as p]))
+
+(def ^:private encoder (js/TextEncoder.))
+(def ^:private decoder (js/TextDecoder.))
+
+;;; TODO: move frontend.idb to deps/, then we can use it in both frontend and db-worker
+;;; now, I just direct use "/frontend/idbkv" here
+(defonce ^:private store (delay (idb-keyval/newStore "localforage" "keyvaluepairs" 2)))
+
+(defn- <get-item
+  [k]
+  (when (and k @store)
+    (idb-keyval/get k @store)))
+
+(defn- <set-item!
+  [k value]
+  (when (and k @store)
+    (idb-keyval/set k value @store)))
+
+(defn- <remove-item!
+  [k]
+  (idb-keyval/del k @store))
+
+(defn- graph-encrypt-key-idb-key
+  [graph-uuid]
+  (assert (some? graph-uuid))
+  (str "rtc-encrypt-key###" graph-uuid))
+
+(defn <get-encrypt-key
+  [graph-uuid]
+  (<get-item (graph-encrypt-key-idb-key graph-uuid)))
+
+(defn <set-encrypt-key!
+  [graph-uuid k]
+  (assert (instance? js/CryptoKey k))
+  (<set-item! (graph-encrypt-key-idb-key graph-uuid) k))
+
+(defn <remove-encrypt-key!
+  [graph-uuid]
+  (<remove-item! (graph-encrypt-key-idb-key graph-uuid)))
 
 (defn- array-buffer->base64 [buffer]
   (let [binary (apply str (map js/String.fromCharCode (js/Uint8Array. buffer)))]
@@ -14,9 +55,6 @@
       (aset bytes' i (.charCodeAt binary-string i)))
     (.-buffer bytes')))
 
-(def ^:private encoder (js/TextEncoder.))
-(def ^:private decoder (js/TextDecoder.))
-
 (defn gen-salt
   []
   (array-buffer->base64 (js/crypto.getRandomValues (js/Uint8Array. 16))))
@@ -65,6 +103,13 @@
                             ciphertext)]
       (.decode decoder decrypted-data))))
 
+(defn <decrypt-text-if-encrypted
+  [key' s]
+  (let [maybe-encrypted-package (ldb/read-transit-str s)]
+    (if (string? maybe-encrypted-package)
+      maybe-encrypted-package
+      (<decrypt-text key' maybe-encrypted-package))))
+
 (comment
   (->
    (p/let [salt (js/crypto.getRandomValues (js/Uint8Array. 16))

+ 34 - 2
src/main/frontend/worker/rtc/full_upload_download_graph.cljs

@@ -123,6 +123,36 @@
                   (:db/ident block) (update :db/ident ldb/read-transit-str)
                   (:block/order block) (update :block/order ldb/read-transit-str)))))))
 
+(defn- task--encrypt-blocks
+  [encrypt-key encrypt-attr-set blocks]
+  (m/sp
+    (loop [[block & rest-blocks] blocks
+           result []]
+      (if-not block
+        result
+        (let [block' (->> block
+                          (map
+                           (fn [[a v]]
+                             (m/sp
+                               (if (and (contains? encrypt-attr-set a) (string? v))
+                                 [a (ldb/write-transit-str (c.m/<? (rtc-encrypt/<encrypt-text encrypt-key v)))]
+                                 [a v]))))
+                          (apply m/join vector)
+                          (m/?)
+                          (into {}))]
+          (recur rest-blocks (conj result block')))))))
+
+(comment
+  (def db @(frontend.worker.state/get-datascript-conn (frontend.worker.state/get-current-repo)))
+  (def blocks (export-as-blocks db))
+  (def salt (rtc-encrypt/gen-salt))
+  (def canceler ((m/sp
+                   (let [k (c.m/<? (rtc-encrypt/<salt+password->key salt "password"))]
+                     (m/? (task--encrypt-blocks k #{:block/title :block/name} blocks))))
+                 #(def encrypted-blocks %) prn))
+
+  )
+
 (defn new-task--upload-graph
   [get-ws-create-task repo conn remote-graph-name major-schema-version]
   (m/sp
@@ -138,8 +168,10 @@
               (let [all-blocks (export-as-blocks
                                 @conn
                                 :ignore-attr-set rtc-const/ignore-attrs-when-init-upload
-                                :ignore-entity-set rtc-const/ignore-entities-when-init-upload)]
-                (ldb/write-transit-str all-blocks)))))]
+                                :ignore-entity-set rtc-const/ignore-entities-when-init-upload)
+                    encrypt-key-for-test (c.m/<? (rtc-encrypt/<salt+password->key (ldb/get-key-value @conn :logseq.kv/graph-rtc-encrypt-salt) "test-password"))
+                    encrypted-blocks (c.m/<? (task--encrypt-blocks encrypt-key-for-test rtc-const/encrypt-attr-set all-blocks))]
+                (ldb/write-transit-str encrypted-blocks)))))]
       (rtc-log-and-state/rtc-log :rtc.log/upload {:sub-type :upload-data
                                                   :message "uploading data"})
       (m/? (http/put url {:body all-blocks-str :with-credentials? false}))