From 169bd1f29b239d314475bdbe05f0a7a9aa332864 Mon Sep 17 00:00:00 2001 From: Matthias Andreas Benkard Date: Sun, 17 Jun 2012 15:08:25 +0200 Subject: Implement client certificate generation and signing. --- project.clj | 2 + src/mulk/benki/genkey.clj | 132 ++++++++++++++++++++++++++++++++++++++++++++++ src/mulk/benki/main.clj | 7 ++- src/mulk/benki/util.clj | 17 ++++++ 4 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 src/mulk/benki/genkey.clj diff --git a/project.clj b/project.clj index 4a49f2a..4288ab1 100644 --- a/project.clj +++ b/project.clj @@ -48,6 +48,8 @@ [jivesoftware/smack "3.1.0"] [jivesoftware/smackx "3.1.0"] [joda-time/joda-time "2.1"] + [org.bouncycastle/bcprov-jdk15on "1.47"] + [org.bouncycastle/bcpkix-jdk15on "1.47"] ] :plugins [[lein-swank "1.4.3"]] :exclusions [org.clojure/clojure-contrib] ;you know, the old pre-1.3.0 versions diff --git a/src/mulk/benki/genkey.clj b/src/mulk/benki/genkey.clj new file mode 100644 index 0000000..0378181 --- /dev/null +++ b/src/mulk/benki/genkey.clj @@ -0,0 +1,132 @@ +(ns mulk.benki.genkey + (:refer-clojure) + (:use [clojure core repl pprint] + [hiccup core page-helpers] + [mulk.benki config util db webutil] + [clojure.core.match + :only [match]] + [noir core] + [clojure.java.jdbc :only [transaction do-commands]]) + (:require [noir.session :as session] + [noir.response :as response] + [noir.request :as request] + [clojure.java.jdbc :as sql] + [com.twinql.clojure.http :as http]) + (:import [org.bouncycastle.jcajce.provider.asymmetric.rsa + BCRSAPublicKey] + [org.bouncycastle.cert + X509v3CertificateBuilder + X509CertificateHolder] + [org.bouncycastle.operator + DefaultSignatureAlgorithmIdentifierFinder + DefaultDigestAlgorithmIdentifierFinder] + [org.bouncycastle.operator.bc + BcRSAContentSignerBuilder] + [org.bouncycastle.asn1.x509 + X509Name + SubjectPublicKeyInfo] + [org.bouncycastle.asn1.x500 + X500Name])) + + + +(defonce signing-keypair + (-> (doto (org.bouncycastle.crypto.generators.RSAKeyPairGenerator.) + (.init (org.bouncycastle.crypto.params.RSAKeyGenerationParameters. + (BigInteger. "123") (java.security.SecureRandom.) 512 123))) + (.generateKeyPair))) + +(defonce cert-signer + (let [sig-id (.find (DefaultSignatureAlgorithmIdentifierFinder.) "SHA1withRSA") + digest-id (.find (DefaultDigestAlgorithmIdentifierFinder.) sig-id) + ;;digest-id (.find (DefaultDigestAlgorithmIdentifierFinder.) "SHA1") + ] + (-> (BcRSAContentSignerBuilder. sig-id digest-id) + (.build (.getPrivate signing-keypair))))) + +(defonce cert-serial + (atom 1N)) + +(defn join-lines [s] + (apply str (clojure.string/split s #"\n"))) + +(defn decode-spkac [spkac] + (org.bouncycastle.mozilla.SignedPublicKeyAndChallenge. + (ring.util.codec/base64-decode (join-lines spkac)))) + +(defmulti spkac-pubkey->map type) + +(defmethod spkac-pubkey->map BCRSAPublicKey [pubkey] + {:modulus (.getModulus pubkey) + :exponent (.getPublicExponent pubkey)}) + +(defn spkac-pubkey [creq] + (spkac-pubkey->map + (.getPublicKey creq "BC"))) + +(defn now [] + (java.util.Date.)) + +(defn twenty-years-from-now [] + (java.util.Date. (+ (* 20 3600 24 365 1000) (.getTime (now))))) + +(defn sign-spkac [spkac] + (let [serial (swap! cert-serial inc) + pubkeyinfo (-> spkac + (.getPublicKeyAndChallenge) + (.getSubjectPublicKeyInfo)) + builder (X509v3CertificateBuilder. + (X500Name. "CN=Guenologlyan Mages Association") + (java.math.BigInteger. (str serial)) + (now) + (twenty-years-from-now) + (X500Name. "CN=Benki User") + pubkeyinfo) + cert (.build builder cert-signer)] + cert)) + +(defpage "/genkey" [] + (redirect "/keys")) + +(defpage [:post "/keys/register"] [] + (let [spkac-data (get-in (request/ring-request) [:params :key]) + spkac (decode-spkac spkac-data) + pubkey (spkac-pubkey spkac)] + (with-dbt + (query "INSERT INTO rsa_keys (modulus, exponent) VALUES (?::NUMERIC, ?::NUMERIC) RETURNING 't'" + (str (:modulus pubkey)) (str (:exponent pubkey))) + (query "INSERT INTO user_rsa_keys (\"user\", modulus, exponent) VALUES (?, ?::NUMERIC, ?::NUMERIC) RETURNING 't'" + *user* (str (:modulus pubkey)) (str (:exponent pubkey))) + ;;(redirect (linkrel :keys)) + {:status 200 + :headers {"Content-Type" "application/x-x509-user-cert"} + :body (.getEncoded (sign-spkac spkac))}))) + +(defpage "/keys" [] + (with-auth + (layout {} "Key Management" + [:p + [:h2 "Your Keys"] + [:table + {:style "border: 1px solid #000" + :border "1"} + [:thead + [:th {:style "text-align: left"} "Exponent"] + [:th {:style "text-align: left"} "Modulus"]] + [:tbody + (with-dbt + (for [{e :exponent, m :modulus} + (query "select * from user_rsa_keys where \"user\" = ?" *user*)] + [:tr + [:td e] + [:td m]]))]]] + [:p + [:h2 "Generate a Key Pair"] + [:h3 "RSA"] + [:form {:method "POST", :action (linkrel :keys :register)} + [:keygen {:name "key", :keytype "RSA"}] + [:input {:type "submit" :value "Generate"}]] + [:h3 "ECDSA" + [:form {:method "POST", :href (linkrel :keys :register)} + [:keygen {:name "key", :keytype "EC", :keyparams "secp521r1"}] + [:input {:type "submit" :value "Generate"}]]]]))) diff --git a/src/mulk/benki/main.clj b/src/mulk/benki/main.clj index 7bcf332..c5ddfd6 100644 --- a/src/mulk/benki/main.clj +++ b/src/mulk/benki/main.clj @@ -5,7 +5,7 @@ [hiccup core page-helpers] [mulk.benki util config db]) (:require [noir server options] - [mulk.benki wiki auth book_marx id lazychat xmpp] + [mulk.benki wiki auth book_marx id lazychat xmpp genkey] [ring.middleware.file] [noir.session :as session] [noir.request :as request] @@ -128,8 +128,13 @@ (def server (atom nil)) +(defn init-security! [] + (java.security.Security/addProvider + (org.bouncycastle.jce.provider.BouncyCastleProvider.))) + (defn -main [& args] (do + (init-security!) (future (mulk.benki.xmpp/init-xmpp!)) (future (mulk.benki.lazychat/init-lazychat!)) (future (swap! server (run-server)))) diff --git a/src/mulk/benki/util.clj b/src/mulk/benki/util.clj index 0bfe5e9..ce9803d 100644 --- a/src/mulk/benki/util.clj +++ b/src/mulk/benki/util.clj @@ -69,6 +69,8 @@ [[:lafargue :feed]] (fmt nil "/lafargue/feed") [[:lafargue :post]] (fmt nil "/lafargue/post") [[:wiki title & xs]] (fmt nil "/wiki/~a~@[~a~]" title (first xs)) + [[:keys]] "/keys" + [[:keys :register]] "/keys/register" )) (defn link [& args] @@ -116,3 +118,18 @@ (defn sanitize-html [html] (Jsoup/clean html (Whitelist/basic))) + + +;;;; * Debugging +(defonce logger + (org.apache.log4j.Logger/getLogger "eu.mulk.benki")) + +(defn info [s] + (.info logger s)) + +(defn log [s] + (.info logger s)) + +(defn debug [s] + (.debug logger s)) + -- cgit v1.2.3