├── .travis.yml ├── test ├── files │ ├── ec │ │ ├── dummy.key │ │ ├── public.key │ │ └── private.key │ └── rsa │ │ ├── 3des.pub.key │ │ ├── no_pass.pub.key │ │ ├── dummy.key │ │ ├── no_pass.key │ │ └── 3des.key └── clj_jwt │ ├── json_key_fn_test.clj │ ├── intdate_test.clj │ ├── attack_test.clj │ ├── sign_test.clj │ ├── key_test.clj │ ├── base64_test.clj │ └── core_test.clj ├── .gitignore ├── src └── clj_jwt │ ├── json_key_fn.clj │ ├── intdate.clj │ ├── base64.clj │ ├── key.clj │ ├── sign.clj │ └── core.clj ├── project.clj └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | lein: lein2 3 | script: lein2 midje 4 | -------------------------------------------------------------------------------- /test/files/ec/dummy.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEnYqYze74sz8aLB7FM2/Zy46UKew9oc51 3 | CzFOprxNk+NMI1vhRmw1kW2B/q7asQFC9/JdHiKcH+sYMlVb7RrzVg== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | .lein-repl-history 13 | .nrepl-port 14 | -------------------------------------------------------------------------------- /test/files/ec/public.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEMJpmzEF1SskoToLDRxIfT9oZ4EjSTw4Z 3 | Kx8q1iDAmNgY2+NEP0OGleP3tcAXLsE1GFZAGd91HuMNMHqeHN/mNw== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /test/files/ec/private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHQCAQEEIAKZoQ/oANuMJQPLur8HMr/cC+f8FFJkx83EEFX6Jf7/oAcGBSuBBAAK 3 | oUQDQgAEMJpmzEF1SskoToLDRxIfT9oZ4EjSTw4ZKx8q1iDAmNgY2+NEP0OGleP3 4 | tcAXLsE1GFZAGd91HuMNMHqeHN/mNw== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /test/files/rsa/3des.pub.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcEfIgOOMqMUpSoLGUlmcAdoes 3 | H2i1zeGDf7mlzj/IusPoWdtLtLKoxGId93ijW1rnEDv6FF+DJXoV6zCS6cN6kx8Z 4 | DPg7tRb1oPbgM9tsyS5LdvJI3Tp3xbKHc+kVJMc0lpcpplOoAS4p7SK/76aXCN+S 5 | 6G40OWoV10FISOW1/QIDAQAB 6 | -----END PUBLIC KEY----- 7 | -------------------------------------------------------------------------------- /test/files/rsa/no_pass.pub.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7GCvT//6UKOcM5cZb45CvK9n3 3 | zce71AYTMHMRWh9leARvhGspEHkGHB/5d2TBvqulssNAwT82Wpgemeh995arbm4Y 4 | dcKEg9SbHEKOKt+BQNCHZ53E5YGXSGcKLkJxPnFQT8TDSRZfzh2zs+lw4RO99QVR 5 | vltKEwQ4l6ik9bx8FQIDAQAB 6 | -----END PUBLIC KEY----- 7 | -------------------------------------------------------------------------------- /src/clj_jwt/json_key_fn.clj: -------------------------------------------------------------------------------- 1 | (ns clj-jwt.json-key-fn 2 | (:require 3 | [clojure.string :as str])) 4 | 5 | (defn write-key 6 | [x] 7 | (cond 8 | (string? x) (str "\"" x "\"") 9 | :else (name x))) 10 | 11 | (defn read-key 12 | [x] 13 | (if-let [y (re-seq #"^\"(.*)\"$" x)] 14 | (-> y first second) 15 | (keyword x))) 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/clj_jwt/json_key_fn_test.clj: -------------------------------------------------------------------------------- 1 | (ns clj-jwt.json-key-fn-test 2 | (:require 3 | [clj-jwt.json-key-fn :refer :all] 4 | [midje.sweet :refer :all])) 5 | 6 | (fact "write-key should work fine." 7 | (write-key :foo) => "foo" 8 | (write-key "foo") => "\"foo\"") 9 | 10 | (fact "read-key should work fine." 11 | (read-key "foo") => :foo 12 | (read-key "\"foo\"") => "foo") 13 | -------------------------------------------------------------------------------- /src/clj_jwt/intdate.clj: -------------------------------------------------------------------------------- 1 | (ns clj-jwt.intdate 2 | (:require 3 | [clj-time.coerce :refer [to-long from-long]])) 4 | 5 | (defn- joda-time? [x] (= org.joda.time.DateTime (type x))) 6 | 7 | (defn joda-time->intdate 8 | [d] 9 | {:pre [(joda-time? d)]} 10 | (int (/ (to-long d) 1000))) 11 | 12 | 13 | (defn intdate->joda-time 14 | [i] 15 | {:pre [(integer? i) (pos? i)]} 16 | (from-long (* i 1000))) 17 | -------------------------------------------------------------------------------- /test/clj_jwt/intdate_test.clj: -------------------------------------------------------------------------------- 1 | (ns clj-jwt.intdate-test 2 | (:require 3 | [clj-jwt.intdate :refer :all] 4 | [clj-time.core :refer [date-time]] 5 | [midje.sweet :refer :all])) 6 | 7 | (fact "joda-time->intdate should work fine." 8 | (let [d (date-time 2000 1 2 3 4 5)] 9 | (joda-time->intdate d) => 946782245 10 | (joda-time->intdate nil) => (throws AssertionError))) 11 | 12 | (fact "intdate->joda-time should work fine." 13 | (let [d (date-time 2000 1 2 3 4 5) 14 | i (joda-time->intdate d)] 15 | (intdate->joda-time i) => d 16 | (intdate->joda-time nil) => (throws AssertionError))) 17 | 18 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject clj-jwt "0.1.1" 2 | :description "Clojure library for JSON Web Token(JWT)" 3 | :url "https://github.com/liquidz/clj-jwt" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.7.0"] 7 | [org.clojure/data.json "0.2.6"] 8 | [org.clojure/data.codec "0.1.0"] 9 | [org.bouncycastle/bcpkix-jdk15on "1.52"] 10 | [crypto-equality "1.0.0"] 11 | [clj-time "0.11.0"]] 12 | :profiles {:dev {:dependencies [[midje "1.7.0" :exclusions [org.clojure/clojure]]]}} 13 | :plugins [[lein-midje "3.1.3"]]) 14 | -------------------------------------------------------------------------------- /test/files/rsa/dummy.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXQIBAAKBgQDTb6056fGwtczLFM2I2qeCCatBdPgF2Gt0qH0toD8LaMEV52Kx 3 | v8PyUXa1zZul90/nWCvcJgX/tSCYG8+u2eoVVp82bVrIbVfI8DB1qTJIwO3fROBs 4 | ZDa5SwHs4sgQJlXB5QW1OuP0Zow9zUiYuMDcBakZLhkRGWmqYTEoHbelfQIDAQAB 5 | AoGBALP0ceg/wBBZu3MBQqn/B+C6oAK3Lj2zZEnG+buyjtYEE4q0BCErCPgd875q 6 | v9Xy9xP8zF+0ERkBLTupOAsmt35i9pw2TYYzmhLPrnuwKJeexe/qcz6BlI8BdJY4 7 | LVIe59AUnKjH624HaxluIRlqNclcLpiSiJOi6AhUMeSxJ1UBAkEA+HndLgQ7cZ+S 8 | u9ZRwKoyNIy7PFXDSMHDu7XaIbUqD9Pi/W3sEq95RlSwVRsyhzF/8efCZLiLkSI1 9 | C8UYK9H0PQJBANnWsAuS6PiK15xQI1FjNk5p25OV7GsTcB5o+/kJRMAxLpv45OE7 10 | O39FOJEEoeSWf5Yqz/fgqSr8BggcO3n7SkECQDfTCUJBaSmJ9GmHKS7kDguIYriX 11 | fBxojBUsMinIjf6oWCMgAx3flpuag1NbnOqK0HgE3cPLQnAFA231hgyySvECQBd3 12 | fTeB9/7uVhPMvkFCQtNnq/PWLsXKLkXYYWyOhw19Ptwmj+GDlAE9374flaEeZVgz 13 | /HtjhFXRGIU/JVkarQECQQC+ebP9KFaQ5Q312H38LZfIiGHgQh75aYjeH8J1i/Ac 14 | LCuCOXtm+vayM4WXYwyrPdjGhD6RNW5rot2QRNS0xIIz 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /test/files/rsa/no_pass.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQC7GCvT//6UKOcM5cZb45CvK9n3zce71AYTMHMRWh9leARvhGsp 3 | EHkGHB/5d2TBvqulssNAwT82Wpgemeh995arbm4YdcKEg9SbHEKOKt+BQNCHZ53E 4 | 5YGXSGcKLkJxPnFQT8TDSRZfzh2zs+lw4RO99QVRvltKEwQ4l6ik9bx8FQIDAQAB 5 | AoGAGQLFMSUCqlnBcWbyGmyUdeZd0BOxRLm2SjBq4YHzuoPNy/6euLGcDCYMXDQK 6 | wx+zIIaCNZDf22tG9KhMfTJw8KVhFkMQJ9A8FHC53uyPjB8/vnq8169rqEcTRHV+ 7 | GncnCb4NuBcpDkAe/OSfKTEnMEg4+pGpvblZcVtJvQpVcgECQQDdgVb3ZRW2VJ9O 8 | FwwVAjW9/uVOiqxpuLahHjzllWqKYwoWW8otwNQ3dTu2mbPkOXi2rpFPTyBTTJAW 9 | Iv71LPaBAkEA2Dr9RjDvCiokv7Je3LmJ2BaDfXdtGSBmyG8Brt9YTve4H2tsYoLE 10 | NmYqzPM7W1cd16aWg1FCDsD1evlSi+2DlQJABYr/CiHVcUKc2e9ptfzgK2j9hAGk 11 | XuDocQ+4pmYezGe+EOErJgn1RY4BeIhQIB3wD2I+8KUiQfNgh61IhAokAQJBAMy9 12 | 9mpLFVyzkP5uwAISMOKKVtErjwMWuhwZeCeEVdLYHuCpUARrO60iym4r9c1ETP6Q 13 | P75x57GephJeF/pk2I0CQF044ph/kQYk022+4QBIzOQLJ7b1+OREIDRfNpt2FE+0 14 | O0RE2fIS3ca8Ppd5ZeMIFCcnWmkrjuPJZUTf7pgdrvY= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /test/files/rsa/3des.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,74F66907660CAA07 4 | 5 | GGsrflsDbr9CwB0Mk26BOUchw1f2xpEFQrs7P1XDxnWmFBMNo/7SlSj4/J4VAxN+ 6 | qnxRio/jcjLLv1lZzO+X1+6r6szPpkrEIdIYVtItkNoFEbd08cmMMqcBwdO8k2AL 7 | cMDftYqqlKvEDHPfTdGeq37xrE/iu0j50n/9+xqQXPXUUIWY8T/TUQ6M/UjMxYEX 8 | wIq3UTOHE1JGR82Umw3FzELkhmv5ta6lK+MIA+t+vQrD3oEh9rIhdPHR3iTQE7rp 9 | 36kKB+mpANvXcDvPy/dN+Wpv/v0PiNBzEnVlCgZ719brr1iMhNfMIPWutkh5tbiZ 10 | pVDu8f2G1X/AqbmzUp4JWdkR8kj2fZUSrl1z/0VIS4bMFI5WIKu8LqiXSeqSufPM 11 | CuL6TE4QPQCkZxzDCV1VDJKa8AMbZR1n5y1ahdNZwzYfAwL8UYnXsV7g4cN2qyFd 12 | LryGtlp2yXH+a5ch7Il32D3ariKG+OVT3972div9stREK70fmeGhn6n9ghdgiCg8 13 | imHK+dFhSwXtZRNQmDlH/b9f8lrGmwjSUBglgiAvXLLZgqTEjRERQ+XK8iqwtYpY 14 | l/INQ14xhV36I9rRzek4igJymHzRfwUg731fWYFR8yEuQWFt1y1jVdk5tKf25I3w 15 | 1SjHrXaJgvVgz+g4LMerjHjQ6JTRA6Tw3oYaanlMZSwLPyEOK5p2Axh6nwtpQdqN 16 | 6MwhZQX0+zlTROXZDwR8JZSPtBTkNlWIgpeHcjNAXC4c6BdiHNzStIdHy4xWUp+e 17 | mxz9RWsYDVAUqOU1Wimm2wciQ+l1xreFax2CQ+clv1rzceCK9vmtfg== 18 | -----END RSA PRIVATE KEY----- 19 | -------------------------------------------------------------------------------- /test/clj_jwt/attack_test.clj: -------------------------------------------------------------------------------- 1 | (ns clj-jwt.attack-test 2 | (:require 3 | [clj-jwt.core :refer :all] 4 | [clj-jwt.sign :refer :all] 5 | [clj-jwt.key :refer [private-key public-key]] 6 | [clojure.string :as str] 7 | [clojure.test :refer :all])) 8 | 9 | (def pub-key-path "test/files/rsa/no_pass.pub.key") 10 | (def rsa-prv-key (private-key "test/files/rsa/no_pass.key")) 11 | (def rsa-pub-key (public-key pub-key-path)) 12 | 13 | (deftest test-algorithm->none-attack 14 | (let [key "secret" 15 | original (-> {:foo "bar"} jwt (sign :HS256 key)) 16 | attacked (update-in original [:header :alg] (constantly "none"))] 17 | (testing "attack" 18 | (is (verify original key)) 19 | (is (not (verify attacked key)))) 20 | 21 | (testing "defense" 22 | (is (verify original :HS256 key)) 23 | (is (not (verify original :RS256 key))) 24 | (is (not (verify attacked :HS256 key)))))) 25 | 26 | (deftest test-rsa->hmac-attack 27 | (let [base (jwt {:foo "bar"}) 28 | original (sign base :RS256 rsa-prv-key) 29 | hmac-sign (-> base (sign :HS256 (str/trim (slurp pub-key-path))) :signature) 30 | attacked (-> original 31 | (update-in [:header :alg] (constantly "HS256")) 32 | (update-in [:signature] (constantly hmac-sign)))] 33 | (testing "attack" 34 | (is (verify original rsa-pub-key)) 35 | (is (thrown? Exception (verify attacked rsa-pub-key)))) 36 | 37 | (testing "defense" 38 | (is (verify original :RS256 rsa-pub-key)) 39 | (is (not (verify original :HS256 rsa-pub-key))) 40 | (is (not (verify attacked :RS256 rsa-pub-key)))))) 41 | -------------------------------------------------------------------------------- /src/clj_jwt/base64.clj: -------------------------------------------------------------------------------- 1 | (ns clj-jwt.base64 2 | (:require [clojure.data.codec.base64 :as base64] 3 | [clojure.string :as str]) 4 | (:import [java.io ByteArrayInputStream ByteArrayOutputStream])) 5 | 6 | (defprotocol ByteArrayInput (input-stream [this])) 7 | (extend-type String 8 | ByteArrayInput 9 | (input-stream [src] (ByteArrayInputStream. (.getBytes src "UTF-8")))) 10 | (extend-type (Class/forName "[B") 11 | ByteArrayInput 12 | (input-stream [src] (ByteArrayInputStream. src))) 13 | 14 | ;; Encoder 15 | (defn encode 16 | [x] 17 | (with-open [in (input-stream x) 18 | out (ByteArrayOutputStream.)] 19 | (base64/encoding-transfer in out) 20 | (.toByteArray out))) 21 | 22 | (defn encode-str 23 | [x & {:keys [charset] :or {charset "UTF-8"}}] 24 | (String. (encode x) charset)) 25 | 26 | ;; Decoder 27 | (defn decode 28 | [x] 29 | (with-open [in (input-stream x) 30 | out (ByteArrayOutputStream.)] 31 | (base64/decoding-transfer in out) 32 | (.toByteArray out))) 33 | 34 | (defn decode-str 35 | [x & {:keys [charset] :or {charset "UTF-8"}}] 36 | (String. (decode x) charset)) 37 | 38 | ;; URL-Safe Encoder 39 | (defn url-safe-encode-str 40 | [x] 41 | (-> (encode-str x) 42 | (str/replace #"\s" "") 43 | (str/replace "=" "") 44 | (str/replace "+" "-") 45 | (str/replace "/" "_"))) 46 | 47 | ;; URL-Safe Decoder 48 | (defn url-safe-decode 49 | [^String s] 50 | (-> (case (mod (count s) 4) 51 | 2 (str s "==") 52 | 3 (str s "=") 53 | s) 54 | (str/replace "-" "+") 55 | (str/replace "_" "/") 56 | decode)) 57 | 58 | (defn url-safe-decode-str 59 | [^String s & {:keys [charset] :or {charset "UTF-8"}}] 60 | (String. (url-safe-decode s) charset)) 61 | -------------------------------------------------------------------------------- /test/clj_jwt/sign_test.clj: -------------------------------------------------------------------------------- 1 | (ns clj-jwt.sign-test 2 | (:require 3 | [clj-jwt.sign :refer :all] 4 | [clj-jwt.base64 :refer [url-safe-encode-str]] 5 | [clj-jwt.key :refer [private-key]] 6 | [midje.sweet :refer :all] 7 | [clj-jwt.core-test :refer [with-bc-provider-fn]])) 8 | 9 | (facts "HMAC" 10 | (let [[hs256 hs384 hs512] (map get-signature-fn [:HS256 :HS384 :HS512]) 11 | key "foo", body "foo"] 12 | (fact "HS256" 13 | (hs256 key body) => "CLo1fidPUoBldmx3CmOav2gJs5zP03wqMVfH9RlU2go") 14 | 15 | (fact "HS384" 16 | (hs384 key body) => (str "piXjQSLhU8VQMR__GcK-j0-B52Y3YhDbUAqkjRZ5skHGnO8bfaqF9smvE8n-6" 17 | "AOR")) 18 | (fact "HS512" 19 | (hs512 key body) => (str "zpfRr559UfVU-WtKizOGdX7fF46Z0Tburo2T_0CzrEVsGD_JZX0eky96QYdkr" 20 | "TNH67E1N7Rh_6z9XnIJBCPj2g")))) 21 | 22 | (facts "RSA" 23 | (let [[rs256 rs384 rs512] (map get-signature-fn [:RS256 :RS384 :RS512]) 24 | key (private-key "test/files/rsa/no_pass.key") 25 | body "foo"] 26 | (fact "RS256" 27 | (rs256 key body) => (str "VUbrxVb4ud4Iqh8h3rBHijagwFbXyml6FkqgYl9JhauWMZReM4brJh__KlBeF" 28 | "R30ZruV2_VUpFYEuSnsoO1KrscnZklUow_Z8AKWCrCSxWO1I8qyskbWyN3MBq" 29 | "fQxVNEc62xrzMMpdnLq6OpIk--Sh5ZdUYl-tT3wy4HV_sxQUU")) 30 | 31 | (fact "RS384" 32 | (rs384 key body) => (str "F1HhYSk8cFdnr1ODDv-Q6YvTpMq3p8STD3lh6gingp1U5gpYmnbMqgOr_YM5z" 33 | "jeUsFI1d1FolwfaeKeBRxVo9tjawb-TxFAFIdVLfZpwb3kR7nHq9NsQHfkDf_" 34 | "DnfSPOi8d7wX8Eunb-padnM9sn1L4g1GYH9ReuoYhV8JUsJZE")) 35 | 36 | (fact "RS512" 37 | (rs512 key body) => (str "VVfaoXP5WUGNSggUE1FVYV-JKZRGnFkm2ATFm2MQ7bZbyan4EBzVPUN1B5Be3" 38 | "A-Z1j3LeLKFWhryRRAjzW--Ut5rs5t0MjJ4OgUUhXAEXXAeJfbeEVxzBv4C-F" 39 | "e9avjnNjUgcPlJgQAMQbrLirSo8Z8hb1Iqz9f7pUuNLTkAQJA")))) 40 | 41 | (with-state-changes [(around :facts (with-bc-provider-fn (fn [] ?form)))] 42 | (facts "EC" 43 | (let [[es256 es384 es512] (map get-signature-fn [:ES256 :ES384 :ES512]) 44 | key (private-key "test/files/ec/private.key") 45 | body "foo"] 46 | (fact "ES256" 47 | (es256 key body) => string?) 48 | (fact "ES384" 49 | (es384 key body) => string?) 50 | (fact "ES512" 51 | (es512 key body) => string?)))) 52 | -------------------------------------------------------------------------------- /test/clj_jwt/key_test.clj: -------------------------------------------------------------------------------- 1 | (ns clj-jwt.key-test 2 | (:require 3 | [clj-jwt.key :refer :all] 4 | [midje.sweet :refer :all] 5 | [clj-jwt.core-test :refer [with-bc-provider-fn]])) 6 | 7 | (with-state-changes [(around :facts (with-bc-provider-fn (fn [] ?form)))] 8 | (facts "rsa private key" 9 | (fact "non encrypt key" 10 | (type (private-key "test/files/rsa/no_pass.key")) 11 | => org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey) 12 | 13 | (fact "crypted key" 14 | (type (private-key "test/files/rsa/3des.key" "pass phrase")) 15 | => org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey) 16 | 17 | (fact "crypted key wrong pass-phrase" 18 | (private-key "test/files/rsa/3des.key" "wrong pass phrase") 19 | => (throws org.bouncycastle.openssl.EncryptionException))) 20 | 21 | (facts "ecdsa private key" 22 | (fact "ecdsa key" 23 | (type (private-key "test/files/ec/private.key")) 24 | => org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey)) 25 | 26 | (facts "rsa public key" 27 | (fact "rsa non encrypted key" 28 | (type (public-key "test/files/rsa/no_pass.key")) 29 | => org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey) 30 | 31 | (fact "rsa encrypted key" 32 | (type (public-key "test/files/rsa/3des.key" "pass phrase")) 33 | => org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey) 34 | 35 | (fact "rsa encrypted key with wrong pass phrase" 36 | (type (public-key "test/files/rsa/3des.key" "wrong pass phrase")) 37 | => (throws org.bouncycastle.openssl.EncryptionException)) 38 | 39 | (fact "rsa non encrypted key from string" 40 | (-> "test/files/rsa/no_pass.key" slurp public-key-from-string type) 41 | => org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey) 42 | 43 | (fact "rsa encrypted key from string" 44 | (-> "test/files/rsa/3des.key" slurp (public-key-from-string "pass phrase") type) 45 | => org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey) 46 | 47 | (fact "rsa encrypted key with wrong pass phrase from string" 48 | (-> "test/files/rsa/3des.key" slurp (public-key-from-string "wrong pass phrase") type) 49 | => (throws org.bouncycastle.openssl.EncryptionException)) 50 | 51 | (fact "invalid key string" 52 | (public-key-from-string "foobar") => nil)) 53 | 54 | (facts "ecdsa public key" 55 | (fact "ecdsa public key" 56 | (type (public-key "test/files/ec/public.key")) 57 | => org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey) 58 | 59 | (fact "ecdsa public key from string" 60 | (-> "test/files/ec/public.key" slurp public-key-from-string type) 61 | => org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey))) 62 | -------------------------------------------------------------------------------- /src/clj_jwt/key.clj: -------------------------------------------------------------------------------- 1 | (ns clj-jwt.key 2 | (:require 3 | [clojure.java.io :as io]) 4 | (:import 5 | [org.bouncycastle.openssl PEMParser PEMKeyPair PEMEncryptedKeyPair] 6 | [org.bouncycastle.openssl.jcajce JcaPEMKeyConverter JcePEMDecryptorProviderBuilder] 7 | [org.bouncycastle.asn1.pkcs PrivateKeyInfo] 8 | [org.bouncycastle.asn1.x509 SubjectPublicKeyInfo] 9 | [org.bouncycastle.cert X509CertificateHolder] 10 | [java.io StringReader])) 11 | 12 | (defprotocol GetPrivateKey 13 | (-get-private-key [key-info password])) 14 | 15 | (defprotocol GetPublicKey 16 | (-get-public-key [key-info password])) 17 | 18 | (defn ^JcaPEMKeyConverter pem-converter 19 | [] 20 | (JcaPEMKeyConverter.)) 21 | 22 | (extend-protocol GetPrivateKey 23 | PrivateKeyInfo 24 | (-get-private-key 25 | [key-info _] 26 | (.getPrivateKey (pem-converter) key-info))) 27 | 28 | (extend-protocol GetPublicKey 29 | SubjectPublicKeyInfo 30 | (-get-public-key 31 | [key-info _] 32 | (.getPublicKey (pem-converter) key-info)) 33 | 34 | X509CertificateHolder 35 | (-get-public-key 36 | [key-info password] 37 | (-get-public-key (.getSubjectPublicKeyInfo key-info) password))) 38 | 39 | (extend-type PEMKeyPair 40 | GetPrivateKey 41 | (-get-private-key 42 | [key-info _] 43 | (-> (pem-converter) 44 | (.getKeyPair key-info) 45 | .getPrivate)) 46 | 47 | GetPublicKey 48 | (-get-public-key 49 | [key-info _] 50 | (-> (pem-converter) 51 | (.getKeyPair key-info) 52 | .getPublic))) 53 | 54 | (extend-type PEMEncryptedKeyPair 55 | GetPrivateKey 56 | (-get-private-key 57 | [key-info ^String password] 58 | (let [dec-prov (-> (JcePEMDecryptorProviderBuilder.) 59 | (.build (.toCharArray password)))] 60 | (-get-private-key (-> key-info 61 | (.decryptKeyPair dec-prov)) nil))) 62 | GetPublicKey 63 | (-get-public-key 64 | [key-info ^String password] 65 | (let [dec-prov (-> (JcePEMDecryptorProviderBuilder.) 66 | (.build (.toCharArray password)))] 67 | (-get-public-key (-> key-info 68 | (.decryptKeyPair dec-prov)) nil)))) 69 | 70 | (defn pem->public-key 71 | [reader pass-phrase] 72 | (some-> reader 73 | PEMParser. 74 | .readObject 75 | (-get-public-key pass-phrase))) 76 | 77 | (defn pem->private-key 78 | [reader pass-phrase] 79 | (some-> reader 80 | PEMParser. 81 | .readObject 82 | (-get-private-key pass-phrase))) 83 | 84 | (defn private-key 85 | [filename & [pass-phrase]] 86 | (with-open [r (io/reader filename)] 87 | (pem->private-key r pass-phrase))) 88 | 89 | (defn public-key 90 | [filename & [pass-phrase]] 91 | (with-open [r (io/reader filename)] 92 | (pem->public-key r pass-phrase))) 93 | 94 | (defn public-key-from-string 95 | [key-str & [pass-phrase]] 96 | (with-open [r (StringReader. key-str)] 97 | (pem->public-key r pass-phrase))) 98 | -------------------------------------------------------------------------------- /test/clj_jwt/base64_test.clj: -------------------------------------------------------------------------------- 1 | (ns clj-jwt.base64-test 2 | (:require 3 | [clj-jwt.base64 :refer :all] 4 | [midje.sweet :refer :all])) 5 | 6 | (facts "base64/encode" 7 | (fact "string -> byte array encode" 8 | (class (encode "foo")) => (Class/forName "[B") 9 | (String. (encode "foo")) => "Zm9v" 10 | (String. (encode "bar")) => "YmFy" 11 | (String. (encode "foo.bar")) => "Zm9vLmJhcg==") 12 | 13 | (fact "string -> string encode" 14 | (encode-str "foo") => "Zm9v" 15 | (encode-str "bar") => "YmFy" 16 | (encode-str "foo.bar") => "Zm9vLmJhcg==") 17 | 18 | (fact "byte array -> string encode" 19 | (encode-str (.getBytes "foo" "UTF-8")) => "Zm9v" 20 | (encode-str (.getBytes "bar" "UTF-8")) => "YmFy" 21 | (encode-str (.getBytes "foo.bar" "UTF-8")) => "Zm9vLmJhcg==") 22 | 23 | (fact "byte array -> byte array encode" 24 | (class (encode (.getBytes "foo" "UTF-8"))) => (Class/forName "[B") 25 | (String. (encode (.getBytes "foo" "UTF-8"))) => "Zm9v" 26 | (String. (encode (.getBytes "bar" "UTF-8"))) => "YmFy" 27 | (String. (encode (.getBytes "foo.bar" "UTF-8"))) => "Zm9vLmJhcg==")) 28 | 29 | (facts "base64/decode" 30 | (fact "string -> byte array decode" 31 | (class (decode "Zm9v")) => (Class/forName "[B") 32 | (String. (decode "Zm9v")) => "foo" 33 | (String. (decode "YmFy")) => "bar" 34 | (String. (decode "Zm9vLmJhcg==")) => "foo.bar") 35 | 36 | (fact "string -> string decode" 37 | (decode-str "Zm9v") => "foo" 38 | (decode-str "YmFy") => "bar" 39 | (decode-str "Zm9vLmJhcg==") => "foo.bar") 40 | 41 | (fact "byte array -> string decode" 42 | (decode-str (.getBytes "Zm9v" "UTF-8")) => "foo" 43 | (decode-str (.getBytes "YmFy" "UTF-8")) => "bar" 44 | (decode-str (.getBytes "Zm9vLmJhcg==" "UTF-8")) => "foo.bar") 45 | 46 | (fact "byte array -> byte array decode" 47 | (class (decode (.getBytes "Zm9v" "UTF-8"))) => (Class/forName "[B") 48 | (String. (decode (.getBytes "Zm9v" "UTF-8"))) => "foo" 49 | (String. (decode (.getBytes "YmFy" "UTF-8"))) => "bar" 50 | (String. (decode (.getBytes "Zm9vLmJhcg==" "UTF-8"))) => "foo.bar")) 51 | 52 | (facts "base64/url-safe-encode-str" 53 | (fact "string -> string encode" 54 | (url-safe-encode-str "foo") => "Zm9v" 55 | (url-safe-encode-str "bar") => "YmFy" 56 | (url-safe-encode-str "foo.bar") => "Zm9vLmJhcg") 57 | 58 | (fact "byte array -> string encode" 59 | (url-safe-encode-str (.getBytes "foo" "UTF-8")) => "Zm9v" 60 | (url-safe-encode-str (.getBytes "bar" "UTF-8")) => "YmFy" 61 | (url-safe-encode-str (.getBytes "foo.bar" "UTF-8")) => "Zm9vLmJhcg")) 62 | 63 | (facts "base64/url-safe-decode" 64 | (fact "string -> string url-safe decode" 65 | (url-safe-decode-str "Zm9v") => "foo" 66 | (url-safe-decode-str "YmFy") => "bar" 67 | (url-safe-decode-str "Zm9vLmJhcg") => "foo.bar") 68 | 69 | (fact "string -> byte array url-safe decode" 70 | (class (url-safe-decode "Zm9v")) => (Class/forName "[B") 71 | (String. (url-safe-decode "Zm9v")) => "foo" 72 | (String. (url-safe-decode "YmFy")) => "bar" 73 | (String. (url-safe-decode "Zm9vLmJhcg")) => "foo.bar")) 74 | -------------------------------------------------------------------------------- /src/clj_jwt/sign.clj: -------------------------------------------------------------------------------- 1 | (ns clj-jwt.sign 2 | (:require 3 | [clj-jwt.base64 :refer [url-safe-encode-str url-safe-decode]] 4 | [crypto.equality :refer [eq?]])) 5 | 6 | ; HMAC 7 | (defn- hmac-sign 8 | "Function to sign data with HMAC algorithm." 9 | [alg key body & {:keys [charset] :or {charset "UTF-8"}}] 10 | (let [hmac-key (javax.crypto.spec.SecretKeySpec. (.getBytes key charset) alg) 11 | hmac (doto (javax.crypto.Mac/getInstance alg) 12 | (.init hmac-key))] 13 | (url-safe-encode-str (.doFinal hmac (.getBytes body charset))))) 14 | 15 | (defn- hmac-verify 16 | "Function to verify data and signature with HMAC algorithm." 17 | [alg key body signature & {:keys [charset] :or {charset "UTF-8"}}] 18 | (eq? signature (hmac-sign alg key body :charset charset))) 19 | 20 | ; RSA 21 | (defn- rsa-sign 22 | "Function to sign data with RSA algorithm." 23 | [alg key body & {:keys [charset] :or {charset "UTF-8"}}] 24 | (let [sig (doto (java.security.Signature/getInstance alg) 25 | (.initSign key (java.security.SecureRandom.)) 26 | (.update (.getBytes body charset)))] 27 | (url-safe-encode-str (.sign sig)))) 28 | 29 | (defn- rsa-verify 30 | "Function to verify data and signature with RSA algorithm." 31 | [alg key body signature & {:keys [charset] :or {charset "UTF-8"}}] 32 | (let [sig (doto (java.security.Signature/getInstance alg) 33 | (.initVerify key) 34 | (.update (.getBytes body charset)))] 35 | (.verify sig (url-safe-decode signature)))) 36 | 37 | 38 | ; ECDSA 39 | (defn- ec-sign 40 | [alg key body & {:keys [charset] :or {charset "UTF-8"}}] 41 | (let [sig (doto (java.security.Signature/getInstance alg) 42 | (.initSign key) 43 | (.update (.getBytes body charset)))] 44 | (url-safe-encode-str (.sign sig)))) 45 | 46 | (defn ec-verify 47 | [alg key body signature & {:keys [charset] :or {charset "UTF-8"}}] 48 | (let [sig (doto (java.security.Signature/getInstance alg) 49 | (.initSign key) 50 | (.update (.getBytes body charset)))] 51 | (.verify sig (url-safe-decode signature)))) 52 | 53 | (def ^:private signature-fns 54 | {:HS256 (partial hmac-sign "HmacSHA256") 55 | :HS384 (partial hmac-sign "HmacSHA384") 56 | :HS512 (partial hmac-sign "HmacSHA512") 57 | :RS256 (partial rsa-sign "SHA256withRSA") 58 | :RS384 (partial rsa-sign "SHA384withRSA") 59 | :RS512 (partial rsa-sign "SHA512withRSA") 60 | :ES256 (partial ec-sign "SHA256withECDSA") 61 | :ES384 (partial ec-sign "SHA384withECDSA") 62 | :ES512 (partial ec-sign "SHA512withECDSA")}) 63 | 64 | (def ^:private verify-fns 65 | {:HS256 (partial hmac-verify "HmacSHA256") 66 | :HS384 (partial hmac-verify "HmacSHA384") 67 | :HS512 (partial hmac-verify "HmacSHA512") 68 | :RS256 (partial rsa-verify "SHA256withRSA") 69 | :RS384 (partial rsa-verify "SHA384withRSA") 70 | :RS512 (partial rsa-verify "SHA512withRSA") 71 | :ES256 (partial rsa-verify "SHA256withECDSA") 72 | :ES384 (partial rsa-verify "SHA384withECDSA") 73 | :ES512 (partial rsa-verify "SHA512withECDSA")}) 74 | 75 | (defn- get-fns [m alg] 76 | (if-let [f (get m alg)] 77 | f 78 | (throw (Exception. "Unkown signature")))) 79 | 80 | (def get-signature-fn (partial get-fns signature-fns)) 81 | (def get-verify-fn (partial get-fns verify-fns)) 82 | (def supported-algorithm? (set (keys verify-fns))) 83 | -------------------------------------------------------------------------------- /src/clj_jwt/core.clj: -------------------------------------------------------------------------------- 1 | (ns clj-jwt.core 2 | (:require 3 | [clj-jwt.base64 :refer [url-safe-encode-str url-safe-decode-str]] 4 | [clj-jwt.sign :refer [get-signature-fn get-verify-fn supported-algorithm?]] 5 | [clj-jwt.intdate :refer [joda-time->intdate]] 6 | [clj-jwt.json-key-fn :refer [write-key read-key]] 7 | [clojure.data.json :as json] 8 | [clojure.string :as str])) 9 | 10 | (def ^:private DEFAULT_SIGNATURE_ALGORITHM :HS256) 11 | (def ^:private map->encoded-json (comp url-safe-encode-str 12 | #(json/write-str % :key-fn write-key))) 13 | (def ^:private encoded-json->map (comp #(json/read-str % :key-fn read-key) 14 | url-safe-decode-str)) 15 | (defn- update-map [m k f] (if (contains? m k) (update-in m [k] f) m)) 16 | 17 | (defrecord JWT [header claims signature encoded-data]) 18 | 19 | ; ---------------------------------- 20 | ; JsonWebToken 21 | ; ---------------------------------- 22 | (defprotocol JsonWebToken 23 | "Protocol for JsonWebToken" 24 | (init [this claims] "Initialize token") 25 | (encoded-header [this] "Get url-safe base64 encoded header json") 26 | (encoded-claims [this] "Get url-safe base64 encoded claims json") 27 | (to-str [this] "Generate JsonWebToken as string")) 28 | 29 | (extend-protocol JsonWebToken 30 | JWT 31 | (init [this claims] 32 | (let [claims (reduce #(update-map % %2 joda-time->intdate) claims [:exp :nbf :iat])] 33 | (assoc this :header {:alg "none" :typ "JWT"} :claims claims :signature ""))) 34 | 35 | (encoded-header [this] 36 | (-> this :header map->encoded-json)) 37 | 38 | (encoded-claims [this] 39 | (-> this :claims map->encoded-json)) 40 | 41 | (to-str [this] 42 | (str (encoded-header this) "." (encoded-claims this) "." (get this :signature "")))) 43 | 44 | 45 | ; ---------------------------------- 46 | ; JsonWebSignature 47 | ; ---------------------------------- 48 | (defprotocol JsonWebSignature 49 | "Protocol for JonWebSignature" 50 | (set-alg [this alg] "Set algorithm name to JWS Header Parameter") 51 | (sign [this key] [this alg key] "Set signature to this token") 52 | (verify [this] [this key] [this algorithm key] "Verify this token")) 53 | 54 | (extend-protocol JsonWebSignature 55 | JWT 56 | (set-alg [this alg] 57 | (assoc-in this [:header :alg] (name alg))) 58 | 59 | (sign 60 | ([this key] (sign this DEFAULT_SIGNATURE_ALGORITHM key)) 61 | ([this alg key] 62 | (let [this* (set-alg this alg) 63 | sign-fn (get-signature-fn alg) 64 | data (str (encoded-header this*) "." (encoded-claims this*))] 65 | (assoc this* :signature (sign-fn key data) :encoded-data data)))) 66 | 67 | (verify 68 | ([this] (verify this "")) 69 | ([this key] 70 | (let [alg (-> this :header :alg keyword)] 71 | (cond 72 | (= :none alg) (= "" key (:signature this)) 73 | 74 | (supported-algorithm? alg) 75 | (let [verify-fn (get-verify-fn alg)] 76 | (verify-fn key (:encoded-data this) (:signature this))) 77 | 78 | :else (throw (Exception. "Unkown signature"))))) 79 | ([this algorithm key] 80 | (if (= algorithm (-> this :header :alg keyword)) 81 | (verify this key) 82 | false)))) 83 | 84 | ; =jwt 85 | (defn jwt [claim] (init (->JWT "" "" "" "") claim)) 86 | 87 | ; =str->jwt 88 | (defn str->jwt 89 | [jwt-string] 90 | (let [[header claims signature] (str/split jwt-string #"\.")] 91 | (->JWT (encoded-json->map header) 92 | (encoded-json->map claims) 93 | (or signature "") 94 | (str header "." claims)))) 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clj-jwt 2 | 3 | [![Build Status](https://travis-ci.org/liquidz/clj-jwt.png?branch=master)](https://travis-ci.org/liquidz/clj-jwt) 4 | [![Dependency Status](https://www.versioneye.com/user/projects/53462a37e97a46e756000308/badge.png)](https://www.versioneye.com/user/projects/53462a37e97a46e756000308) 5 | 6 | A Clojure library for JSON Web Token(JWT) [draft-ietf-oauth-json-web-token-19](http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-19) 7 | 8 | ## Supporting algorithms 9 | * HS256, HS384, HS512 10 | * RS256, RS384, RS512 11 | * ES256, ES384, ES512 12 | 13 | ## Not supporting 14 | * JSON Web Encryption (JWE) 15 | 16 | ## Usage 17 | 18 | ### Leiningen 19 | [![clj-jwt](https://clojars.org/clj-jwt/latest-version.svg)](https://clojars.org/clj-jwt) 20 | 21 | ### Generate 22 | 23 | ```clojure 24 | (ns foo 25 | (:require 26 | [clj-jwt.core :refer :all] 27 | [clj-jwt.key :refer [private-key]] 28 | [clj-time.core :refer [now plus days]])) 29 | 30 | (def claim 31 | {:iss "foo" 32 | :exp (plus (now) (days 1)) 33 | :iat (now)}) 34 | 35 | (def rsa-prv-key (private-key "rsa/private.key" "pass phrase")) 36 | (def ec-prv-key (private-key "ec/private.key")) 37 | 38 | ;; plain JWT 39 | (-> claim jwt to-str) 40 | 41 | ;; HMAC256 signed JWT 42 | (-> claim jwt (sign :HS256 "secret") to-str) 43 | 44 | ;; RSA256 signed JWT 45 | (-> claim jwt (sign :RS256 rsa-prv-key) to-str) 46 | 47 | ;; ECDSA256 signed JWT 48 | (-> claim jwt (sign :ES256 ec-prv-key) to-str) 49 | ``` 50 | 51 | ### Verify 52 | 53 | ```clojure 54 | (ns foo 55 | (:require 56 | [clj-jwt.core :refer :all] 57 | [clj-jwt.key :refer [private-key public-key]] 58 | [clj-time.core :refer [now plus days]])) 59 | 60 | (def claim 61 | {:iss "foo" 62 | :exp (plus (now) (days 1)) 63 | :iat (now)}) 64 | 65 | (def rsa-prv-key (private-key "rsa/private.key" "pass phrase")) 66 | (def rsa-pub-key (public-key "rsa/public.key")) 67 | (def ec-prv-key (private-key "ec/private.key")) 68 | (def ec-pub-key (public-key "ec/public.key")) 69 | 70 | ;; verify plain JWT 71 | (let [token (-> claim jwt to-str)] 72 | (-> token str->jwt verify)) 73 | 74 | ;; verify HMAC256 signed JWT 75 | (let [token (-> claim jwt (sign :HS256 "secret") to-str)] 76 | (-> token str->jwt (verify "secret"))) 77 | 78 | ;; verify RSA256 signed JWT 79 | (let [token (-> claim jwt (sign :RS256 rsa-prv-key) to-str)] 80 | (-> token str->jwt (verify rsa-pub-key))) 81 | 82 | ;; verify ECDSA256 signed JWT 83 | (let [token (-> claim jwt (sign :ES256 ec-prv-key) to-str)] 84 | (-> token str->jwt (verify ec-pub-key))) 85 | ``` 86 | 87 | You can specify algorithm name (OPTIONAL) for more secure verification. 88 | 89 | ```clj 90 | (ns foo 91 | (:require 92 | [clj-jwt.core :refer :all])) 93 | 94 | ;; verify with specified algorithm 95 | (let [key "secret" 96 | token (-> {:foo "bar"} jwt (sign :HS256 key) to-str)] 97 | (-> token str->jwt (verify :HS256 key)) ;; => true 98 | (-> token str->jwt (verify :none key))) ;; => false 99 | ``` 100 | 101 | ### Decode 102 | 103 | ```clj 104 | (ns foo 105 | (:require 106 | [clj-jwt.core :refer :all])) 107 | 108 | (def claim 109 | {:iss "foo" 110 | :exp (plus (now) (days 1)) 111 | :iat (now)}) 112 | 113 | ;; decode plain JWT 114 | (let [token (-> claim jwt to-str)] 115 | (println (-> token str->jwt :claims))) 116 | 117 | ;; decode signed JWT 118 | (let [token (-> claim jwt (sign :HS256 "secret") to-str)] 119 | (println (-> token str->jwt :claims))) 120 | ``` 121 | 122 | ## License 123 | 124 | Copyright © 2015 [uochan](http://twitter.com/uochan) 125 | 126 | Distributed under the Eclipse Public License, the same as Clojure. 127 | -------------------------------------------------------------------------------- /test/clj_jwt/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns clj-jwt.core-test 2 | (:require 3 | [clj-jwt.core :refer :all] 4 | [clj-jwt.key :refer [private-key public-key]] 5 | [clj-time.core :refer [date-time plus days now]] 6 | [midje.sweet :refer :all]) 7 | (:import 8 | [java.security Security] 9 | [org.bouncycastle.jce.provider BouncyCastleProvider])) 10 | 11 | (defn with-bc-provider-fn 12 | [f] 13 | (try 14 | (Security/insertProviderAt (BouncyCastleProvider.) 1) 15 | (f) 16 | (finally 17 | (java.security.Security/removeProvider "BC")))) 18 | 19 | (def claim {:iss "foo"}) 20 | (def rsa-prv-key (private-key "test/files/rsa/no_pass.key")) 21 | (def rsa-pub-key (public-key "test/files/rsa/no_pass.pub.key")) 22 | (def rsa-enc-prv-key (private-key "test/files/rsa/3des.key" "pass phrase")) 23 | (def rsa-enc-pub-key (public-key "test/files/rsa/3des.pub.key")) 24 | (def rsa-dmy-key (public-key "test/files/rsa/dummy.key")) 25 | 26 | (def ec-prv-key (with-bc-provider-fn #(private-key "test/files/ec/private.key"))) 27 | (def ec-pub-key (with-bc-provider-fn #(public-key "test/files/ec/public.key"))) 28 | (def ec-dmy-key (with-bc-provider-fn #(public-key "test/files/ec/dummy.key"))) 29 | 30 | (facts "JWT tokenize" 31 | (fact "Plain JWT should be generated." 32 | (-> claim jwt to-str) 33 | => "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJmb28ifQ.") 34 | 35 | (fact "If unknown algorithm is specified, exception is throwed." 36 | (-> claim jwt (sign :DUMMY "foo")) => (throws Exception)) 37 | 38 | (fact "HS256 signed JWT should be generated." 39 | (-> claim jwt (sign "foo") to-str) 40 | => (str "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmb28ifQ.8yUIo-xkh537lD_CZycqS1zB" 41 | "NhBNkIrcfzaFgwt8zdg") 42 | 43 | (-> claim jwt (sign :HS256 "foo") to-str) 44 | => (str "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmb28ifQ.8yUIo-xkh537lD_CZycqS1zB" 45 | "NhBNkIrcfzaFgwt8zdg")) 46 | 47 | (fact "HS384 signed JWT should be generated." 48 | (-> claim jwt (sign :HS384 "foo") to-str) 49 | => (str "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmb28ifQ.34ZaTLCZGBAfcCryhYaFYy8Z" 50 | "-47do1cftq365YmvIcubonhGdRnvpgV8s_iG_lvd")) 51 | 52 | (fact "HS512 signed JWT should be generated." 53 | (-> claim jwt (sign :HS512 "foo") to-str) 54 | => (str "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmb28ifQ.58Q4HxaxKAZIffEyDI2eRM_2" 55 | "L7mK7NlNwOq8v96gbfZLMM7r2hxXKuwvMLez2XivUUCEyoaVB1Yz3vGtwAvSZQ")) 56 | 57 | (fact "RS256 signed JWT should be generated." 58 | (-> claim jwt (sign :RS256 rsa-prv-key) to-str) 59 | => (str "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmb28ifQ.ZIjAlGryslu1APkgY1eCmaK7" 60 | "GDINiGX-htlD1-33F4VXK8lUXbdm1n9F1fpHcOFksScniWMvC5f9520jdxyb5c-9CmXz21iDtFdFKWGG" 61 | "zlT_hPjZ0Ta_M8goReBO0L-nDM5hJHxzEqgSZQ7tkcJ18PCdxeMia5NMRV0shGMMUzU") 62 | 63 | (-> claim jwt (sign :RS256 rsa-enc-prv-key) to-str) 64 | => (str "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmb28ifQ.E20DLUOR5VeoTKtH5FjR71rm" 65 | "_rZV2AdXYDQCxqHpMWyZSO6wO4g67phTD727izDxd_NjuNXd2m7Atth7tGABaMhqHLh9EUwba_0nTbw6" 66 | "mc_4mWaK4KBq8LG4WErQnFAVhzGbo1aEK_J7iasuUCfnxN9fZeBBUGH_h5JgPogCPdA")) 67 | 68 | (fact "RS384 signed JWT should be generated." 69 | (-> claim jwt (sign :RS384 rsa-prv-key) to-str) 70 | => (str "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmb28ifQ.sWyMCwJhztfOcSoxRRiCAioB" 71 | "H5F8WFJs5t8DxPV0D7JvB9JwaN8reIQ7kFKJiQWFbhrC7tnlT5UDX9z3fyLjdmNvLTSOII3J9UPpidE1" 72 | "4WvqnXk5DV8k4QxTdWHRufssDFZe7Bsq5yBRAGZos2e8U9hOuqxCib7EjGCe09PdDhg") 73 | 74 | (-> claim jwt (sign :RS384 rsa-enc-prv-key) to-str) 75 | => (str "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmb28ifQ.Uz3ZGXNuuKYsmoslBrNJnVKC" 76 | "GW-dptW-eOWPrTGVN1P54bgjS6QbhwE-PPL2HHGUIYlebVmHb2RKLLvmQ8y63NZ1QSXEk8QBz5-bwy6Y" 77 | "m_QCYh4tfvZYheH97zHcLF3GDLlfrodukO9gGc1xpiXJiZMtIso6sGACHmXNn4LA1bk")) 78 | 79 | (fact "RS512 signed JWT should be generated." 80 | (-> claim jwt (sign :RS512 rsa-prv-key) to-str) 81 | => (str "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmb28ifQ.QKK5oOrVU5e0eG0nt7a_3Hzw" 82 | "v1YJIp1F3iSKVgbdjWyp6rhyS4O4HEql6UxUOVDvf_aTrO4NG81dIo_wzjI1LBNCVtwKhR-8KUFs4Yg3" 83 | "1NLwBMazIzxX_IfkpIkUPuyDGrca7pksJ9dppte33mMK3MDv0RQQqgXiJpbLRGWSNrs") 84 | 85 | (-> claim jwt (sign :RS512 rsa-enc-prv-key) to-str) 86 | => (str "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmb28ifQ.P6ER78xL8AlV4BXrtTtBIcsc" 87 | "JOktKH03Uj12mqjiS6o1h4Cf7QHKXjWxe33hrEgkzcYBHDqw7wH915f6ZnB5mkvDtBkLinA9gK0M2rfB" 88 | "7NqAbxXYMDXti2PhV9PgRzOp97zPCSD98bML0Cy89E8sPcnM7-07wWOK4yhuoTWyV_8")) 89 | 90 | (fact "'exp', 'nbf', 'iat' claims should be converted as IntDate." 91 | (let [d (date-time 2000 1 2 3 4 5) 92 | claim (merge claim {:exp (plus d (days 1)) :nbf d :iat d :dmy d}) 93 | token (jwt claim)] 94 | (-> token :claims :exp) => 946868645 95 | (-> token :claims :nbf) => 946782245 96 | (-> token :claims :iat) => 946782245 97 | (-> token :claims :dmy) => d))) 98 | 99 | (facts "JWT verify" 100 | (fact "Unknown signature algorithm should be thrown exception." 101 | (verify (->JWT {:typ "JWT" :alg "DUMMY"} claim "" "")) => (throws Exception) 102 | (verify (->JWT {:typ "JWT" :alg "DUMMY"} claim "" "") "") => (throws Exception)) 103 | 104 | (fact "Plain JWT should be verified." 105 | (-> claim jwt verify) => true 106 | (-> claim jwt (verify "")) => true 107 | (-> claim jwt (verify :none "")) => true 108 | (-> claim jwt to-str str->jwt verify) => true 109 | (-> claim jwt to-str str->jwt (verify "foo")) => false 110 | (-> claim jwt to-str str->jwt (verify :HS256 "")) => false 111 | (-> claim jwt (assoc :signature "foo") verify) => false) 112 | 113 | (fact "HS256 signed JWT should be verified." 114 | (-> claim jwt (sign "foo") (verify "foo")) => true 115 | (-> claim jwt (sign "foo") (verify :HS256 "foo")) => true 116 | (-> claim jwt (sign "foo") (verify :HS384 "foo")) => false 117 | (-> claim jwt (sign "foo") to-str str->jwt (verify "foo")) => true 118 | (-> claim jwt (sign "foo") (verify "bar")) => false) 119 | 120 | (fact "HS384 signed JWT should be verified." 121 | (-> claim jwt (sign :HS384 "foo") (verify "foo")) => true 122 | (-> claim jwt (sign :HS384 "foo") (verify :HS384 "foo")) => true 123 | (-> claim jwt (sign :HS384 "foo") (verify :HS256 "foo")) => false 124 | (-> claim jwt (sign :HS384 "foo") to-str str->jwt (verify "foo")) => true 125 | (-> claim jwt (sign :HS384 "foo") (verify "bar")) => false) 126 | 127 | (fact "HS512 signed JWT should be verified." 128 | (-> claim jwt (sign :HS512 "foo") (verify "foo")) => true 129 | (-> claim jwt (sign :HS512 "foo") (verify :HS512 "foo")) => true 130 | (-> claim jwt (sign :HS512 "foo") (verify :HS256 "foo")) => false 131 | (-> claim jwt (sign :HS512 "foo") to-str str->jwt (verify "foo")) => true 132 | (-> claim jwt (sign :HS512 "foo") (verify "bar")) => false) 133 | 134 | (fact "RS256 signed JWT should be verified." 135 | (-> claim jwt (sign :RS256 rsa-prv-key) (verify rsa-pub-key)) => true 136 | (-> claim jwt (sign :RS256 rsa-prv-key) (verify :RS256 rsa-pub-key)) => true 137 | (-> claim jwt (sign :RS256 rsa-prv-key) (verify :RS384 rsa-pub-key)) => false 138 | (-> claim jwt (sign :RS256 rsa-prv-key) to-str str->jwt (verify rsa-pub-key)) => true 139 | (-> claim jwt (sign :RS256 rsa-prv-key) (verify rsa-dmy-key)) => false 140 | 141 | (-> claim jwt (sign :RS256 rsa-enc-prv-key) (verify rsa-enc-pub-key)) => true 142 | (-> claim jwt (sign :RS256 rsa-enc-prv-key) (verify :RS256 rsa-enc-pub-key)) => true 143 | (-> claim jwt (sign :RS256 rsa-enc-prv-key) (verify :RS384 rsa-enc-pub-key)) => false 144 | (-> claim jwt (sign :RS256 rsa-enc-prv-key) to-str str->jwt (verify rsa-enc-pub-key)) => true 145 | (-> claim jwt (sign :RS256 rsa-enc-prv-key) (verify rsa-dmy-key)) => false) 146 | 147 | (fact "RS384 signed JWT should be verified." 148 | (-> claim jwt (sign :RS384 rsa-prv-key) (verify rsa-pub-key)) => true 149 | (-> claim jwt (sign :RS384 rsa-prv-key) (verify :RS384 rsa-pub-key)) => true 150 | (-> claim jwt (sign :RS384 rsa-prv-key) (verify :RS256 rsa-pub-key)) => false 151 | (-> claim jwt (sign :RS384 rsa-prv-key) to-str str->jwt (verify rsa-pub-key)) => true 152 | (-> claim jwt (sign :RS384 rsa-prv-key) (verify rsa-dmy-key)) => false 153 | 154 | (-> claim jwt (sign :RS384 rsa-enc-prv-key) (verify rsa-enc-pub-key)) => true 155 | (-> claim jwt (sign :RS384 rsa-enc-prv-key) to-str str->jwt (verify rsa-enc-pub-key)) => true 156 | (-> claim jwt (sign :RS384 rsa-enc-prv-key) (verify rsa-dmy-key)) => false) 157 | 158 | (fact "RS512 signed JWT should be verified." 159 | (-> claim jwt (sign :RS512 rsa-prv-key) (verify rsa-pub-key)) => true 160 | (-> claim jwt (sign :RS512 rsa-prv-key) (verify :RS512 rsa-pub-key)) => true 161 | (-> claim jwt (sign :RS512 rsa-prv-key) (verify :RS256 rsa-pub-key)) => false 162 | (-> claim jwt (sign :RS512 rsa-prv-key) to-str str->jwt (verify rsa-pub-key)) => true 163 | (-> claim jwt (sign :RS512 rsa-prv-key) (verify rsa-dmy-key)) => false 164 | 165 | (-> claim jwt (sign :RS512 rsa-enc-prv-key) (verify rsa-enc-pub-key)) => true 166 | (-> claim jwt (sign :RS512 rsa-enc-prv-key) (verify :RS512 rsa-enc-pub-key)) => true 167 | (-> claim jwt (sign :RS512 rsa-enc-prv-key) (verify :RS256 rsa-enc-pub-key)) => false 168 | (-> claim jwt (sign :RS512 rsa-enc-prv-key) to-str str->jwt (verify rsa-enc-pub-key)) => true 169 | (-> claim jwt (sign :RS512 rsa-enc-prv-key) (verify rsa-dmy-key)) => false) 170 | 171 | (with-state-changes [(around :facts (with-bc-provider-fn (fn [] ?form)))] 172 | (fact "ES256 signed JWT should be verified." 173 | (-> claim jwt (sign :ES256 ec-prv-key) (verify ec-pub-key)) => true 174 | (-> claim jwt (sign :ES256 ec-prv-key) (verify :ES256 ec-pub-key)) => true 175 | (-> claim jwt (sign :ES256 ec-prv-key) (verify :ES384 ec-pub-key)) => false 176 | (-> claim jwt (sign :ES256 ec-prv-key) to-str str->jwt (verify ec-pub-key)) => true) 177 | (fact "ES384 signed JWT should be verified." 178 | (-> claim jwt (sign :ES384 ec-prv-key) (verify ec-pub-key)) => true 179 | (-> claim jwt (sign :ES384 ec-prv-key) (verify :ES384 ec-pub-key)) => true 180 | (-> claim jwt (sign :ES384 ec-prv-key) (verify :ES256 ec-pub-key)) => false 181 | (-> claim jwt (sign :ES384 ec-prv-key) to-str str->jwt (verify ec-pub-key)) => true) 182 | 183 | (fact "ES512 signed JWT should be verified." 184 | (-> claim jwt (sign :ES512 ec-prv-key) (verify ec-pub-key)) => true 185 | (-> claim jwt (sign :ES512 ec-prv-key) (verify :ES512 ec-pub-key)) => true 186 | (-> claim jwt (sign :ES512 ec-prv-key) (verify :ES256 ec-pub-key)) => false 187 | (-> claim jwt (sign :ES512 ec-prv-key) to-str str->jwt (verify ec-pub-key)) => true)) 188 | 189 | (fact "Claims containing string key should be verified" 190 | (let [sclaim {"a/b" "c"} 191 | token (-> sclaim jwt (sign "foo"))] 192 | (verify token "foo") => true 193 | (-> token to-str str->jwt (verify "foo")) => true 194 | (verify token "bar") => false))) 195 | 196 | (facts "str->jwt function should work." 197 | (let [before (jwt claim) 198 | after (-> before to-str str->jwt)] 199 | (fact "plain jwt" 200 | (:header before) => (:header after) 201 | (:claims before) => (:claims after) 202 | (:signature before) => (:signature after))) 203 | 204 | (let [claim {:iss "foo"} 205 | before (-> claim jwt (sign "foo")) 206 | after (-> before to-str str->jwt)] 207 | (fact "signed jwt" 208 | (:header before) => (:header after) 209 | (:claims before) => (:claims after) 210 | (:signature before) => (:signature after))) 211 | 212 | (let [claim {"a/b" "c"} 213 | before (jwt claim) 214 | after (-> before to-str str->jwt)] 215 | (fact "Claim containing string key" 216 | (:header before) => (:header after) 217 | (:claims before) => (:claims after) 218 | (:signature before) => (:signature after)))) 219 | --------------------------------------------------------------------------------