mirror of
https://github.com/logseq/logseq.git
synced 2026-05-27 22:24:09 +00:00
315 lines
13 KiB
Clojure
315 lines
13 KiB
Clojure
(ns frontend.common.crypt
|
|
"crypto utils"
|
|
(:require [lambdaisland.glogi :as log]
|
|
[logseq.db :as ldb]
|
|
[promesa.core :as p]))
|
|
|
|
(defonce subtle (.. js/crypto -subtle))
|
|
|
|
(defn <export-aes-key
|
|
[aes-key]
|
|
(assert (instance? js/CryptoKey aes-key))
|
|
(p/let [exported (.exportKey subtle "raw" aes-key)]
|
|
(js/Uint8Array. exported)))
|
|
|
|
(defn <import-aes-key
|
|
[exported-aes-key]
|
|
(assert (instance? js/Uint8Array exported-aes-key))
|
|
(.importKey subtle
|
|
"raw"
|
|
exported-aes-key
|
|
"AES-GCM"
|
|
true
|
|
#js ["encrypt" "decrypt"]))
|
|
|
|
(defn <export-public-key
|
|
[public-key]
|
|
(assert (instance? js/CryptoKey public-key))
|
|
(p/let [exported (.exportKey subtle "spki" public-key)]
|
|
(js/Uint8Array. exported)))
|
|
|
|
(defn <import-public-key
|
|
[exported-public-key]
|
|
(assert (instance? js/Uint8Array exported-public-key))
|
|
(.importKey subtle "spki" exported-public-key
|
|
#js {:name "RSA-OAEP" :hash "SHA-256"}
|
|
true
|
|
#js ["encrypt"]))
|
|
|
|
(defn <export-private-key
|
|
[private-key]
|
|
(assert (instance? js/CryptoKey private-key))
|
|
(p/let [exported (.exportKey subtle "pkcs8" private-key)]
|
|
(js/Uint8Array. exported)))
|
|
|
|
(defn <import-private-key
|
|
[exported-private-key]
|
|
(assert (instance? js/Uint8Array exported-private-key))
|
|
(.importKey subtle "pkcs8" exported-private-key
|
|
#js {:name "RSA-OAEP" :hash "SHA-256"}
|
|
true
|
|
#js ["decrypt"]))
|
|
|
|
(comment
|
|
(->
|
|
(p/let [kp (<generate-rsa-key-pair)
|
|
public-key (:publicKey kp)
|
|
exported-public-key (<export-public-key public-key)
|
|
public-key* (<import-public-key exported-public-key)
|
|
exported-public-key2 (<export-public-key public-key*)]
|
|
(prn (= (vec exported-public-key) (vec exported-public-key2))))
|
|
(p/catch (fn [e] (prn :e e)))))
|
|
|
|
(defn <generate-rsa-key-pair
|
|
"Generates a new RSA public/private key pair.
|
|
Return
|
|
{:publicKey #object [CryptoKey [object CryptoKey]],
|
|
:privateKey #object [CryptoKey [object CryptoKey]]}"
|
|
[]
|
|
(p/let [r (.generateKey subtle
|
|
#js {:name "RSA-OAEP"
|
|
:modulusLength 4096
|
|
:publicExponent (js/Uint8Array. [1 0 1])
|
|
:hash "SHA-256"}
|
|
true
|
|
#js ["encrypt" "decrypt"])]
|
|
{:publicKey (.-publicKey r)
|
|
:privateKey (.-privateKey r)}))
|
|
|
|
(defn <generate-aes-key
|
|
"Generates a new AES-GCM-256 key."
|
|
[]
|
|
(.generateKey subtle
|
|
#js {:name "AES-GCM"
|
|
:length 256}
|
|
true
|
|
#js ["encrypt" "decrypt"]))
|
|
|
|
(defn <encrypt-private-key
|
|
"Encrypts a private key with a password.
|
|
Return encrypted-data which is a vector of [version, salt, iv, encrypted-private-key]"
|
|
[password private-key]
|
|
(assert (and (string? password) (instance? js/CryptoKey private-key)))
|
|
(p/let [salt (js/crypto.getRandomValues (js/Uint8Array. 16))
|
|
iv (js/crypto.getRandomValues (js/Uint8Array. 12))
|
|
password-key (.importKey subtle "raw"
|
|
(.encode (js/TextEncoder.) password)
|
|
"PBKDF2"
|
|
false
|
|
#js ["deriveKey"])
|
|
derived-key (.deriveKey subtle
|
|
#js {:name "PBKDF2"
|
|
:salt salt
|
|
:iterations 600000
|
|
:hash "SHA-256"}
|
|
password-key
|
|
#js {:name "AES-GCM" :length 256}
|
|
true
|
|
#js ["encrypt" "decrypt"])
|
|
exported-private-key (.exportKey subtle "pkcs8" private-key)
|
|
encrypted-private-key (.encrypt subtle
|
|
#js {:name "AES-GCM" :iv iv}
|
|
derived-key
|
|
exported-private-key)]
|
|
["20251210" salt iv (js/Uint8Array. encrypted-private-key)]))
|
|
|
|
(defn <decrypt-private-key
|
|
"Decrypts a private key with a password.
|
|
encrypted-key-data is vector of
|
|
[version, salt, iv, encrypted-private-key-data]
|
|
or (old version)
|
|
[salt, iv, encrypted-private-key-data]"
|
|
[password encrypted-key-data]
|
|
(assert (and (vector? encrypted-key-data) (<= 3 (count encrypted-key-data))))
|
|
(->
|
|
(p/let [len (count encrypted-key-data)
|
|
[salt-data iv-data encrypted-private-key-data] (if (= len 3)
|
|
encrypted-key-data
|
|
(drop 1 encrypted-key-data))
|
|
version (when (= len 4) (first encrypted-key-data))
|
|
version>=20251210 (>= (compare version "20251210") 0)
|
|
salt (js/Uint8Array. salt-data)
|
|
iv (js/Uint8Array. iv-data)
|
|
encrypted-private-key (js/Uint8Array. encrypted-private-key-data)
|
|
password-key (.importKey subtle "raw"
|
|
(.encode (js/TextEncoder.) password)
|
|
"PBKDF2"
|
|
false
|
|
#js ["deriveKey"])
|
|
derived-key (.deriveKey subtle
|
|
#js {:name "PBKDF2"
|
|
:salt salt
|
|
:iterations (if version>=20251210 600000 100000)
|
|
:hash "SHA-256"}
|
|
password-key
|
|
#js {:name "AES-GCM" :length 256}
|
|
true
|
|
#js ["encrypt" "decrypt"])
|
|
decrypted-private-key-data (.decrypt subtle
|
|
#js {:name "AES-GCM" :iv iv}
|
|
derived-key
|
|
encrypted-private-key)
|
|
private-key (.importKey subtle "pkcs8"
|
|
decrypted-private-key-data
|
|
#js {:name "RSA-OAEP" :hash "SHA-256"}
|
|
true
|
|
#js ["decrypt"])]
|
|
private-key)
|
|
(p/catch (fn [e]
|
|
(log/error "decrypt-private-key" e)
|
|
(ex-info "decrypt-private-key" {} e)))))
|
|
|
|
(defn <encrypt-aes-key
|
|
"Encrypts an AES key with a public key."
|
|
[public-key aes-key]
|
|
(assert (and (instance? js/CryptoKey public-key)
|
|
(instance? js/CryptoKey aes-key)))
|
|
(p/let [exported-aes-key (<export-aes-key aes-key)
|
|
encrypted-aes-key (.encrypt subtle
|
|
#js {:name "RSA-OAEP"}
|
|
public-key
|
|
exported-aes-key)]
|
|
(js/Uint8Array. encrypted-aes-key)))
|
|
|
|
(defn <decrypt-aes-key
|
|
"Decrypts an AES key with a private key."
|
|
[private-key encrypted-aes-key-data]
|
|
(assert (and (instance? js/CryptoKey private-key)
|
|
(instance? js/Uint8Array encrypted-aes-key-data)))
|
|
(->
|
|
(p/let [encrypted-aes-key (js/Uint8Array. encrypted-aes-key-data)
|
|
decrypted-key-data (.decrypt subtle
|
|
#js {:name "RSA-OAEP"}
|
|
private-key
|
|
encrypted-aes-key)]
|
|
(.importKey subtle
|
|
"raw"
|
|
decrypted-key-data
|
|
"AES-GCM"
|
|
true
|
|
#js ["encrypt" "decrypt"]))
|
|
(p/catch (fn [e]
|
|
(log/error "decrypt-aes-key failed" e)
|
|
(ex-info "decrypt-aes-key" {} e)))))
|
|
|
|
(defn <encrypt-uint8array
|
|
[aes-key arr]
|
|
(assert (and (instance? js/CryptoKey aes-key) (instance? js/Uint8Array arr)))
|
|
(p/let [iv (js/crypto.getRandomValues (js/Uint8Array. 12))
|
|
encrypted-data (.encrypt subtle
|
|
#js {:name "AES-GCM" :iv iv}
|
|
aes-key
|
|
arr)]
|
|
[iv (js/Uint8Array. encrypted-data)]))
|
|
|
|
(defn <decrypt-uint8array
|
|
[aes-key encrypted-data-vector]
|
|
(->
|
|
(p/let [[iv-data encrypted-data] encrypted-data-vector
|
|
_ (assert (instance? js/Uint8Array encrypted-data))
|
|
iv (js/Uint8Array. iv-data)
|
|
decrypted-data (.decrypt subtle
|
|
#js {:name "AES-GCM" :iv iv}
|
|
aes-key
|
|
encrypted-data)]
|
|
(js/Uint8Array. decrypted-data))
|
|
(p/catch
|
|
(fn [e]
|
|
(log/error "decrypt-uint8array" e)
|
|
(ex-info "decrypt-uint8array" {} e)))))
|
|
|
|
(defn <encrypt-text
|
|
"Encrypts text with an AES key."
|
|
[aes-key text]
|
|
(assert (and (string? text) (ldb/read-transit-str text)) "text must be transit-encoded")
|
|
(assert (instance? js/CryptoKey aes-key))
|
|
(p/let [iv (js/crypto.getRandomValues (js/Uint8Array. 12))
|
|
encoded-text (.encode (js/TextEncoder.) text)
|
|
encrypted-data (.encrypt subtle
|
|
#js {:name "AES-GCM" :iv iv}
|
|
aes-key
|
|
encoded-text)]
|
|
[iv (js/Uint8Array. encrypted-data)]))
|
|
|
|
(defn <decrypt-text
|
|
"Decrypts text with an AES key."
|
|
[aes-key encrypted-text-data-vector]
|
|
(-> (p/let [[iv-data encrypted-data] encrypted-text-data-vector
|
|
iv (js/Uint8Array. iv-data)
|
|
encrypted-data (js/Uint8Array. encrypted-data)
|
|
decrypted-data (.decrypt subtle
|
|
#js {:name "AES-GCM" :iv iv}
|
|
aes-key
|
|
encrypted-data)
|
|
decoded-text (.decode (js/TextDecoder.) decrypted-data)]
|
|
decoded-text)
|
|
(p/catch
|
|
(fn [e]
|
|
(log/error "decrypt-text" e)
|
|
(ex-info "decrypt-text" {} e)))))
|
|
|
|
(defn <decrypt-text-if-encrypted
|
|
"return nil if not a encrypted-package"
|
|
[aes-key maybe-encrypted-package]
|
|
(when (and (vector? maybe-encrypted-package)
|
|
(<= 2 (count maybe-encrypted-package)))
|
|
(<decrypt-text aes-key maybe-encrypted-package)))
|
|
|
|
(defn <encrypt-text-by-text-password
|
|
[text-password text]
|
|
(assert (and (string? text-password) (string? text)))
|
|
(p/let [salt (js/crypto.getRandomValues (js/Uint8Array. 16))
|
|
iv (js/crypto.getRandomValues (js/Uint8Array. 12))
|
|
password-key (.importKey subtle "raw"
|
|
(.encode (js/TextEncoder.) text-password)
|
|
"PBKDF2"
|
|
false
|
|
#js ["deriveKey"])
|
|
derived-key (.deriveKey subtle
|
|
#js {:name "PBKDF2"
|
|
:salt salt
|
|
:iterations 600000
|
|
:hash "SHA-256"}
|
|
password-key
|
|
#js {:name "AES-GCM" :length 256}
|
|
true
|
|
#js ["encrypt" "decrypt"])
|
|
encoded-text (.encode (js/TextEncoder.) text)
|
|
encrypted-text (.encrypt subtle
|
|
#js {:name "AES-GCM" :iv iv}
|
|
derived-key
|
|
encoded-text)]
|
|
[salt iv (js/Uint8Array. encrypted-text)]))
|
|
|
|
(defn <decrypt-text-by-text-password
|
|
[text-password encrypted-data-vector]
|
|
(assert (and (string? text-password) (vector? encrypted-data-vector)))
|
|
(->
|
|
(p/let [[salt-data iv-data encrypted-data] encrypted-data-vector
|
|
salt (js/Uint8Array. salt-data)
|
|
iv (js/Uint8Array. iv-data)
|
|
encrypted-data (js/Uint8Array. encrypted-data)
|
|
password-key (.importKey subtle "raw"
|
|
(.encode (js/TextEncoder.) text-password)
|
|
"PBKDF2"
|
|
false
|
|
#js ["deriveKey"])
|
|
derived-key (.deriveKey subtle
|
|
#js {:name "PBKDF2"
|
|
:salt salt
|
|
:iterations 600000
|
|
:hash "SHA-256"}
|
|
password-key
|
|
#js {:name "AES-GCM" :length 256}
|
|
true
|
|
#js ["encrypt" "decrypt"])
|
|
decrypted-data (.decrypt subtle
|
|
#js {:name "AES-GCM" :iv iv}
|
|
derived-key
|
|
encrypted-data)]
|
|
(.decode (js/TextDecoder.) decrypted-data))
|
|
(p/catch
|
|
(fn [e]
|
|
(log/error "decrypt-text-by-text-password" e)
|
|
(ex-info "decrypt-text-by-text-password" {} e)))))
|