diff --git a/project.clj b/project.clj index 3e98a6f..c2df95e 100644 --- a/project.clj +++ b/project.clj @@ -6,6 +6,8 @@ :license {:name "Eclipse Public License" :url "https://raw.githubusercontent.com/metabase/saml20-clj/master/LICENSE"} + :global-vars {*warn-on-reflection* true} + :aliases {"test" ["with-profile" "+test" "test"] "bikeshed" ["with-profile" "+bikeshed" "bikeshed" "--max-line-length" "150"] diff --git a/src/saml20_clj/coerce.clj b/src/saml20_clj/coerce.clj index 70139e0..8acd0d0 100644 --- a/src/saml20_clj/coerce.clj +++ b/src/saml20_clj/coerce.clj @@ -6,7 +6,28 @@ [page :as h.page]] [saml20-clj [encode-decode :as encode-decode] - [xml :as saml.xml]])) + [xml :as saml.xml]]) + (:import [clojure.lang IPersistentMap IPersistentVector] + [java.io ByteArrayInputStream StringWriter] + [java.security KeyFactory KeyStore PrivateKey PublicKey Security] + [java.security.cert CertificateFactory X509Certificate] + java.security.interfaces.RSAPrivateCrtKey + [java.security.spec PKCS8EncodedKeySpec RSAPublicKeySpec] + javax.crypto.spec.SecretKeySpec + [javax.xml.transform OutputKeys TransformerFactory] + javax.xml.transform.dom.DOMSource + javax.xml.transform.stream.StreamResult + org.bouncycastle.jce.provider.BouncyCastleProvider + org.opensaml.core.config.InitializationService + org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport + org.opensaml.core.xml.XMLObject + org.opensaml.saml.common.SignableSAMLObject + org.opensaml.saml.saml2.core.Response + [org.opensaml.security.credential BasicCredential Credential] + org.opensaml.security.x509.BasicX509Credential + org.opensaml.security.x509.impl.KeyStoreX509CredentialAdapter + org.opensaml.xmlsec.config.impl.JavaCryptoValidationInitializer + [org.w3c.dom Document Element Node])) ;; these have to be initialized before using. ;; @@ -15,30 +36,30 @@ (defonce ^:private -init (do ;; add BouncyCastle as a security provider. - (java.security.Security/addProvider (org.bouncycastle.jce.provider.BouncyCastleProvider.)) + (Security/addProvider (BouncyCastleProvider.)) ;; initialize OpenSAML - (org.opensaml.core.config.InitializationService/initialize) + (InitializationService/initialize) ;; verify that OpenSAML has the crypto classes it needs - (.init (org.opensaml.xmlsec.config.impl.JavaCryptoValidationInitializer.)) + (.init (JavaCryptoValidationInitializer.)) nil)) (defprotocol CoerceToPrivateKey (->PrivateKey - ^java.security.PrivateKey [this] - ^java.security.PrivateKey [this ^String algorithm] + [this] + [this ^String algorithm] "Coerce something such as a base-64-encoded string or byte array to a `PrivateKey`. This isn't used directly by OpenSAML -- the key must be passed as part of an OpenSAML `Credential`. See `->Credential`.")) (defprotocol CoerceToX509Certificate - (->X509Certificate ^java.security.cert.X509Certificate [this] + (^java.security.cert.X509Certificate ->X509Certificate [this] "Coerce something such as a base-64-encoded string or byte array to a `java.security.cert.X509Certificate`. This class isn't used directly by OpenSAML; instead, certificate must be coerced to an OpenSAML `Credential`. See `->Credential`.")) (defprotocol CoerceToCredential (->Credential - ^org.opensaml.security.credential.Credential [this] - ^org.opensaml.security.credential.Credential [public-key private-key] + [this] + [public-key private-key] "Coerce something such as a byte array or base-64-encoded String to an OpenSAML `Credential`. Typically, you'd use the credential with just the public key for the IdP's credentials, for encrypting requests (in combination with SP credentails) or verifying signature(s) in the response. A credential with both public and private keys would @@ -46,47 +67,47 @@ for decrypting encrypted assertions in the response.")) (defprotocol CoerceToElement - (->Element ^org.w3c.dom.Element [this])) + (^org.w3c.dom.Element ->Element [this])) (defprotocol CoerceToSAMLObject - (->SAMLObject ^org.opensaml.saml.common.SignableSAMLObject [this])) + (^org.opensaml.saml.common.SignableSAMLObject ->SAMLObject [this])) (defprotocol CoerceToResponse - (->Response ^org.opensaml.saml.saml2.core.Response [this])) + (^org.opensaml.saml.saml2.core.Response ->Response [this])) (defprotocol SerializeXMLString - (->xml-string ^String [this])) + (^String ->xml-string [this])) ;;; ------------------------------------------------------ Impl ------------------------------------------------------ (defn keystore - ^java.security.KeyStore [{:keys [keystore ^String filename ^String password]}] + ^KeyStore [{:keys [keystore ^String filename ^String password]}] (or keystore (when (some-> filename io/as-file .exists) (with-open [is (io/input-stream filename)] - (doto (java.security.KeyStore/getInstance "JKS") + (doto (KeyStore/getInstance "JKS") (.load is (.toCharArray password))))))) (defmulti bytes->PrivateKey "Generate a private key from a byte array using the given `algorithm`. (bytes->PrivateKey my-byte-array :rsa) ;; -> ..." - {:arglists '(^java.security.PrivateKey [^bytes key-bytes algorithm])} + {:arglists '(^PrivateKey [^bytes key-bytes algorithm])} (fn [_ algorithm] (keyword algorithm))) (defmethod bytes->PrivateKey :default [^bytes key-bytes algorithm] - (.generatePrivate (java.security.KeyFactory/getInstance (str/upper-case (name algorithm)), "BC") - (java.security.spec.PKCS8EncodedKeySpec. key-bytes))) + (.generatePrivate (KeyFactory/getInstance (str/upper-case (name algorithm)), "BC") + (PKCS8EncodedKeySpec. key-bytes))) (defmethod bytes->PrivateKey :aes [^bytes key-bytes _] - (javax.crypto.spec.SecretKeySpec. key-bytes - 0 - (count key-bytes) - "AES")) + (SecretKeySpec. key-bytes + 0 + (count key-bytes) + "AES")) ;; I don't think we can use the "class name" of a byte array in `extend-protocol` (extend (Class/forName "[B") @@ -109,29 +130,29 @@ ([s] (->PrivateKey s :rsa)) ([s algorithm] (->PrivateKey (encode-decode/base64-credential->bytes s) algorithm))) - java.security.PrivateKey + PrivateKey (->PrivateKey ([this] this) ([this _] this)) - org.opensaml.security.credential.Credential + Credential (->PrivateKey ([this] (.getPrivateKey this)) ([this _] (->PrivateKey this))) - clojure.lang.IPersistentMap + IPersistentMap (->PrivateKey ([{^String key-alias :alias, ^String password :password, :as m}] (when-let [keystore (keystore m)] (when-let [key (.getKey keystore key-alias (.toCharArray password))] - (assert (instance? java.security.PrivateKey key)) + (assert (instance? PrivateKey key)) key))) ([this _] (->PrivateKey this))) - clojure.lang.IPersistentVector + IPersistentVector (->PrivateKey ([[_ k]] (->PrivateKey k)) @@ -142,9 +163,8 @@ CoerceToX509Certificate {:->X509Certificate (fn [^bytes this] - (let [cert-factory (java.security.cert.CertificateFactory/getInstance - "X.509")] - (with-open [is (java.io.ByteArrayInputStream. this)] + (let [cert-factory (CertificateFactory/getInstance "X.509")] + (with-open [is (ByteArrayInputStream. this)] (.generateCertificate cert-factory is))))}) (extend-protocol CoerceToX509Certificate @@ -155,14 +175,14 @@ (->X509Certificate [s] (->X509Certificate (encode-decode/base64-credential->bytes s))) - java.security.cert.X509Certificate + X509Certificate (->X509Certificate [this] this) - org.opensaml.security.x509.BasicX509Credential + BasicX509Credential (->X509Certificate [this] (.getEntityCertificate this)) - clojure.lang.IPersistentMap + IPersistentMap (->X509Certificate [{^String key-alias :alias, ^String password :password, :as m}] (when (and key-alias password) @@ -182,53 +202,53 @@ ([public-key private-key] (let [cert (->X509Certificate public-key)] (if private-key - (org.opensaml.security.x509.BasicX509Credential. cert (->PrivateKey private-key)) - (org.opensaml.security.x509.BasicX509Credential. cert))))) + (BasicX509Credential. cert (->PrivateKey private-key)) + (BasicX509Credential. cert))))) - clojure.lang.IPersistentMap + IPersistentMap (->Credential ([{^String key-alias :alias, ^String password :password, :as m}] (when (and key-alias password) (when-let [keystore (keystore m)] - (org.opensaml.security.x509.impl.KeyStoreX509CredentialAdapter. keystore key-alias (.toCharArray password))))) + (KeyStoreX509CredentialAdapter. keystore key-alias (.toCharArray password))))) ([m private-key] - (let [credential (->Credential m) + (let [credential ^Credential (->Credential m) public-key (.getPublicKey credential)] (->Credential public-key private-key)))) - clojure.lang.IPersistentVector + IPersistentVector (->Credential [[public-key private-key]] (->Credential public-key private-key)) - java.security.PublicKey + PublicKey (->Credential [this] - (org.opensaml.security.credential.BasicCredential. this)) + (BasicCredential. this)) - javax.crypto.spec.SecretKeySpec + SecretKeySpec (->Credential [this] - (org.opensaml.security.credential.BasicCredential. this)) + (BasicCredential. this)) - java.security.interfaces.RSAPrivateCrtKey + RSAPrivateCrtKey (->Credential [this] - (org.opensaml.security.credential.BasicCredential. - (.generatePublic (java.security.KeyFactory/getInstance "RSA") - (java.security.spec.RSAPublicKeySpec. (.getModulus this) (.getPublicExponent this))) + (BasicCredential. + (.generatePublic (KeyFactory/getInstance "RSA") + (RSAPublicKeySpec. (.getModulus this) (.getPublicExponent this))) this))) (extend-protocol CoerceToElement nil (->Element [_] nil) - org.w3c.dom.Element + Element (->Element [this] this) - org.w3c.dom.Document + Document (->Element [this] (.getDocumentElement this)) - org.opensaml.core.xml.XMLObject + XMLObject (->Element [this] - (let [marshaller-factory (org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport/getMarshallerFactory) + (let [marshaller-factory (XMLObjectProviderRegistrySupport/getMarshallerFactory) marshaller (.getMarshaller marshaller-factory this)] (when-not marshaller (throw (ex-info (format "Don't know how to marshall %s" (.getCanonicalName (class this))) @@ -241,7 +261,7 @@ ;; hiccup-style xml element ;; TODO -- it's a little inefficient to serialize this to a string and then back to an element - clojure.lang.IPersistentVector + IPersistentVector (->Element [this] (->Element (->xml-string this)))) @@ -249,19 +269,19 @@ nil (->SAMLObject [_] nil) - org.opensaml.saml.common.SignableSAMLObject + SignableSAMLObject (->SAMLObject [this] this) - org.w3c.dom.Element + Element (->SAMLObject [this] - (let [unmarshaller-factory (org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport/getUnmarshallerFactory) + (let [unmarshaller-factory (XMLObjectProviderRegistrySupport/getUnmarshallerFactory) unmarshaller (.getUnmarshaller unmarshaller-factory this)] (when-not unmarshaller (throw (ex-info (format "Don't know how to unmarshall %s" (.getCanonicalName (class this))) {:object this}))) (.unmarshall unmarshaller this))) - org.w3c.dom.Document + Document (->SAMLObject [this] (->SAMLObject (.getDocumentElement this))) @@ -273,10 +293,10 @@ nil (->Response [_] nil) - org.opensaml.saml.saml2.core.Response + Response (->Response [this] this) - org.opensaml.saml.common.SignableSAMLObject + SignableSAMLObject (->Response [this] (throw (ex-info (format "Don't know how to coerce a %s to a Response" (.getCanonicalName (class this))) {:object this}))) @@ -292,25 +312,25 @@ String (->xml-string [this] this) - clojure.lang.IPersistentVector + IPersistentVector (->xml-string [this] (str (h.page/xml-declaration "UTF-8") (hiccup/html this))) - org.w3c.dom.Node + Node (->xml-string [this] - (let [transformer (doto (.. javax.xml.transform.TransformerFactory newInstance newTransformer) - #_(.setOutputProperty javax.xml.transform.OutputKeys/OMIT_XML_DECLARATION "yes") - (.setOutputProperty javax.xml.transform.OutputKeys/ENCODING "UTF-8") - (.setOutputProperty javax.xml.transform.OutputKeys/INDENT "yes") + (let [transformer (doto (.. TransformerFactory newInstance newTransformer) + #_(.setOutputProperty OutputKeys/OMIT_XML_DECLARATION "yes") + (.setOutputProperty OutputKeys/ENCODING "UTF-8") + (.setOutputProperty OutputKeys/INDENT "yes") (.setOutputProperty "{http://xml.apache.org/xslt}indent-amount" "2")) - dom-source (javax.xml.transform.dom.DOMSource. this)] - (with-open [w (java.io.StringWriter.)] - (let [stream-result (javax.xml.transform.stream.StreamResult. w)] + dom-source (DOMSource. this)] + (with-open [w (StringWriter.)] + (let [stream-result (StreamResult. w)] (.transform transformer dom-source stream-result)) (.toString w)))) - org.opensaml.core.xml.XMLObject + XMLObject (->xml-string [this] (->xml-string (.getDOM this)))) diff --git a/src/saml20_clj/crypto.clj b/src/saml20_clj/crypto.clj index dd1a4a0..b4eee59 100644 --- a/src/saml20_clj/crypto.clj +++ b/src/saml20_clj/crypto.clj @@ -1,8 +1,16 @@ (ns saml20-clj.crypto (:require [saml20-clj.coerce :as coerce]) - (:import org.apache.xml.security.Init + (:import com.onelogin.saml2.util.Util + [java.security PrivateKey SecureRandom] + javax.crypto.spec.SecretKeySpec + org.apache.xml.security.Init + org.opensaml.saml.common.SignableSAMLObject + org.opensaml.saml.security.impl.SAMLSignatureProfileValidator org.opensaml.security.credential.Credential - org.opensaml.xmlsec.signature.support.SignatureConstants)) + org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory + org.opensaml.xmlsec.signature.impl.SignatureBuilder + [org.opensaml.xmlsec.signature.support SignatureConstants SignatureValidator Signer] + org.w3c.dom.Element)) (def signature-algorithms {:dsa {nil SignatureConstants/ALGO_ID_SIGNATURE_DSA @@ -29,16 +37,16 @@ ;; TODO -- I'm pretty sure this mutates `object` (defn sign - ^org.w3c.dom.Element [object credential & {:keys [signature-algorithm + ^Element [object credential & {:keys [signature-algorithm canonicalization-algorithm] :or {signature-algorithm [:rsa :sha256] canonicalization-algorithm :excl-omit-comments}}] - (when-let [object (coerce/->SAMLObject object)] + (when-let [^SignableSAMLObject object (coerce/->SAMLObject object)] (when-let [^Credential credential (try (coerce/->Credential credential) (catch Throwable _ (coerce/->Credential (coerce/->PrivateKey credential))))] - (let [signature (doto (.buildObject (org.opensaml.xmlsec.signature.impl.SignatureBuilder.)) + (let [signature (doto (.buildObject (SignatureBuilder.)) (.setSigningCredential credential) (.setSignatureAlgorithm (or (get-in signature-algorithms signature-algorithm) (throw (ex-info "No matching signature algorithm" @@ -46,41 +54,41 @@ (.setCanonicalizationAlgorithm (or (get canonicalization-algorithms canonicalization-algorithm) (throw (ex-info "No matching canonicalization algorithm" {:algorithm canonicalization-algorithm}))))) - key-info-gen (doto (new org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory) + key-info-gen (doto (new X509KeyInfoGeneratorFactory) (.setEmitEntityCertificate true))] (when-let [key-info (.generate (.newInstance key-info-gen) credential)] ; No need to test X509 coercion first (.setKeyInfo signature key-info)) (.setSignature object signature) (let [element (coerce/->Element object)] - (org.opensaml.xmlsec.signature.support.Signer/signObject signature) + (Signer/signObject signature) element))))) (defn decrypt! [sp-private-key element] - (when-let [sp-private-key (coerce/->PrivateKey sp-private-key)] + (when-let [^PrivateKey sp-private-key (coerce/->PrivateKey sp-private-key)] (when-let [element (coerce/->Element element)] - (com.onelogin.saml2.util.Util/decryptElement element sp-private-key)))) + (Util/decryptElement element sp-private-key)))) (defn recursive-decrypt! [sp-private-key element] - (when-let [sp-private-key (coerce/->PrivateKey sp-private-key)] - (when-let [element (coerce/->Element element)] + (when-let [^PrivateKey sp-private-key (coerce/->PrivateKey sp-private-key)] + (when-let [^Element element (coerce/->Element element)] (when (= (.getNodeName element) "saml:EncryptedAssertion") (decrypt! sp-private-key element)) (doseq [i (range (.. element getChildNodes getLength)) :let [child (.. element getChildNodes (item i))] - :when (instance? org.w3c.dom.Element child)] + :when (instance? Element child)] (recursive-decrypt! sp-private-key child))))) (defn ^:private secure-random-bytes (^bytes [size] (let [ba (byte-array size) - r (java.security.SecureRandom.)] + r (SecureRandom.)] (.nextBytes r ba) ba)) (^bytes [] (secure-random-bytes 20))) -(defn new-secret-key ^javax.crypto.spec.SecretKeySpec [] - (javax.crypto.spec.SecretKeySpec. (secure-random-bytes) "HmacSHA1")) +(defn new-secret-key ^SecretKeySpec [] + (SecretKeySpec. (secure-random-bytes) "HmacSHA1")) (defonce ^:private -init (do @@ -101,14 +109,14 @@ (when-let [credential (coerce/->Credential credential)] ;; validate that the signature conforms to the SAML signature spec (try - (.validate (org.opensaml.saml.security.impl.SAMLSignatureProfileValidator.) signature) + (.validate (SAMLSignatureProfileValidator.) signature) (catch Throwable e (throw (ex-info "Signature does not conform to SAML signature spec" {:object (coerce/->xml-string object)} e)))) ;; validate that the signature matches the credential (try - (org.opensaml.xmlsec.signature.support.SignatureValidator/validate signature credential) + (SignatureValidator/validate signature credential) (catch Throwable e (throw (ex-info "Signature does not match credential" {:object (coerce/->xml-string object)} diff --git a/src/saml20_clj/sp/metadata.clj b/src/saml20_clj/sp/metadata.clj index c18f7f1..af268c7 100644 --- a/src/saml20_clj/sp/metadata.clj +++ b/src/saml20_clj/sp/metadata.clj @@ -2,14 +2,15 @@ (:require [clojure.string :as str] [saml20-clj [coerce :as coerce] - [encode-decode :as encode]])) + [encode-decode :as encode]]) + (:import java.security.cert.X509Certificate)) (defn metadata [{:keys [app-name acs-url slo-url sp-cert requests-signed want-assertions-signed] :or {want-assertions-signed true requests-signed true}}] - (let [encoded-cert (some-> ^java.security.cert.X509Certificate sp-cert + (let [encoded-cert (some-> ^X509Certificate sp-cert .getEncoded encode/encode-base64 encode/bytes->str)] diff --git a/src/saml20_clj/sp/request.clj b/src/saml20_clj/sp/request.clj index 980ae25..17e0a19 100644 --- a/src/saml20_clj/sp/request.clj +++ b/src/saml20_clj/sp/request.clj @@ -6,7 +6,9 @@ [coerce :as coerce] [crypto :as crypto] [encode-decode :as encode-decode] - [state :as state]])) + [state :as state]]) + (:import java.util.UUID + org.w3c.dom.Element)) (defn- format-instant "Converts a date-time to a SAML 2.0 time string." @@ -15,23 +17,23 @@ (defn request "Return XML elements that represent a SAML 2.0 auth request." - ^org.w3c.dom.Element [{:keys [ ;; e.g. something like a UUID - request-id - ;; e.g. "Metabase" - sp-name - ;; e.g. ttp://sp.example.com/demo1/index.php?acs - acs-url - ;; e.g. http://sp.example.com/demo1/index.php?acs - idp-url - ;; e.g. http://idp.example.com/SSOService.php - issuer - ;; If present, record the request - state-manager - ;; If present, we can sign the request - credential - instant] - :or {request-id (str (java.util.UUID/randomUUID)) - instant (t/instant)}}] + ^Element [{:keys [ ;; e.g. something like a UUID + request-id + ;; e.g. "Metabase" + sp-name + ;; e.g. ttp://sp.example.com/demo1/index.php?acs + acs-url + ;; e.g. http://sp.example.com/demo1/index.php?acs + idp-url + ;; e.g. http://idp.example.com/SSOService.php + issuer + ;; If present, record the request + state-manager + ;; If present, we can sign the request + credential + instant] + :or {request-id (str (UUID/randomUUID)) + instant (t/instant)}}] (assert acs-url) (assert idp-url) (assert sp-name) diff --git a/src/saml20_clj/sp/response.clj b/src/saml20_clj/sp/response.clj index 18a201c..7f6d9ff 100644 --- a/src/saml20_clj/sp/response.clj +++ b/src/saml20_clj/sp/response.clj @@ -7,8 +7,10 @@ [crypto :as crypto] [state :as state] [xml :as xml]]) - (:import [org.opensaml.saml.saml2.core Assertion Attribute AttributeStatement Audience AudienceRestriction Response - Subject SubjectConfirmation SubjectConfirmationData])) + (:import org.opensaml.core.xml.XMLObject + [org.opensaml.saml.saml2.core + Assertion Attribute AttributeStatement Audience AudienceRestriction + Response StatusCode Subject SubjectConfirmation SubjectConfirmationData])) (defn clone-response "Clone an OpenSAML `response` object." @@ -220,7 +222,7 @@ (let [status (.. response getStatus getStatusCode getValue)] {:in-response-to (.getInResponseTo response) :status status - :success? (= status org.opensaml.saml.saml2.core.StatusCode/SUCCESS) + :success? (= status StatusCode/SUCCESS) :version (.. response getVersion toString) :issue-instant (t/instant (.getIssueInstant response)) :destination (.getDestination response)}))) @@ -262,7 +264,7 @@ attrs (into {} (for [^AttributeStatement statement statements ^Attribute attribute (.getAttributes statement)] [(saml2-attr->name (.getName attribute)) ; Or (.getFriendlyName a) ?? - (map #(-> ^org.opensaml.core.xml.XMLObject % .getDOM .getTextContent) + (map #(-> ^XMLObject % .getDOM .getTextContent) (.getAttributeValues attribute))])) audiences (for [^AudienceRestriction restriction (.. assertion getConditions getAudienceRestrictions) ^Audience audience (.getAudiences restriction)] diff --git a/src/saml20_clj/xml.clj b/src/saml20_clj/xml.clj index 577ddcf..2a0c890 100644 --- a/src/saml20_clj/xml.clj +++ b/src/saml20_clj/xml.clj @@ -1,6 +1,7 @@ (ns saml20-clj.xml (:require [saml20-clj.encode-decode :as encode-decode]) - (:import [javax.xml.parsers DocumentBuilder DocumentBuilderFactory] + (:import java.io.ByteArrayInputStream + [javax.xml.parsers DocumentBuilder DocumentBuilderFactory] org.w3c.dom.Document)) (defn document-builder @@ -12,7 +13,7 @@ (.setFeature "http://apache.org/xml/features/nonvalidating/load-external-dtd" false) (.setExpandEntityReferences false)))) -(defn clone-document [^org.w3c.dom.Document document] +(defn clone-document [^Document document] (when document (let [clone (.. (DocumentBuilderFactory/newInstance) newDocumentBuilder newDocument) original-root (.getDocumentElement document) @@ -24,5 +25,5 @@ "Parse a string into an XML `Document`." ^Document [^String s] (let [document (document-builder)] - (with-open [is (java.io.ByteArrayInputStream. (encode-decode/str->bytes s))] + (with-open [is (ByteArrayInputStream. (encode-decode/str->bytes s))] (.parse document is)))) diff --git a/test/saml20_clj/coerce_test.clj b/test/saml20_clj/coerce_test.clj index d911b8c..d303d76 100644 --- a/test/saml20_clj/coerce_test.clj +++ b/test/saml20_clj/coerce_test.clj @@ -2,17 +2,21 @@ (:require [clojure.test :refer :all] [saml20-clj [coerce :as coerce] - [test :as test]])) + [test :as test]]) + (:import [java.security Key PrivateKey] + java.security.cert.X509Certificate + org.apache.commons.codec.digest.DigestUtils + org.opensaml.security.x509.X509Credential)) -(defn- key-fingerprint [^java.security.Key k] +(defn- key-fingerprint [^Key k] (when k - (org.apache.commons.codec.digest.DigestUtils/md5Hex (.getEncoded k)))) + (DigestUtils/md5Hex (.getEncoded k)))) (deftest ->PrivateKey-test (is (= nil (coerce/->PrivateKey nil))) (letfn [(is-key-with-fingerprint? [input] (let [k (coerce/->PrivateKey input)] - (is (instance? java.security.PrivateKey k)) + (is (instance? PrivateKey k)) (is (= "af284d1f7bfa789c787f689a95604d31" (key-fingerprint k)))))] (testing "Should be able to get a private key from base-64-encoded string" @@ -108,19 +112,19 @@ c7tL1QjbfAUHAQYwmHkWgPP+T2wAv0pOt36GgMCM (testing "from String" (testing "make sure we can parse a certificate, no armor" (coerce/->X509Certificate test-certificate-str-1) - (is (instance? java.security.cert.X509Certificate + (is (instance? X509Certificate (coerce/->X509Certificate test-certificate-str-1)))) (testing "make sure we can parse a certificate with armor 512b key" - (is (instance? java.security.cert.X509Certificate + (is (instance? X509Certificate (coerce/->X509Certificate test-certificate-str-2)))) (testing "make sure we can parse a certificate with armor 2048b key" - (is (instance? java.security.cert.X509Certificate + (is (instance? X509Certificate (coerce/->X509Certificate test-certificate-str-3)))) (testing "make sure we can parse a certificate with armor 4096b key" - (is (instance? java.security.cert.X509Certificate + (is (instance? X509Certificate (coerce/->X509Certificate test-certificate-str-4)))))) -(defn- x509-credential-fingerprints [^org.opensaml.security.x509.X509Credential credential] +(defn- x509-credential-fingerprints [^X509Credential credential] {:public (key-fingerprint (.getPublicKey credential)) :private (key-fingerprint (.getPrivateKey credential))})