Skip to content

Commit 8de2542

Browse files
committed
use salted PBKDF2 hash
1 parent f9a3fb5 commit 8de2542

3 files changed

Lines changed: 96 additions & 32 deletions

File tree

deps/publish/src/logseq/publish/common.cljs

Lines changed: 87 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,89 @@
120120
digest (.digest js/crypto.subtle "SHA-256" data)]
121121
(to-hex digest)))
122122

123+
(def password-kdf-iterations 210000)
124+
125+
(defn bytes->base64url [bytes]
126+
(let [binary (apply str (map #(js/String.fromCharCode %) (array-seq bytes)))
127+
b64 (js/btoa binary)]
128+
(-> b64
129+
(string/replace #"\+" "-")
130+
(string/replace #"/" "_")
131+
(string/replace #"=+$" ""))))
132+
133+
(defn hash-password [password]
134+
(js-await [salt (doto (js/Uint8Array. 16)
135+
(js/crypto.getRandomValues))
136+
crypto-key (.importKey js/crypto.subtle
137+
"raw"
138+
(.encode text-encoder password)
139+
#js {:name "PBKDF2"}
140+
false
141+
#js ["deriveBits"])
142+
derived (.deriveBits js/crypto.subtle
143+
#js {:name "PBKDF2"
144+
:hash "SHA-256"
145+
:salt salt
146+
:iterations password-kdf-iterations}
147+
crypto-key
148+
256)
149+
derived-bytes (js/Uint8Array. derived)
150+
salt-encoded (bytes->base64url salt)
151+
hash-encoded (bytes->base64url derived-bytes)]
152+
(str "pbkdf2$sha256$"
153+
password-kdf-iterations
154+
"$"
155+
salt-encoded
156+
"$"
157+
hash-encoded)))
158+
159+
(defn base64url->uint8array [input]
160+
(let [pad (if (pos? (mod (count input) 4))
161+
(apply str (repeat (- 4 (mod (count input) 4)) "="))
162+
"")
163+
base64 (-> (str input pad)
164+
(string/replace "-" "+")
165+
(string/replace "_" "/"))
166+
raw (js/atob base64)
167+
data (js/Uint8Array. (.-length raw))]
168+
(dotimes [i (.-length raw)]
169+
(aset data i (.charCodeAt raw i)))
170+
data))
171+
172+
(defn verify-password [password stored-hash]
173+
(let [parts (when (string? stored-hash)
174+
(string/split stored-hash #"\$"))]
175+
(if-not (and (= 5 (count parts))
176+
(= "pbkdf2" (nth parts 0))
177+
(= "sha256" (nth parts 1)))
178+
false
179+
(js-await [iterations (js/parseInt (nth parts 2))
180+
salt (base64url->uint8array (nth parts 3))
181+
expected (base64url->uint8array (nth parts 4))
182+
crypto-key (.importKey js/crypto.subtle
183+
"raw"
184+
(.encode text-encoder password)
185+
#js {:name "PBKDF2"}
186+
false
187+
#js ["deriveBits"])
188+
derived (.deriveBits js/crypto.subtle
189+
#js {:name "PBKDF2"
190+
:hash "SHA-256"
191+
:salt salt
192+
:iterations iterations}
193+
crypto-key
194+
(* 8 (.-length expected)))
195+
derived-bytes (js/Uint8Array. derived)]
196+
(if (not= (.-length derived-bytes) (.-length expected))
197+
false
198+
(let [mismatch (reduce (fn [acc idx]
199+
(bit-or acc
200+
(bit-xor (aget derived-bytes idx)
201+
(aget expected idx))))
202+
0
203+
(range (.-length expected)))]
204+
(zero? mismatch)))))))
205+
123206
(defn hmac-sha256 [key message]
124207
(js-await [crypto-key (.importKey js/crypto.subtle
125208
"raw"
@@ -194,22 +277,9 @@
194277
signed-query (str canonical-query "&X-Amz-Signature=" signature)]
195278
(str "https://" host canonical-uri "?" signed-query)))
196279

197-
(defn base64url->uint8array [input]
198-
(let [pad (if (pos? (mod (count input) 4))
199-
(apply str (repeat (- 4 (mod (count input) 4)) "="))
200-
"")
201-
base64 (-> (str input pad)
202-
(string/replace "-" "+")
203-
(string/replace "_" "/"))
204-
raw (js/atob base64)
205-
bytes (js/Uint8Array. (.-length raw))]
206-
(dotimes [i (.-length raw)]
207-
(aset bytes i (.charCodeAt raw i)))
208-
bytes))
209-
210280
(defn decode-jwt-part [part]
211-
(let [bytes (base64url->uint8array part)]
212-
(js/JSON.parse (.decode text-decoder bytes))))
281+
(let [data (base64url->uint8array part)]
282+
(js/JSON.parse (.decode text-decoder data))))
213283

214284
(defn import-rsa-key [jwk]
215285
(.importKey js/crypto.subtle
@@ -254,17 +324,9 @@
254324
(when etag
255325
(string/replace etag #"\"" "")))
256326

257-
(defn bytes->base64url [bytes]
258-
(let [binary (apply str (map #(js/String.fromCharCode %) (array-seq bytes)))
259-
b64 (js/btoa binary)]
260-
(-> b64
261-
(string/replace #"\+" "-")
262-
(string/replace #"/" "_")
263-
(string/replace #"=+$" ""))))
264-
265327
(defn short-id-for-page [graph-uuid page-uuid]
266328
(js-await [payload (.encode text-encoder (str graph-uuid ":" page-uuid))
267329
digest (.digest js/crypto.subtle "SHA-256" payload)]
268-
(let [bytes (js/Uint8Array. digest)
269-
encoded (bytes->base64url bytes)]
330+
(let [data (js/Uint8Array. digest)
331+
encoded (bytes->base64url data)]
270332
(subs encoded 0 10))))

deps/publish/src/logseq/publish/routes.cljs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@
3939
{:allowed? true :provided? false}
4040
(let [provided (request-password request)]
4141
(if (string? provided)
42-
(js-await [hashed (publish-common/sha256-hex provided)]
43-
{:allowed? (= hashed stored-hash) :provided? true})
42+
(js-await [valid? (publish-common/verify-password provided stored-hash)]
43+
{:allowed? valid? :provided? true})
4444
{:allowed? false :provided? false})))))
4545

4646
(defn- auth-claims
@@ -78,8 +78,8 @@
7878
(publish-model/entity->title (get payload-entities page-eid))))
7979
blocks (or (:blocks payload)
8080
(get payload "blocks"))
81-
page-password-hash (or (:page-password-hash payload)
82-
(get payload "page-password-hash"))
81+
page-password (or (:page-password payload)
82+
(get payload "page-password"))
8383
refs (when (and page-eid page-title)
8484
(publish-index/page-refs-from-payload payload page-eid page_uuid page-title graph))
8585
tagged-nodes (when (and page-eid page-title)
@@ -113,12 +113,15 @@
113113
(throw (ex-info "owner sub or username is missing"
114114
{:owner-sub owner-sub
115115
:owner-username owner-username})))
116+
password-hash (when (and (string? page-password)
117+
(not (string/blank? page-password)))
118+
(publish-common/hash-password page-password))
116119
payload (bean/->js
117120
{:page_uuid page_uuid
118121
:page_title page-title
119122
:page_tags (when page-tags
120123
(js/JSON.stringify (clj->js page-tags)))
121-
:password_hash page-password-hash
124+
:password_hash password-hash
122125
:graph graph-uuid
123126
:schema_version schema_version
124127
:block_count block_count

src/main/frontend/handler/publish.cljs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,9 +321,8 @@
321321
page-password (when (and (string? page-password)
322322
(not (string/blank? page-password)))
323323
page-password)
324-
page-password-hash (when page-password (<sha256-hex page-password))
325324
payload (cond-> payload
326-
page-password-hash (assoc :page-password-hash page-password-hash))
325+
page-password (assoc :page-password page-password))
327326
body (ldb/write-transit-str payload)
328327
content-hash (<sha256-hex body)
329328
graph-uuid (or (:graph-uuid payload)

0 commit comments

Comments
 (0)