|
120 | 120 | digest (.digest js/crypto.subtle "SHA-256" data)] |
121 | 121 | (to-hex digest))) |
122 | 122 |
|
| 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 | + |
123 | 206 | (defn hmac-sha256 [key message] |
124 | 207 | (js-await [crypto-key (.importKey js/crypto.subtle |
125 | 208 | "raw" |
|
194 | 277 | signed-query (str canonical-query "&X-Amz-Signature=" signature)] |
195 | 278 | (str "https://" host canonical-uri "?" signed-query))) |
196 | 279 |
|
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 | | - |
210 | 280 | (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)))) |
213 | 283 |
|
214 | 284 | (defn import-rsa-key [jwk] |
215 | 285 | (.importKey js/crypto.subtle |
|
254 | 324 | (when etag |
255 | 325 | (string/replace etag #"\"" ""))) |
256 | 326 |
|
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 | | - |
265 | 327 | (defn short-id-for-page [graph-uuid page-uuid] |
266 | 328 | (js-await [payload (.encode text-encoder (str graph-uuid ":" page-uuid)) |
267 | 329 | 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)] |
270 | 332 | (subs encoded 0 10)))) |
0 commit comments