From 649d5673b3ac45ac01e6ba3c2e97c320cabe0511 Mon Sep 17 00:00:00 2001 From: Sean Walker Date: Wed, 12 Feb 2020 14:21:33 -0700 Subject: [PATCH] Stop base64 encoding encryption keys. Copy rails for request forgery --- project.janet | 2 +- src/joy/cli/projects.janet | 2 +- src/joy/form-helper.janet | 5 ++- src/joy/middleware.janet | 92 +++++++++++++++++++------------------- 4 files changed, 51 insertions(+), 50 deletions(-) diff --git a/project.janet b/project.janet index 467ca3a..13227a1 100644 --- a/project.janet +++ b/project.janet @@ -4,7 +4,7 @@ :dependencies [{:repo "https://github.com/janet-lang/json" :tag "fc59d46f06501569c21d18fff3df15e1494bf144"} {:repo "https://github.com/janet-lang/sqlite3" :tag "a3a254003c605cf4e048963feda70a60537057d9"} {:repo "https://github.com/janet-lang/path" :tag "d8619960d428c45ebb784600771a7c584ae49431"} - {:repo "https://github.com/joy-framework/cipher" :tag "87fc9bc38b335d0f31c93d6c95f35b8a6abce6af"} + {:repo "https://github.com/joy-framework/cipher" :tag "0.2.0"} {:repo "https://github.com/joy-framework/codec" :tag "f50c00462b9d048034ee8bdd2f6af6e67bc5aff4"} {:repo "https://github.com/joy-framework/halo" :tag "fd34201fee41b372314aede9e72044d711510afa"} {:repo "https://github.com/andrewchambers/janet-uri" :tag "d191ed238dc7c4966f121f9f4c40b19cc75e34ee"} diff --git a/src/joy/cli/projects.janet b/src/joy/cli/projects.janet index 7ddd0f3..885dd0e 100644 --- a/src/joy/cli/projects.janet +++ b/src/joy/cli/projects.janet @@ -24,6 +24,6 @@ (helper/with-file [f (path/join project-name ".env") :r] (set tmp (->> (file/read f :all) - (string/replace-all "%encryption-key%" (-> (cipher/password-key) (codec/encode)))))) + (string/replace-all "%encryption-key%" (cipher/encryption-key))))) (helper/with-file [f (path/join project-name ".env") :w] (file/write f tmp)))) diff --git a/src/joy/form-helper.janet b/src/joy/form-helper.janet index 016df96..5ea2754 100644 --- a/src/joy/form-helper.janet +++ b/src/joy/form-helper.janet @@ -1,4 +1,5 @@ (import ./router :as router) +(import ./middleware :as middleware) (defn- field [kind val key & attrs] @@ -79,8 +80,8 @@ action (apply router/action-for (drop 1 action-args))] [:form action body - (when (truthy? (request :csrf-token)) - (hidden-field request :csrf-token)) + (when (get request :csrf-token) + [:input {:type "hidden" :name "__csrf-token" :value (middleware/form-csrf-token request)}]) (when (truthy? (action :_method)) (hidden-field action :_method))])) diff --git a/src/joy/middleware.janet b/src/joy/middleware.janet index 056c8ee..c3a57ab 100644 --- a/src/joy/middleware.janet +++ b/src/joy/middleware.janet @@ -60,18 +60,15 @@ (http/cookie-string cookie-name cookie-value options))))) -(defn- deserialize-session [decrypted] - (when (and (not (nil? decrypted)) - (not (empty? decrypted))) - (unmarshal decrypted))) +(defn- safe-unmarshal [val] + (unless (or (nil? val) (empty? val)) + (unmarshal val))) (defn- decrypt-session [key str] (when (string? str) (try - (as-> str ? - (base64/decode ?) - (cipher/decrypt key ?)) + (cipher/decrypt key str) ([err] (unless (= err "decryption failed") (error err)))))) @@ -82,15 +79,14 @@ (truthy? key)) (as-> str ? (decrypt-session key ?) - (deserialize-session ?)))) + (safe-unmarshal ?)))) (defn- encode-session [val key] (when (truthy? key) (->> (marshal val) (string) - (cipher/encrypt key) - (base64/encode)))) + (cipher/encrypt key)))) (defn- session-from-request [key request] @@ -101,67 +97,71 @@ (defn session [handler] - (let [key (base64/decode (env/env :encryption-key))] + (let [key (env/env :encryption-key)] (fn [request] (let [request-session (or (session-from-request key request) @{}) response (handler (merge request request-session)) session-value (or (get response :session) - (get request-session :session)) - session-id (or (get request-session :session-id) - (base64/encode (cipher/password-key)))] - (let [joy-session {:session session-value :session-id session-id :csrf-token (get response :csrf-token)}] + (get request-session :session))] + (let [joy-session {:session session-value :csrf-token (get response :csrf-token)}] (when (truthy? response) (put-in response [:headers "Set-Cookie"] (http/cookie-string "id" (encode-session joy-session key) {"SameSite" "Strict" "HttpOnly" "" "Path" "/"})))))))) -(defn session-id [request] - (when-let [id (get request :session-id)] - (base64/decode id))) +(defn xor-byte-strings [str1 str2] + (let [arr @[] + bytes1 (string/bytes str1) + bytes2 (string/bytes str2)] + (loop [i :range [0 32]] + (array/push arr (bxor (get bytes1 i) (get bytes2 i)))) + (string/from-bytes ;arr))) -(defn decode-token [token session-id] - (when (truthy? token) - (try - (->> (base64/decode token) - (cipher/decrypt session-id)) - ([err] - (unless (= err "decryption failed") - (error err)))))) +(defn mask-token [request] + (let [pad (os/cryptorand 32) + csrf-token (get request :csrf-token) + masked-token (xor-byte-strings pad csrf-token)] + (base64/encode (string pad masked-token)))) + + +(defn session-csrf-token [request] + (or (get request :csrf-token) + (os/cryptorand 32))) (defn form-csrf-token [request] - (decode-token (get-in request [:body :csrf-token]) - (session-id request))) + (mask-token request)) -(defn session-token [request] - (decode-token (get request :csrf-token) - (session-id request))) +(defn csrf-tokens-equal? [form-token session-token] + (cipher/secure-compare form-token session-token)) -(defn new-csrf-token [session-id] - (when (truthy? session-id) - (->> (rand-str 20) (cipher/encrypt session-id) (base64/encode)))) +(defn unmask-token [request] + (let [masked-token (get-in request [:body :__csrf-token]) + _ (when (nil? masked-token) + (error "Required parameter __csrf-token not found")) + token (base64/decode masked-token) + pad (string/slice token 0 32) + csrf-token (string/slice token 32)] + (xor-byte-strings pad csrf-token))) (defn csrf-token [handler] (fn [request] - (let [session-id (session-id request) - new-csrf-token (new-csrf-token session-id)] + (let [session-token (session-csrf-token request)] (if (or (head? request) (get? request)) - (let [response (handler (put request :csrf-token new-csrf-token))] - (when (truthy? response) - (put response :csrf-token new-csrf-token))) - (let [session-token (session-token request) - form-csrf-token (form-csrf-token request)] - (if (= form-csrf-token session-token) - (let [response (handler request)] - (when (truthy? response) - (put response :csrf-token new-csrf-token))) - (responder/render :text "Invalid CSRF Token" :status 403))))))) + (when-let [response (handler request)] + (put response :csrf-token session-token)) + (let [form-token (unmask-token request)] + (if (csrf-tokens-equal? form-token session-token) + (when-let [response (handler request)] + (put response :csrf-token session-token)) + (-> (responder/render :text "Invalid CSRF Token" :status 403) + (put :csrf-token session-token)))))))) (defn x-headers [handler &opt options]