support ECDSA algorithm

This commit is contained in:
liquidz 2013-05-06 01:26:11 +09:00
parent 858be1122b
commit a17f6f3623
12 changed files with 178 additions and 97 deletions

View file

@ -9,7 +9,6 @@ A Clojure library for JSON Web Token(JWT) [draft-jones-json-web-token-10](http:/
* RS256, RS384, RS512
## Not supporting
* ECDSA algorithm
* JSON Web Encryption (JWE)
## Usage
@ -25,7 +24,7 @@ A Clojure library for JSON Web Token(JWT) [draft-jones-json-web-token-10](http:/
(ns foo
(:require
[clj-jwt.core :refer :all]
[clj-jwt.rsa.key :refer [rsa-private-key]]
[clj-jwt.key :refer [private-key]]
[clj-time.core :refer [now plus days]]))
(def claim
@ -33,7 +32,8 @@ A Clojure library for JSON Web Token(JWT) [draft-jones-json-web-token-10](http:/
:exp (plus (now) (days 1))
:nbf (now)})
(def prv-key (rsa-private-key "private.key" "pass phrase"))
(def rsa-prv-key (private-key "rsa/private.key" "pass phrase"))
(def ec-prv-key (private-key "ec/private.key"))
; plain JWT
(-> claim jwt to-str)
@ -42,7 +42,10 @@ A Clojure library for JSON Web Token(JWT) [draft-jones-json-web-token-10](http:/
(-> claim jwt (sign :HS256 "secret") to-str)
; RSA256 signed JWT
(-> claim jwt (sign :RS256 prv-key) to-str)
(-> claim jwt (sign :RS256 rsa-prv-key) to-str)
; ECDSA256 signed JWT
(-> claim jwt (sign :ES256 ec-prv-key) to-str)
```
### Verify
@ -51,7 +54,7 @@ A Clojure library for JSON Web Token(JWT) [draft-jones-json-web-token-10](http:/
(ns foo
(:require
[clj-jwt.core :refer :all]
[clj-jwt.rsa.key :refer [rsa-private-key rsa-public-key]]
[clj-jwt.key :refer [private-key public-key]]
[clj-time.core :refer [now plus days]]))
(def claim
@ -59,8 +62,10 @@ A Clojure library for JSON Web Token(JWT) [draft-jones-json-web-token-10](http:/
:exp (plus (now) (days 1))
:nbf (now)})
(def prv-key (rsa-private-key "private.key" "pass phrase"))
(def pub-key (rsa-public-key "public.key"))
(def rsa-prv-key (private-key "rsa/private.key" "pass phrase"))
(def rsa-pub-key (public-key "rsa/public.key"))
(def ec-prv-key (private-key "ec/private.key"))
(def ec-pub-key (public-key "ec/public.key"))
(let [token (-> claim jwt to-str)]
(-> token str->jwt verify))
@ -68,8 +73,11 @@ A Clojure library for JSON Web Token(JWT) [draft-jones-json-web-token-10](http:/
(let [token (-> claim jwt (sign :HS256 "secret") to-str)]
(-> token str->jwt (verify "secret")))
(let [token (-> claim jwt (sign :RS256 prv-key) to-str)]
(-> token str->jwt (verify pub-key)))
(let [token (-> claim jwt (sign :RS256 rsa-prv-key) to-str)]
(-> token str->jwt (verify rsa-pub-key)))
(let [token (-> claim jwt (sign :ES256 ec-prv-key) to-str)]
(-> token str->jwt (verify ec-pub-key)))
```
## License

View file

@ -1,4 +1,4 @@
(defproject clj-jwt "0.0.1"
(defproject clj-jwt "0.0.2"
:description "Clojure library for JSON Web Token(JWT)"
:url "https://github.com/liquidz/clj-jwt"
:license {:name "Eclipse Public License"
@ -12,4 +12,4 @@
:profiles {:dev {:dependencies [[midje "1.5.1" :exclusions [org.clojure/clojure]]]}}
:plugins [[lein-midje "3.0.0"]]
:main jwt.core)
:main clj-jwt.core)

View file

@ -87,3 +87,4 @@
(->JWT (encoded-json->map header)
(encoded-json->map claims)
(or signature ""))))

View file

@ -1,4 +1,4 @@
(ns clj-jwt.rsa.key
(ns clj-jwt.key
(:require [clojure.java.io :as io])
(:import [org.bouncycastle.openssl PasswordFinder PEMReader]))
@ -9,7 +9,7 @@
(reify PasswordFinder
(getPassword [this] (.toCharArray s))))
(defn- rsa-key
(defn- pem->key
[filename & [pass-phrase]]
(with-open [r (io/reader filename)]
(let [pr (if pass-phrase
@ -17,13 +17,18 @@
(PEMReader. r))]
(.readObject pr))))
(defn rsa-private-key
(defn private-key
[& args]
(.getPrivate (apply rsa-key args)))
(.getPrivate (apply pem->key args)))
(defn rsa-public-key
(defn- public-key? [k]
(let [typ (type k)]
(or (= org.bouncycastle.jce.provider.JCERSAPublicKey typ)
(= org.bouncycastle.jce.provider.JCEECPublicKey typ))))
(defn public-key
[& args]
(let [res (apply rsa-key args)]
(if (= org.bouncycastle.jce.provider.JCERSAPublicKey (type res))
(let [res (apply pem->key args)]
(if (public-key? res)
res
(.getPublic res))))

View file

@ -36,13 +36,32 @@
(.update (.getBytes body charset)))]
(.verify sig (url-safe-decode signature))))
; ECDSA
(defn- ec-sign
[alg key body & {:keys [charset] :or {charset "UTF-8"}}]
(let [sig (doto (java.security.Signature/getInstance alg)
(.initSign key)
(.update (.getBytes body charset)))]
(url-safe-encode-str (.sign sig))))
(defn ec-verify
[alg key body signature & {:keys [charset] :or {charset "UTF-8"}}]
(let [sig (doto (java.security.Signature/getInstance alg)
(.initSign key)
(.update (.getBytes body charset)))]
(.verify sig (url-safe-decode signature))))
(def ^:private signature-fns
{:HS256 (partial hmac-sign "HmacSHA256")
:HS384 (partial hmac-sign "HmacSHA384")
:HS512 (partial hmac-sign "HmacSHA512")
:RS256 (partial rsa-sign "SHA256withRSA")
:RS384 (partial rsa-sign "SHA384withRSA")
:RS512 (partial rsa-sign "SHA512withRSA")})
:RS512 (partial rsa-sign "SHA512withRSA")
:ES256 (partial ec-sign "SHA256withECDSA")
:ES384 (partial ec-sign "SHA384withECDSA")
:ES512 (partial ec-sign "SHA512withECDSA")})
(def ^:private verify-fns
{:HS256 (partial hmac-verify "HmacSHA256")
@ -50,7 +69,10 @@
:HS512 (partial hmac-verify "HmacSHA512")
:RS256 (partial rsa-verify "SHA256withRSA")
:RS384 (partial rsa-verify "SHA384withRSA")
:RS512 (partial rsa-verify "SHA512withRSA")})
:RS512 (partial rsa-verify "SHA512withRSA")
:ES256 (partial rsa-verify "SHA256withECDSA")
:ES384 (partial rsa-verify "SHA384withECDSA")
:ES512 (partial rsa-verify "SHA512withECDSA")})
(defn- get-fns [m alg]
(if-let [f (get m alg)]

View file

@ -1,17 +1,20 @@
(ns clj-jwt.core-test
(:require
[clj-jwt.core :refer :all]
[clj-jwt.rsa.key :refer [rsa-private-key rsa-public-key]]
[clj-jwt.key :refer [private-key public-key]]
[clj-time.core :refer [date-time plus days now]]
[midje.sweet :refer :all]))
(def claim {:iss "foo"})
(def prv-key (rsa-private-key "test/files/rsa/no_pass.key"))
(def pub-key (rsa-public-key "test/files/rsa/no_pass.pub.key"))
(def enc-prv-key (rsa-private-key "test/files/rsa/3des.key" "pass phrase"))
;(def enc-pub-key (rsa-public-key "test/files/rsa/3des.pub.key" "pass phrase"))
(def enc-pub-key (rsa-public-key "test/files/rsa/3des.pub.key"))
(def dmy-key (rsa-public-key "test/files/rsa/dummy.key"))
(def rsa-prv-key (private-key "test/files/rsa/no_pass.key"))
(def rsa-pub-key (public-key "test/files/rsa/no_pass.pub.key"))
(def rsa-enc-prv-key (private-key "test/files/rsa/3des.key" "pass phrase"))
(def rsa-enc-pub-key (public-key "test/files/rsa/3des.pub.key"))
(def rsa-dmy-key (public-key "test/files/rsa/dummy.key"))
(def ec-prv-key (private-key "test/files/ec/private.key"))
(def ec-pub-key (public-key "test/files/ec/public.key"))
(def ec-dmy-key (public-key "test/files/ec/dummy.key"))
(facts "JWT tokenize"
(fact "Plain JWT should be generated."
@ -41,34 +44,34 @@
"FAugLwYeGGhXSjlJ57n4EHoapm6nvheJzIF8OlLYtjwdPcdFbsuaTgPSIa1tCQ"))
(fact "RS256 signed JWT should be generated."
(-> claim jwt (sign :RS256 prv-key) to-str)
(-> claim jwt (sign :RS256 rsa-prv-key) to-str)
=> (str "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJmb28ifQ.CqIxuQMw8-sJR0X7v7DqvJua"
"ND2Oy_LpG_kc-SaAM_sfyuC2TMTnqKJiQLmr-VUbM5-EXiCF853xQIr6xnoNmrHFPgbLeynhPyfvsx1u"
"1RIw25z8r0ZJiNtNbSelueYRAjYlrnYUPxqreervGqkLRdEz5uBn3Vy250ggvHb3S_I")
(-> claim jwt (sign :RS256 enc-prv-key) to-str)
(-> claim jwt (sign :RS256 rsa-enc-prv-key) to-str)
=> (str "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJmb28ifQ.0oDxxzP3AoZGosxJSCKE9J9G"
"r_z1TYog8XbQvVaBPAscXPlGpesLP9Nu4PZDwkPa6qMuVYomcea4P61zIeFUNNPq_KhGtOLTEmw7fWX8"
"sjX-YAWxfz1lJFOkCeYrG3rRXzRJStZa-phsfI_f_XyiSbIM6FQrt_pn8z0KJvI-_Oo"))
(fact "RS384 signed JWT should be generated."
(-> claim jwt (sign :RS384 prv-key) to-str)
(-> claim jwt (sign :RS384 rsa-prv-key) to-str)
=> (str "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9.eyJpc3MiOiJmb28ifQ.RIe33rDJ8qKN49qis9DnvEHt"
"cw2af5bndLWaEChSYFRd5MN5e0c936HkyV_40z2DCOLrKt-6HPz1zVePKYOiM0wKr_hEiPEBUtxo4EOS"
"l_XRHgGC2ol3NM57Z0NzUONW4L9GZoojaDopBxfT5zYxt403dgbsp6BzYlnnODHCbfs")
(-> claim jwt (sign :RS384 enc-prv-key) to-str)
(-> claim jwt (sign :RS384 rsa-enc-prv-key) to-str)
=> (str "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9.eyJpc3MiOiJmb28ifQ.Goh6VyLf3RmVzfh708QKLrVb"
"x4nLa8SOnXb3mV_UqGRqU6KGO712rMNXbRVddkktC1d1xgxv13XKgZX2RVTMb0utI_yii0hCSHijnvSb"
"x7CgQOJLdXPryQ_K1pUcZWPZbQ965O07XxHApGEjMPVZYFF6GhiGe0xBjR4TCNNOipA"))
(fact "RS512 signed JWT should be generated."
(-> claim jwt (sign :RS512 prv-key) to-str)
(-> claim jwt (sign :RS512 rsa-prv-key) to-str)
=> (str "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJpc3MiOiJmb28ifQ.stQhJDu0Hv1ZMTBBYyGNPjap"
"HyDrWRDJSJARhHXmC0T2LkOEtNGBFeQ4mojvxcwd1u2FUu9N5KEmCsmqMpaIubVvdd4GkmGQ4REhR7Cm"
"YFBvB4dFPCpG_B8jn5QkYpXm_zr4wXhW6mdSR4oq_wsULT5O4Z8haoSCl3ysT9SbI0g")
(-> claim jwt (sign :RS512 enc-prv-key) to-str)
(-> claim jwt (sign :RS512 rsa-enc-prv-key) to-str)
=> (str "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJpc3MiOiJmb28ifQ.LsOapoyhzPzsFy1dkP7o-Se9"
"4VKnwuyigZW5i0r0nTymBHa9auctCW9saJIk7Lt9GVLQkRM92exgEX3rqntZPQXm960FPr5-csgCuHnV"
"qACM0zaoAL3x97lOgK_f93aw2g7Tv3AtXfN9LZ8P7YMfmnwGdeqZRREXWJcpc-7Dioo"))
@ -108,31 +111,43 @@
(-> claim jwt (sign :HS512 "foo") (verify "bar")) => false)
(fact "RS256 signed JWT should be verified."
(-> claim jwt (sign :RS256 prv-key) (verify pub-key)) => true
(-> claim jwt (sign :RS256 prv-key) to-str str->jwt (verify pub-key)) => true
(-> claim jwt (sign :RS256 prv-key) (verify dmy-key)) => false
(-> claim jwt (sign :RS256 rsa-prv-key) (verify rsa-pub-key)) => true
(-> claim jwt (sign :RS256 rsa-prv-key) to-str str->jwt (verify rsa-pub-key)) => true
(-> claim jwt (sign :RS256 rsa-prv-key) (verify rsa-dmy-key)) => false
(-> claim jwt (sign :RS256 enc-prv-key) (verify enc-pub-key)) => true
(-> claim jwt (sign :RS256 enc-prv-key) to-str str->jwt (verify enc-pub-key)) => true
(-> claim jwt (sign :RS256 enc-prv-key) (verify dmy-key)) => false)
(-> claim jwt (sign :RS256 rsa-enc-prv-key) (verify rsa-enc-pub-key)) => true
(-> claim jwt (sign :RS256 rsa-enc-prv-key) to-str str->jwt (verify rsa-enc-pub-key)) => true
(-> claim jwt (sign :RS256 rsa-enc-prv-key) (verify rsa-dmy-key)) => false)
(fact "RS384 signed JWT should be verified."
(-> claim jwt (sign :RS384 prv-key) (verify pub-key)) => true
(-> claim jwt (sign :RS384 prv-key) to-str str->jwt (verify pub-key)) => true
(-> claim jwt (sign :RS384 prv-key) (verify dmy-key)) => false
(-> claim jwt (sign :RS384 rsa-prv-key) (verify rsa-pub-key)) => true
(-> claim jwt (sign :RS384 rsa-prv-key) to-str str->jwt (verify rsa-pub-key)) => true
(-> claim jwt (sign :RS384 rsa-prv-key) (verify rsa-dmy-key)) => false
(-> claim jwt (sign :RS384 enc-prv-key) (verify enc-pub-key)) => true
(-> claim jwt (sign :RS384 enc-prv-key) to-str str->jwt (verify enc-pub-key)) => true
(-> claim jwt (sign :RS384 enc-prv-key) (verify dmy-key)) => false)
(-> claim jwt (sign :RS384 rsa-enc-prv-key) (verify rsa-enc-pub-key)) => true
(-> claim jwt (sign :RS384 rsa-enc-prv-key) to-str str->jwt (verify rsa-enc-pub-key)) => true
(-> claim jwt (sign :RS384 rsa-enc-prv-key) (verify rsa-dmy-key)) => false)
(fact "RS512 signed JWT should be verified."
(-> claim jwt (sign :RS512 prv-key) (verify pub-key)) => true
(-> claim jwt (sign :RS512 prv-key) to-str str->jwt (verify pub-key)) => true
(-> claim jwt (sign :RS512 prv-key) (verify dmy-key)) => false
(-> claim jwt (sign :RS512 rsa-prv-key) (verify rsa-pub-key)) => true
(-> claim jwt (sign :RS512 rsa-prv-key) to-str str->jwt (verify rsa-pub-key)) => true
(-> claim jwt (sign :RS512 rsa-prv-key) (verify rsa-dmy-key)) => false
(-> claim jwt (sign :RS512 enc-prv-key) (verify enc-pub-key)) => true
(-> claim jwt (sign :RS512 enc-prv-key) to-str str->jwt (verify enc-pub-key)) => true
(-> claim jwt (sign :RS512 enc-prv-key) (verify dmy-key)) => false))
(-> claim jwt (sign :RS512 rsa-enc-prv-key) (verify rsa-enc-pub-key)) => true
(-> claim jwt (sign :RS512 rsa-enc-prv-key) to-str str->jwt (verify rsa-enc-pub-key)) => true
(-> claim jwt (sign :RS512 rsa-enc-prv-key) (verify rsa-dmy-key)) => false)
(fact "ES256 signed JWT shoud be verified."
(-> claim jwt (sign :ES256 ec-prv-key) (verify ec-pub-key)) => true
(-> claim jwt (sign :ES256 ec-prv-key) to-str str->jwt (verify ec-pub-key)) => true)
(fact "ES384 signed JWT shoud be verified."
(-> claim jwt (sign :ES384 ec-prv-key) (verify ec-pub-key)) => true
(-> claim jwt (sign :ES384 ec-prv-key) to-str str->jwt (verify ec-pub-key)) => true)
(fact "ES512 signed JWT shoud be verified."
(-> claim jwt (sign :ES512 ec-prv-key) (verify ec-pub-key)) => true
(-> claim jwt (sign :ES512 ec-prv-key) to-str str->jwt (verify ec-pub-key)) => true))
(facts "str->jwt function should work."

39
test/clj_jwt/key_test.clj Normal file
View file

@ -0,0 +1,39 @@
(ns clj-jwt.key-test
(:require
[clj-jwt.key :refer :all]
[midje.sweet :refer :all]))
(facts "private key"
(fact "rsa non encrypt key"
(type (private-key "test/files/rsa/no_pass.key"))
=> org.bouncycastle.jce.provider.JCERSAPrivateCrtKey)
(fact "rsa crypted key"
(type (private-key "test/files/rsa/3des.key" "pass phrase"))
=> org.bouncycastle.jce.provider.JCERSAPrivateCrtKey)
(fact "rsa crypted key wrong pass-phrase"
(private-key "test/files/rsa/3des.key" "wrong pass phrase")
=> (throws org.bouncycastle.openssl.EncryptionException))
(fact "ecdsa key"
(type (private-key "test/files/ec/private.key"))
=> org.bouncycastle.jce.provider.JCEECPrivateKey))
(facts "public key"
(fact "rsa non encrypted key"
(type (public-key "test/files/rsa/no_pass.key"))
=> org.bouncycastle.jce.provider.JCERSAPublicKey)
(fact "rsa encrypted key"
(type (public-key "test/files/rsa/3des.key" "pass phrase"))
=> org.bouncycastle.jce.provider.JCERSAPublicKey
)
(fact "rsa encrypted key with wrong pass phrase"
(type (public-key "test/files/rsa/3des.key" "wrong pass phrase"))
=> (throws org.bouncycastle.openssl.EncryptionException))
(fact "ecdsa public key"
(type (public-key "test/files/ec/public.key"))
=> org.bouncycastle.jce.provider.JCEECPublicKey))

View file

@ -1,31 +0,0 @@
(ns clj-jwt.rsa.key-test
(:require
[clj-jwt.rsa.key :refer :all]
[midje.sweet :refer :all]))
(facts "rsa private key"
(fact "non encrypt key"
(type (rsa-private-key "test/files/rsa/no_pass.key"))
=> org.bouncycastle.jce.provider.JCERSAPrivateCrtKey)
(fact "crypted key"
(type (rsa-private-key "test/files/rsa/3des.key" "pass phrase"))
=> org.bouncycastle.jce.provider.JCERSAPrivateCrtKey)
(fact "crypted key wrong pass-phrase"
(rsa-private-key "test/files/rsa/3des.key" "wrong pass phrase")
=> (throws org.bouncycastle.openssl.EncryptionException)))
(facts "rsa public key"
(fact "non encrypted key"
(type (rsa-public-key "test/files/rsa/no_pass.key"))
=> org.bouncycastle.jce.provider.JCERSAPublicKey)
(fact "encrypted key"
(type (rsa-public-key "test/files/rsa/3des.key" "pass phrase"))
=> org.bouncycastle.jce.provider.JCERSAPublicKey
)
(fact "encrypted key with wrong pass phrase"
(type (rsa-public-key "test/files/rsa/3des.key" "wrong pass phrase"))
=> (throws org.bouncycastle.openssl.EncryptionException)))

View file

@ -2,7 +2,7 @@
(:require
[clj-jwt.sign :refer :all]
[clj-jwt.base64 :refer [url-safe-encode-str]]
[clj-jwt.rsa.key :refer [rsa-private-key]]
[clj-jwt.key :refer [private-key]]
[midje.sweet :refer :all]))
(facts "HMAC"
@ -20,7 +20,7 @@
(facts "RSA"
(let [[rs256 rs384 rs512] (map get-signature-fn [:RS256 :RS384 :RS512])
key (rsa-private-key "test/files/rsa/no_pass.key")
key (private-key "test/files/rsa/no_pass.key")
body "foo"]
(fact "RS256"
(rs256 key body) => (str "VUbrxVb4ud4Iqh8h3rBHijagwFbXyml6FkqgYl9JhauWMZReM4brJh__KlBeF"
@ -37,7 +37,16 @@
"A-Z1j3LeLKFWhryRRAjzW--Ut5rs5t0MjJ4OgUUhXAEXXAeJfbeEVxzBv4C-F"
"e9avjnNjUgcPlJgQAMQbrLirSo8Z8hb1Iqz9f7pUuNLTkAQJA"))))
(facts "EC"
(let [[es256 es384 es512] (map get-signature-fn [:ES256 :ES384 :ES512])
key (private-key "test/files/ec/private.key")
body "foo"]
(fact "ES256"
(es256 key body) => string?)
(fact "ES384"
(es384 key body) => string?)
(fact "ES512"
(es512 key body) => string?)))

4
test/files/ec/dummy.key Normal file
View file

@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEnYqYze74sz8aLB7FM2/Zy46UKew9oc51
CzFOprxNk+NMI1vhRmw1kW2B/q7asQFC9/JdHiKcH+sYMlVb7RrzVg==
-----END PUBLIC KEY-----

View file

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIAKZoQ/oANuMJQPLur8HMr/cC+f8FFJkx83EEFX6Jf7/oAcGBSuBBAAK
oUQDQgAEMJpmzEF1SskoToLDRxIfT9oZ4EjSTw4ZKx8q1iDAmNgY2+NEP0OGleP3
tcAXLsE1GFZAGd91HuMNMHqeHN/mNw==
-----END EC PRIVATE KEY-----

4
test/files/ec/public.key Normal file
View file

@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEMJpmzEF1SskoToLDRxIfT9oZ4EjSTw4Z
Kx8q1iDAmNgY2+NEP0OGleP3tcAXLsE1GFZAGd91HuMNMHqeHN/mNw==
-----END PUBLIC KEY-----