From e13b665c1b19b8c83cc2effe64183128a323f46f Mon Sep 17 00:00:00 2001 From: Ryan Senior Date: Tue, 29 Jul 2014 08:03:58 -0500 Subject: [PATCH 1/2] Upgraded to Bouncy Castle 1.50 and removed direct calls to addProvider --- project.clj | 3 +- src/clj_jwt/key.clj | 92 +++++++++++++++++++++++++++----------- src/clj_jwt/sign.clj | 7 +-- test/clj_jwt/core_test.clj | 28 ++++++++---- test/clj_jwt/key_test.clj | 90 ++++++++++++++++++------------------- test/clj_jwt/sign_test.clj | 27 ++++++----- 6 files changed, 146 insertions(+), 101 deletions(-) diff --git a/project.clj b/project.clj index 09ace5d..b580f98 100644 --- a/project.clj +++ b/project.clj @@ -6,9 +6,8 @@ :dependencies [[org.clojure/clojure "1.6.0"] [org.clojure/data.json "0.2.6"] [org.clojure/data.codec "0.1.0"] - [org.bouncycastle/bcprov-jdk15 "1.46"] + [org.bouncycastle/bcpkix-jdk15on "1.50"] [crypto-equality "1.0.0"] [clj-time "0.9.0"]] - :profiles {:dev {:dependencies [[midje "1.6.3" :exclusions [org.clojure/clojure]]]}} :plugins [[lein-midje "3.1.3"]]) diff --git a/src/clj_jwt/key.clj b/src/clj_jwt/key.clj index ce49b0b..6528d3b 100644 --- a/src/clj_jwt/key.clj +++ b/src/clj_jwt/key.clj @@ -1,44 +1,84 @@ (ns clj-jwt.key (:require [clojure.java.io :as io]) - (:import [org.bouncycastle.openssl PasswordFinder PEMReader] + (:import [org.bouncycastle.openssl PEMParser PEMKeyPair PEMEncryptedKeyPair] + [org.bouncycastle.openssl.jcajce JcaPEMKeyConverter JcePEMDecryptorProviderBuilder] + [org.bouncycastle.asn1.pkcs PrivateKeyInfo] + [org.bouncycastle.asn1.x509 SubjectPublicKeyInfo] + [org.bouncycastle.cert X509CertificateHolder] [java.io StringReader])) -(java.security.Security/addProvider -(org.bouncycastle.jce.provider.BouncyCastleProvider.)) +(defprotocol GetPrivateKey + (-get-private-key [key-info password])) -(defn- password-finder [s] - (reify PasswordFinder - (getPassword [this] (.toCharArray s)))) +(defprotocol GetPublicKey + (-get-public-key [key-info password])) -(defn- pem->key - [reader pass-phrase] - (if pass-phrase - (.readObject (PEMReader. reader (password-finder pass-phrase))) - (.readObject (PEMReader. reader)))) +(defn ^JcaPEMKeyConverter pem-converter [] + (JcaPEMKeyConverter.)) + +(extend-protocol GetPrivateKey + PrivateKeyInfo + (-get-private-key [key-info _] + (.getPrivateKey (pem-converter) key-info))) + +(extend-protocol GetPublicKey + SubjectPublicKeyInfo + (-get-public-key [key-info _] + (.getPublicKey (pem-converter) key-info)) + X509CertificateHolder + (-get-public-key [key-info password] + (-get-public-key (.getSubjectPublicKeyInfo key-info) password))) + +(extend-type PEMKeyPair + GetPrivateKey + (-get-private-key [key-info _] + (-> (pem-converter) + (.getKeyPair key-info) + .getPrivate)) + + GetPublicKey + (-get-public-key [key-info _] + (-> (pem-converter) + (.getKeyPair key-info) + .getPublic))) + +(extend-type PEMEncryptedKeyPair + GetPrivateKey + (-get-private-key [key-info ^String password] + (let [dec-prov (-> (JcePEMDecryptorProviderBuilder.) + (.build (.toCharArray password)))] + (-get-private-key (-> key-info + (.decryptKeyPair dec-prov)) nil))) + GetPublicKey + (-get-public-key [key-info ^String password] + (let [dec-prov (-> (JcePEMDecryptorProviderBuilder.) + (.build (.toCharArray password)))] + (-get-public-key (-> key-info + (.decryptKeyPair dec-prov)) nil)))) + +(defn pem->public-key [reader pass-phrase] + (some-> reader + PEMParser. + .readObject + (-get-public-key pass-phrase))) + +(defn pem->private-key [reader pass-phrase] + (some-> reader + PEMParser. + .readObject + (-get-private-key pass-phrase))) (defn private-key [filename & [pass-phrase]] (with-open [r (io/reader filename)] - (.getPrivate - (pem->key r pass-phrase)))) - -(defn- public-key? [k] - (let [typ (type k)] - (or (= org.bouncycastle.jce.provider.JCERSAPublicKey typ) - (= org.bouncycastle.jce.provider.JCEECPublicKey typ)))) + (pem->private-key r pass-phrase))) (defn public-key [filename & [pass-phrase]] (with-open [r (io/reader filename)] - (let [res (pem->key r pass-phrase)] - (if (public-key? res) - res - (.getPublic res))))) + (pem->public-key r pass-phrase))) (defn public-key-from-string [key-str & [pass-phrase]] (with-open [r (StringReader. key-str)] - (when-let [res (pem->key r pass-phrase)] - (if (public-key? res) - res - (.getPublic res))))) + (pem->public-key r pass-phrase))) diff --git a/src/clj_jwt/sign.clj b/src/clj_jwt/sign.clj index 044a63b..0c2c020 100644 --- a/src/clj_jwt/sign.clj +++ b/src/clj_jwt/sign.clj @@ -3,9 +3,6 @@ [clj-jwt.base64 :refer [url-safe-encode-str url-safe-decode]] [crypto.equality :refer [eq?]])) -(java.security.Security/addProvider - (org.bouncycastle.jce.provider.BouncyCastleProvider.)) - ; HMAC (defn- hmac-sign "Function to sign data with HMAC algorithm." @@ -24,7 +21,7 @@ (defn- rsa-sign "Function to sign data with RSA algorithm." [alg key body & {:keys [charset] :or {charset "UTF-8"}}] - (let [sig (doto (java.security.Signature/getInstance alg "BC") + (let [sig (doto (java.security.Signature/getInstance alg) (.initSign key (java.security.SecureRandom.)) (.update (.getBytes body charset)))] (url-safe-encode-str (.sign sig)))) @@ -32,7 +29,7 @@ (defn- rsa-verify "Function to verify data and signature with RSA algorithm." [alg key body signature & {:keys [charset] :or {charset "UTF-8"}}] - (let [sig (doto (java.security.Signature/getInstance alg "BC") + (let [sig (doto (java.security.Signature/getInstance alg) (.initVerify key) (.update (.getBytes body charset)))] (.verify sig (url-safe-decode signature)))) diff --git a/test/clj_jwt/core_test.clj b/test/clj_jwt/core_test.clj index e689632..e762629 100644 --- a/test/clj_jwt/core_test.clj +++ b/test/clj_jwt/core_test.clj @@ -3,7 +3,17 @@ [clj-jwt.core :refer :all] [clj-jwt.key :refer [private-key public-key]] [clj-time.core :refer [date-time plus days now]] - [midje.sweet :refer :all])) + [midje.sweet :refer :all]) + (:import + [java.security Security] + [org.bouncycastle.jce.provider BouncyCastleProvider])) + +(defn with-bc-provider-fn [f] + (try + (Security/insertProviderAt (BouncyCastleProvider.) 1) + (f) + (finally + (java.security.Security/removeProvider "BC")))) (def claim {:iss "foo"}) (def rsa-prv-key (private-key "test/files/rsa/no_pass.key")) @@ -12,9 +22,9 @@ (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")) +(def ec-prv-key (with-bc-provider-fn #(private-key "test/files/ec/private.key"))) +(def ec-pub-key (with-bc-provider-fn #(public-key "test/files/ec/public.key"))) +(def ec-dmy-key (with-bc-provider-fn #(public-key "test/files/ec/dummy.key"))) (facts "JWT tokenize" (fact "Plain JWT should be generated." @@ -157,23 +167,23 @@ (-> 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." +(with-state-changes [(around :facts (with-bc-provider-fn (fn [] ?form)))] + (fact "ES256 signed JWT should be verified." (-> claim jwt (sign :ES256 ec-prv-key) (verify ec-pub-key)) => true (-> claim jwt (sign :ES256 ec-prv-key) (verify :ES256 ec-pub-key)) => true (-> claim jwt (sign :ES256 ec-prv-key) (verify :ES384 ec-pub-key)) => false (-> claim jwt (sign :ES256 ec-prv-key) to-str str->jwt (verify ec-pub-key)) => true) - - (fact "ES384 signed JWT shoud be verified." + (fact "ES384 signed JWT should be verified." (-> claim jwt (sign :ES384 ec-prv-key) (verify ec-pub-key)) => true (-> claim jwt (sign :ES384 ec-prv-key) (verify :ES384 ec-pub-key)) => true (-> claim jwt (sign :ES384 ec-prv-key) (verify :ES256 ec-pub-key)) => false (-> claim jwt (sign :ES384 ec-prv-key) to-str str->jwt (verify ec-pub-key)) => true) - (fact "ES512 signed JWT shoud be verified." + (fact "ES512 signed JWT should be verified." (-> claim jwt (sign :ES512 ec-prv-key) (verify ec-pub-key)) => true (-> claim jwt (sign :ES512 ec-prv-key) (verify :ES512 ec-pub-key)) => true (-> claim jwt (sign :ES512 ec-prv-key) (verify :ES256 ec-pub-key)) => false - (-> claim jwt (sign :ES512 ec-prv-key) to-str str->jwt (verify ec-pub-key)) => true) + (-> claim jwt (sign :ES512 ec-prv-key) to-str str->jwt (verify ec-pub-key)) => true)) (fact "Claims containing string key should be verified" (let [sclaim {"a/b" "c"} diff --git a/test/clj_jwt/key_test.clj b/test/clj_jwt/key_test.clj index 3eaa1f9..f1d66d8 100644 --- a/test/clj_jwt/key_test.clj +++ b/test/clj_jwt/key_test.clj @@ -1,61 +1,61 @@ (ns clj-jwt.key-test (:require [clj-jwt.key :refer :all] - [midje.sweet :refer :all])) + [midje.sweet :refer :all] + [clj-jwt.core-test :refer [with-bc-provider-fn]])) -(facts "rsa private key" - (fact "non encrypt key" - (type (private-key "test/files/rsa/no_pass.key")) - => org.bouncycastle.jce.provider.JCERSAPrivateCrtKey) +(with-state-changes [(around :facts (with-bc-provider-fn (fn [] ?form)))] + (facts "rsa private key" + (fact "non encrypt key" + (type (private-key "test/files/rsa/no_pass.key")) + => org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey) - (fact "crypted key" - (type (private-key "test/files/rsa/3des.key" "pass phrase")) - => org.bouncycastle.jce.provider.JCERSAPrivateCrtKey) + (fact "crypted key" + (type (private-key "test/files/rsa/3des.key" "pass phrase")) + => org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey) - (fact "crypted key wrong pass-phrase" - (private-key "test/files/rsa/3des.key" "wrong pass phrase") - => (throws org.bouncycastle.openssl.EncryptionException))) + (fact "crypted key wrong pass-phrase" + (private-key "test/files/rsa/3des.key" "wrong pass phrase") + => (throws org.bouncycastle.openssl.EncryptionException))) -(facts "ecdsa private key" - (fact "ecdsa key" - (type (private-key "test/files/ec/private.key")) - => org.bouncycastle.jce.provider.JCEECPrivateKey)) + (facts "ecdsa private key" + (fact "ecdsa key" + (type (private-key "test/files/ec/private.key")) + => org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey)) -(facts "rsa public key" - (fact "rsa non encrypted key" - (type (public-key "test/files/rsa/no_pass.key")) - => org.bouncycastle.jce.provider.JCERSAPublicKey) + (facts "rsa public key" + (fact "rsa non encrypted key" + (type (public-key "test/files/rsa/no_pass.key")) + => org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey) - (fact "rsa encrypted key" - (type (public-key "test/files/rsa/3des.key" "pass phrase")) - => org.bouncycastle.jce.provider.JCERSAPublicKey) + (fact "rsa encrypted key" + (type (public-key "test/files/rsa/3des.key" "pass phrase")) + => org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey) - (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 "rsa encrypted key with wrong pass phrase" + (type (public-key "test/files/rsa/3des.key" "wrong pass phrase")) + => (throws org.bouncycastle.openssl.EncryptionException)) - (fact "rsa non encrypted key from string" - (-> "test/files/rsa/no_pass.key" slurp public-key-from-string type) - => org.bouncycastle.jce.provider.JCERSAPublicKey) + (fact "rsa non encrypted key from string" + (-> "test/files/rsa/no_pass.key" slurp public-key-from-string type) + => org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey) - (fact "rsa encrypted key from string" - (-> "test/files/rsa/3des.key" slurp (public-key-from-string "pass phrase") type) - => org.bouncycastle.jce.provider.JCERSAPublicKey) + (fact "rsa encrypted key from string" + (-> "test/files/rsa/3des.key" slurp (public-key-from-string "pass phrase") type) + => org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey) - (fact "rsa encrypted key with wrong pass phrase from string" - (-> "test/files/rsa/3des.key" slurp (public-key-from-string "wrong pass phrase") type) - => (throws org.bouncycastle.openssl.EncryptionException)) + (fact "rsa encrypted key with wrong pass phrase from string" + (-> "test/files/rsa/3des.key" slurp (public-key-from-string "wrong pass phrase") type) + => (throws org.bouncycastle.openssl.EncryptionException)) - (fact "invalid key string" - (public-key-from-string "foobar") => nil)) + (fact "invalid key string" + (public-key-from-string "foobar") => nil)) -(facts "ecdsa public key" - (fact "ecdsa public key" - (type (public-key "test/files/ec/public.key")) - => org.bouncycastle.jce.provider.JCEECPublicKey) - - (fact "ecdsa public key from string" - (-> "test/files/ec/public.key" slurp public-key-from-string type) - => org.bouncycastle.jce.provider.JCEECPublicKey) - ) + (facts "ecdsa public key" + (fact "ecdsa public key" + (type (public-key "test/files/ec/public.key")) + => org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey) + (fact "ecdsa public key from string" + (-> "test/files/ec/public.key" slurp public-key-from-string type) + => org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey))) diff --git a/test/clj_jwt/sign_test.clj b/test/clj_jwt/sign_test.clj index 19ec25d..a2642d4 100644 --- a/test/clj_jwt/sign_test.clj +++ b/test/clj_jwt/sign_test.clj @@ -3,7 +3,8 @@ [clj-jwt.sign :refer :all] [clj-jwt.base64 :refer [url-safe-encode-str]] [clj-jwt.key :refer [private-key]] - [midje.sweet :refer :all])) + [midje.sweet :refer :all] + [clj-jwt.core-test :refer [with-bc-provider-fn]])) (facts "HMAC" (let [[hs256 hs384 hs512] (map get-signature-fn [:HS256 :HS384 :HS512]) @@ -37,16 +38,14 @@ "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?))) - - - +(with-state-changes [(around :facts (with-bc-provider-fn (fn [] ?form)))] + (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?)))) From 7e9895a09295bc6c0cee54833ca5778d220958d0 Mon Sep 17 00:00:00 2001 From: liquidz Date: Tue, 30 Jun 2015 20:35:25 +0900 Subject: [PATCH 2/2] #18 use bcpkix-jdk15on 1.52 instead of 1.50 --- project.clj | 4 ++-- src/clj_jwt/key.clj | 47 ++++++++++++++++++++++++-------------- test/clj_jwt/core_test.clj | 3 ++- test/clj_jwt/key_test.clj | 4 ++-- test/clj_jwt/sign_test.clj | 8 +++---- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/project.clj b/project.clj index b580f98..b191dd7 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject clj-jwt "0.0.13" +(defproject clj-jwt "0.1.0" :description "Clojure library for JSON Web Token(JWT)" :url "https://github.com/liquidz/clj-jwt" :license {:name "Eclipse Public License" @@ -6,7 +6,7 @@ :dependencies [[org.clojure/clojure "1.6.0"] [org.clojure/data.json "0.2.6"] [org.clojure/data.codec "0.1.0"] - [org.bouncycastle/bcpkix-jdk15on "1.50"] + [org.bouncycastle/bcpkix-jdk15on "1.52"] [crypto-equality "1.0.0"] [clj-time "0.9.0"]] :profiles {:dev {:dependencies [[midje "1.6.3" :exclusions [org.clojure/clojure]]]}} diff --git a/src/clj_jwt/key.clj b/src/clj_jwt/key.clj index 6528d3b..08e59c8 100644 --- a/src/clj_jwt/key.clj +++ b/src/clj_jwt/key.clj @@ -1,11 +1,13 @@ (ns clj-jwt.key - (:require [clojure.java.io :as io]) - (:import [org.bouncycastle.openssl PEMParser PEMKeyPair PEMEncryptedKeyPair] - [org.bouncycastle.openssl.jcajce JcaPEMKeyConverter JcePEMDecryptorProviderBuilder] - [org.bouncycastle.asn1.pkcs PrivateKeyInfo] - [org.bouncycastle.asn1.x509 SubjectPublicKeyInfo] - [org.bouncycastle.cert X509CertificateHolder] - [java.io StringReader])) + (:require + [clojure.java.io :as io]) + (:import + [org.bouncycastle.openssl PEMParser PEMKeyPair PEMEncryptedKeyPair] + [org.bouncycastle.openssl.jcajce JcaPEMKeyConverter JcePEMDecryptorProviderBuilder] + [org.bouncycastle.asn1.pkcs PrivateKeyInfo] + [org.bouncycastle.asn1.x509 SubjectPublicKeyInfo] + [org.bouncycastle.cert X509CertificateHolder] + [java.io StringReader])) (defprotocol GetPrivateKey (-get-private-key [key-info password])) @@ -13,56 +15,67 @@ (defprotocol GetPublicKey (-get-public-key [key-info password])) -(defn ^JcaPEMKeyConverter pem-converter [] +(defn ^JcaPEMKeyConverter pem-converter + [] (JcaPEMKeyConverter.)) (extend-protocol GetPrivateKey PrivateKeyInfo - (-get-private-key [key-info _] + (-get-private-key + [key-info _] (.getPrivateKey (pem-converter) key-info))) (extend-protocol GetPublicKey SubjectPublicKeyInfo - (-get-public-key [key-info _] + (-get-public-key + [key-info _] (.getPublicKey (pem-converter) key-info)) + X509CertificateHolder - (-get-public-key [key-info password] + (-get-public-key + [key-info password] (-get-public-key (.getSubjectPublicKeyInfo key-info) password))) (extend-type PEMKeyPair GetPrivateKey - (-get-private-key [key-info _] + (-get-private-key + [key-info _] (-> (pem-converter) (.getKeyPair key-info) .getPrivate)) GetPublicKey - (-get-public-key [key-info _] + (-get-public-key + [key-info _] (-> (pem-converter) (.getKeyPair key-info) .getPublic))) (extend-type PEMEncryptedKeyPair GetPrivateKey - (-get-private-key [key-info ^String password] + (-get-private-key + [key-info ^String password] (let [dec-prov (-> (JcePEMDecryptorProviderBuilder.) (.build (.toCharArray password)))] (-get-private-key (-> key-info (.decryptKeyPair dec-prov)) nil))) GetPublicKey - (-get-public-key [key-info ^String password] + (-get-public-key + [key-info ^String password] (let [dec-prov (-> (JcePEMDecryptorProviderBuilder.) (.build (.toCharArray password)))] (-get-public-key (-> key-info (.decryptKeyPair dec-prov)) nil)))) -(defn pem->public-key [reader pass-phrase] +(defn pem->public-key + [reader pass-phrase] (some-> reader PEMParser. .readObject (-get-public-key pass-phrase))) -(defn pem->private-key [reader pass-phrase] +(defn pem->private-key + [reader pass-phrase] (some-> reader PEMParser. .readObject diff --git a/test/clj_jwt/core_test.clj b/test/clj_jwt/core_test.clj index e762629..dd5c9bf 100644 --- a/test/clj_jwt/core_test.clj +++ b/test/clj_jwt/core_test.clj @@ -8,7 +8,8 @@ [java.security Security] [org.bouncycastle.jce.provider BouncyCastleProvider])) -(defn with-bc-provider-fn [f] +(defn with-bc-provider-fn + [f] (try (Security/insertProviderAt (BouncyCastleProvider.) 1) (f) diff --git a/test/clj_jwt/key_test.clj b/test/clj_jwt/key_test.clj index f1d66d8..a8e85ed 100644 --- a/test/clj_jwt/key_test.clj +++ b/test/clj_jwt/key_test.clj @@ -1,7 +1,7 @@ (ns clj-jwt.key-test (:require - [clj-jwt.key :refer :all] - [midje.sweet :refer :all] + [clj-jwt.key :refer :all] + [midje.sweet :refer :all] [clj-jwt.core-test :refer [with-bc-provider-fn]])) (with-state-changes [(around :facts (with-bc-provider-fn (fn [] ?form)))] diff --git a/test/clj_jwt/sign_test.clj b/test/clj_jwt/sign_test.clj index a2642d4..5c02b57 100644 --- a/test/clj_jwt/sign_test.clj +++ b/test/clj_jwt/sign_test.clj @@ -1,9 +1,9 @@ (ns clj-jwt.sign-test (:require - [clj-jwt.sign :refer :all] - [clj-jwt.base64 :refer [url-safe-encode-str]] - [clj-jwt.key :refer [private-key]] - [midje.sweet :refer :all] + [clj-jwt.sign :refer :all] + [clj-jwt.base64 :refer [url-safe-encode-str]] + [clj-jwt.key :refer [private-key]] + [midje.sweet :refer :all] [clj-jwt.core-test :refer [with-bc-provider-fn]])) (facts "HMAC"