|
6 | 6 | [page :as h.page]]
|
7 | 7 | [saml20-clj
|
8 | 8 | [encode-decode :as encode-decode]
|
9 |
| - [xml :as saml.xml]])) |
| 9 | + [xml :as saml.xml]]) |
| 10 | + (:import [clojure.lang IPersistentMap IPersistentVector] |
| 11 | + [java.io ByteArrayInputStream StringWriter] |
| 12 | + [java.security KeyFactory KeyStore PrivateKey PublicKey Security] |
| 13 | + [java.security.cert CertificateFactory X509Certificate] |
| 14 | + java.security.interfaces.RSAPrivateCrtKey |
| 15 | + [java.security.spec PKCS8EncodedKeySpec RSAPublicKeySpec] |
| 16 | + javax.crypto.spec.SecretKeySpec |
| 17 | + [javax.xml.transform OutputKeys TransformerFactory] |
| 18 | + javax.xml.transform.dom.DOMSource |
| 19 | + javax.xml.transform.stream.StreamResult |
| 20 | + org.bouncycastle.jce.provider.BouncyCastleProvider |
| 21 | + org.opensaml.core.config.InitializationService |
| 22 | + org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport |
| 23 | + org.opensaml.core.xml.XMLObject |
| 24 | + org.opensaml.saml.common.SignableSAMLObject |
| 25 | + org.opensaml.saml.saml2.core.Response |
| 26 | + [org.opensaml.security.credential BasicCredential Credential] |
| 27 | + org.opensaml.security.x509.BasicX509Credential |
| 28 | + org.opensaml.security.x509.impl.KeyStoreX509CredentialAdapter |
| 29 | + org.opensaml.xmlsec.config.impl.JavaCryptoValidationInitializer |
| 30 | + [org.w3c.dom Document Element Node])) |
10 | 31 |
|
11 | 32 | ;; these have to be initialized before using.
|
12 | 33 | ;;
|
|
15 | 36 | (defonce ^:private -init
|
16 | 37 | (do
|
17 | 38 | ;; add BouncyCastle as a security provider.
|
18 |
| - (java.security.Security/addProvider (org.bouncycastle.jce.provider.BouncyCastleProvider.)) |
| 39 | + (Security/addProvider (BouncyCastleProvider.)) |
19 | 40 | ;; initialize OpenSAML
|
20 |
| - (org.opensaml.core.config.InitializationService/initialize) |
| 41 | + (InitializationService/initialize) |
21 | 42 | ;; verify that OpenSAML has the crypto classes it needs
|
22 |
| - (.init (org.opensaml.xmlsec.config.impl.JavaCryptoValidationInitializer.)) |
| 43 | + (.init (JavaCryptoValidationInitializer.)) |
23 | 44 | nil))
|
24 | 45 |
|
25 | 46 | (defprotocol CoerceToPrivateKey
|
26 | 47 | (->PrivateKey
|
27 |
| - ^java.security.PrivateKey [this] |
28 |
| - ^java.security.PrivateKey [this ^String algorithm] |
| 48 | + ^PrivateKey [this] |
| 49 | + ^PrivateKey [this ^String algorithm] |
29 | 50 | "Coerce something such as a base-64-encoded string or byte array to a `PrivateKey`. This isn't used directly by
|
30 | 51 | OpenSAML -- the key must be passed as part of an OpenSAML `Credential`. See `->Credential`."))
|
31 | 52 |
|
32 | 53 | (defprotocol CoerceToX509Certificate
|
33 |
| - (->X509Certificate ^java.security.cert.X509Certificate [this] |
| 54 | + (->X509Certificate ^X509Certificate [this] |
34 | 55 | "Coerce something such as a base-64-encoded string or byte array to a `java.security.cert.X509Certificate`. This
|
35 | 56 | class isn't used directly by OpenSAML; instead, certificate must be coerced to an OpenSAML `Credential`. See
|
36 | 57 | `->Credential`."))
|
37 | 58 |
|
38 | 59 | (defprotocol CoerceToCredential
|
39 | 60 | (->Credential
|
40 |
| - ^org.opensaml.security.credential.Credential [this] |
41 |
| - ^org.opensaml.security.credential.Credential [public-key private-key] |
| 61 | + ^Credential [this] |
| 62 | + ^Credential [public-key private-key] |
42 | 63 | "Coerce something such as a byte array or base-64-encoded String to an OpenSAML `Credential`. Typically, you'd use
|
43 | 64 | the credential with just the public key for the IdP's credentials, for encrypting requests (in combination with SP
|
44 | 65 | credentails) or verifying signature(s) in the response. A credential with both public and private keys would
|
45 | 66 | typically contain *your* public and private keys, for encrypting requests (in combination with IdP credentials) or
|
46 | 67 | for decrypting encrypted assertions in the response."))
|
47 | 68 |
|
48 | 69 | (defprotocol CoerceToElement
|
49 |
| - (->Element ^org.w3c.dom.Element [this])) |
| 70 | + (->Element ^Element [this])) |
50 | 71 |
|
51 | 72 | (defprotocol CoerceToSAMLObject
|
52 |
| - (->SAMLObject ^org.opensaml.saml.common.SignableSAMLObject [this])) |
| 73 | + (->SAMLObject ^SignableSAMLObject [this])) |
53 | 74 |
|
54 | 75 | (defprotocol CoerceToResponse
|
55 |
| - (->Response ^org.opensaml.saml.saml2.core.Response [this])) |
| 76 | + (->Response ^Response [this])) |
56 | 77 |
|
57 | 78 | (defprotocol SerializeXMLString
|
58 | 79 | (->xml-string ^String [this]))
|
|
61 | 82 | ;;; ------------------------------------------------------ Impl ------------------------------------------------------
|
62 | 83 |
|
63 | 84 | (defn keystore
|
64 |
| - ^java.security.KeyStore [{:keys [keystore ^String filename ^String password]}] |
| 85 | + ^KeyStore [{:keys [keystore ^String filename ^String password]}] |
65 | 86 | (or keystore
|
66 | 87 | (when (some-> filename io/as-file .exists)
|
67 | 88 | (with-open [is (io/input-stream filename)]
|
68 |
| - (doto (java.security.KeyStore/getInstance "JKS") |
| 89 | + (doto (KeyStore/getInstance "JKS") |
69 | 90 | (.load is (.toCharArray password)))))))
|
70 | 91 |
|
71 | 92 | (defmulti bytes->PrivateKey
|
72 | 93 | "Generate a private key from a byte array using the given `algorithm`.
|
73 | 94 |
|
74 | 95 | (bytes->PrivateKey my-byte-array :rsa) ;; -> ..."
|
75 |
| - {:arglists '(^java.security.PrivateKey [^bytes key-bytes algorithm])} |
| 96 | + {:arglists '(^PrivateKey [^bytes key-bytes algorithm])} |
76 | 97 | (fn [_ algorithm]
|
77 | 98 | (keyword algorithm)))
|
78 | 99 |
|
79 | 100 | (defmethod bytes->PrivateKey :default
|
80 | 101 | [^bytes key-bytes algorithm]
|
81 |
| - (.generatePrivate (java.security.KeyFactory/getInstance (str/upper-case (name algorithm)), "BC") |
82 |
| - (java.security.spec.PKCS8EncodedKeySpec. key-bytes))) |
| 102 | + (.generatePrivate (KeyFactory/getInstance (str/upper-case (name algorithm)), "BC") |
| 103 | + (PKCS8EncodedKeySpec. key-bytes))) |
83 | 104 |
|
84 | 105 | (defmethod bytes->PrivateKey :aes
|
85 | 106 | [^bytes key-bytes _]
|
86 |
| - (javax.crypto.spec.SecretKeySpec. key-bytes |
87 |
| - 0 |
88 |
| - (count key-bytes) |
89 |
| - "AES")) |
| 107 | + (SecretKeySpec. key-bytes |
| 108 | + 0 |
| 109 | + (count key-bytes) |
| 110 | + "AES")) |
90 | 111 |
|
91 | 112 | ;; I don't think we can use the "class name" of a byte array in `extend-protocol`
|
92 | 113 | (extend (Class/forName "[B")
|
|
109 | 130 | ([s] (->PrivateKey s :rsa))
|
110 | 131 | ([s algorithm] (->PrivateKey (encode-decode/base64-credential->bytes s) algorithm)))
|
111 | 132 |
|
112 |
| - java.security.PrivateKey |
| 133 | + PrivateKey |
113 | 134 | (->PrivateKey
|
114 | 135 | ([this] this)
|
115 | 136 | ([this _] this))
|
116 | 137 |
|
117 |
| - org.opensaml.security.credential.Credential |
| 138 | + Credential |
118 | 139 | (->PrivateKey
|
119 | 140 | ([this]
|
120 | 141 | (.getPrivateKey this))
|
121 | 142 | ([this _]
|
122 | 143 | (->PrivateKey this)))
|
123 | 144 |
|
124 |
| - clojure.lang.IPersistentMap |
| 145 | + IPersistentMap |
125 | 146 | (->PrivateKey
|
126 | 147 | ([{^String key-alias :alias, ^String password :password, :as m}]
|
127 | 148 | (when-let [keystore (keystore m)]
|
128 | 149 | (when-let [key (.getKey keystore key-alias (.toCharArray password))]
|
129 |
| - (assert (instance? java.security.PrivateKey key)) |
| 150 | + (assert (instance? PrivateKey key)) |
130 | 151 | key)))
|
131 | 152 | ([this _]
|
132 | 153 | (->PrivateKey this)))
|
133 | 154 |
|
134 |
| - clojure.lang.IPersistentVector |
| 155 | + IPersistentVector |
135 | 156 | (->PrivateKey
|
136 | 157 | ([[_ k]]
|
137 | 158 | (->PrivateKey k))
|
|
142 | 163 | CoerceToX509Certificate
|
143 | 164 | {:->X509Certificate
|
144 | 165 | (fn [^bytes this]
|
145 |
| - (let [cert-factory (java.security.cert.CertificateFactory/getInstance |
146 |
| - "X.509")] |
147 |
| - (with-open [is (java.io.ByteArrayInputStream. this)] |
| 166 | + (let [cert-factory (CertificateFactory/getInstance "X.509")] |
| 167 | + (with-open [is (ByteArrayInputStream. this)] |
148 | 168 | (.generateCertificate cert-factory is))))})
|
149 | 169 |
|
150 | 170 | (extend-protocol CoerceToX509Certificate
|
|
155 | 175 | (->X509Certificate [s]
|
156 | 176 | (->X509Certificate (encode-decode/base64-credential->bytes s)))
|
157 | 177 |
|
158 |
| - java.security.cert.X509Certificate |
| 178 | + X509Certificate |
159 | 179 | (->X509Certificate [this] this)
|
160 | 180 |
|
161 |
| - org.opensaml.security.x509.BasicX509Credential |
| 181 | + BasicX509Credential |
162 | 182 | (->X509Certificate [this]
|
163 | 183 | (.getEntityCertificate this))
|
164 | 184 |
|
165 |
| - clojure.lang.IPersistentMap |
| 185 | + IPersistentMap |
166 | 186 | (->X509Certificate
|
167 | 187 | [{^String key-alias :alias, ^String password :password, :as m}]
|
168 | 188 | (when (and key-alias password)
|
|
182 | 202 | ([public-key private-key]
|
183 | 203 | (let [cert (->X509Certificate public-key)]
|
184 | 204 | (if private-key
|
185 |
| - (org.opensaml.security.x509.BasicX509Credential. cert (->PrivateKey private-key)) |
186 |
| - (org.opensaml.security.x509.BasicX509Credential. cert))))) |
| 205 | + (BasicX509Credential. cert (->PrivateKey private-key)) |
| 206 | + (BasicX509Credential. cert))))) |
187 | 207 |
|
188 |
| - clojure.lang.IPersistentMap |
| 208 | + IPersistentMap |
189 | 209 | (->Credential
|
190 | 210 | ([{^String key-alias :alias, ^String password :password, :as m}]
|
191 | 211 | (when (and key-alias password)
|
192 | 212 | (when-let [keystore (keystore m)]
|
193 |
| - (org.opensaml.security.x509.impl.KeyStoreX509CredentialAdapter. keystore key-alias (.toCharArray password))))) |
| 213 | + (KeyStoreX509CredentialAdapter. keystore key-alias (.toCharArray password))))) |
194 | 214 | ([m private-key]
|
195 | 215 | (let [credential (->Credential m)
|
196 | 216 | public-key (.getPublicKey credential)]
|
197 | 217 | (->Credential public-key private-key))))
|
198 | 218 |
|
199 |
| - clojure.lang.IPersistentVector |
| 219 | + IPersistentVector |
200 | 220 | (->Credential [[public-key private-key]]
|
201 | 221 | (->Credential public-key private-key))
|
202 | 222 |
|
203 |
| - java.security.PublicKey |
| 223 | + PublicKey |
204 | 224 | (->Credential [this]
|
205 |
| - (org.opensaml.security.credential.BasicCredential. this)) |
| 225 | + (BasicCredential. this)) |
206 | 226 |
|
207 |
| - javax.crypto.spec.SecretKeySpec |
| 227 | + SecretKeySpec |
208 | 228 | (->Credential [this]
|
209 |
| - (org.opensaml.security.credential.BasicCredential. this)) |
| 229 | + (BasicCredential. this)) |
210 | 230 |
|
211 |
| - java.security.interfaces.RSAPrivateCrtKey |
| 231 | + RSAPrivateCrtKey |
212 | 232 | (->Credential [this]
|
213 |
| - (org.opensaml.security.credential.BasicCredential. |
214 |
| - (.generatePublic (java.security.KeyFactory/getInstance "RSA") |
215 |
| - (java.security.spec.RSAPublicKeySpec. (.getModulus this) (.getPublicExponent this))) |
| 233 | + (BasicCredential. |
| 234 | + (.generatePublic (KeyFactory/getInstance "RSA") |
| 235 | + (RSAPublicKeySpec. (.getModulus this) (.getPublicExponent this))) |
216 | 236 | this)))
|
217 | 237 |
|
218 | 238 | (extend-protocol CoerceToElement
|
219 | 239 | nil
|
220 | 240 | (->Element [_] nil)
|
221 | 241 |
|
222 |
| - org.w3c.dom.Element |
| 242 | + Element |
223 | 243 | (->Element [this] this)
|
224 | 244 |
|
225 |
| - org.w3c.dom.Document |
| 245 | + Document |
226 | 246 | (->Element [this]
|
227 | 247 | (.getDocumentElement this))
|
228 | 248 |
|
229 |
| - org.opensaml.core.xml.XMLObject |
| 249 | + XMLObject |
230 | 250 | (->Element [this]
|
231 |
| - (let [marshaller-factory (org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport/getMarshallerFactory) |
| 251 | + (let [marshaller-factory (XMLObjectProviderRegistrySupport/getMarshallerFactory) |
232 | 252 | marshaller (.getMarshaller marshaller-factory this)]
|
233 | 253 | (when-not marshaller
|
234 | 254 | (throw (ex-info (format "Don't know how to marshall %s" (.getCanonicalName (class this)))
|
|
241 | 261 |
|
242 | 262 | ;; hiccup-style xml element
|
243 | 263 | ;; TODO -- it's a little inefficient to serialize this to a string and then back to an element
|
244 |
| - clojure.lang.IPersistentVector |
| 264 | + IPersistentVector |
245 | 265 | (->Element [this]
|
246 | 266 | (->Element (->xml-string this))))
|
247 | 267 |
|
248 | 268 | (extend-protocol CoerceToSAMLObject
|
249 | 269 | nil
|
250 | 270 | (->SAMLObject [_] nil)
|
251 | 271 |
|
252 |
| - org.opensaml.saml.common.SignableSAMLObject |
| 272 | + SignableSAMLObject |
253 | 273 | (->SAMLObject [this] this)
|
254 | 274 |
|
255 |
| - org.w3c.dom.Element |
| 275 | + Element |
256 | 276 | (->SAMLObject [this]
|
257 |
| - (let [unmarshaller-factory (org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport/getUnmarshallerFactory) |
| 277 | + (let [unmarshaller-factory (XMLObjectProviderRegistrySupport/getUnmarshallerFactory) |
258 | 278 | unmarshaller (.getUnmarshaller unmarshaller-factory this)]
|
259 | 279 | (when-not unmarshaller
|
260 | 280 | (throw (ex-info (format "Don't know how to unmarshall %s" (.getCanonicalName (class this)))
|
261 | 281 | {:object this})))
|
262 | 282 | (.unmarshall unmarshaller this)))
|
263 | 283 |
|
264 |
| - org.w3c.dom.Document |
| 284 | + Document |
265 | 285 | (->SAMLObject [this]
|
266 | 286 | (->SAMLObject (.getDocumentElement this)))
|
267 | 287 |
|
|
273 | 293 | nil
|
274 | 294 | (->Response [_] nil)
|
275 | 295 |
|
276 |
| - org.opensaml.saml.saml2.core.Response |
| 296 | + Response |
277 | 297 | (->Response [this] this)
|
278 | 298 |
|
279 |
| - org.opensaml.saml.common.SignableSAMLObject |
| 299 | + SignableSAMLObject |
280 | 300 | (->Response [this]
|
281 | 301 | (throw (ex-info (format "Don't know how to coerce a %s to a Response" (.getCanonicalName (class this)))
|
282 | 302 | {:object this})))
|
|
292 | 312 | String
|
293 | 313 | (->xml-string [this] this)
|
294 | 314 |
|
295 |
| - clojure.lang.IPersistentVector |
| 315 | + IPersistentVector |
296 | 316 | (->xml-string [this]
|
297 | 317 | (str
|
298 | 318 | (h.page/xml-declaration "UTF-8")
|
299 | 319 | (hiccup/html this)))
|
300 | 320 |
|
301 |
| - org.w3c.dom.Node |
| 321 | + Node |
302 | 322 | (->xml-string [this]
|
303 |
| - (let [transformer (doto (.. javax.xml.transform.TransformerFactory newInstance newTransformer) |
304 |
| - #_(.setOutputProperty javax.xml.transform.OutputKeys/OMIT_XML_DECLARATION "yes") |
305 |
| - (.setOutputProperty javax.xml.transform.OutputKeys/ENCODING "UTF-8") |
306 |
| - (.setOutputProperty javax.xml.transform.OutputKeys/INDENT "yes") |
| 323 | + (let [transformer (doto (.. TransformerFactory newInstance newTransformer) |
| 324 | + #_(.setOutputProperty OutputKeys/OMIT_XML_DECLARATION "yes") |
| 325 | + (.setOutputProperty OutputKeys/ENCODING "UTF-8") |
| 326 | + (.setOutputProperty OutputKeys/INDENT "yes") |
307 | 327 | (.setOutputProperty "{http://xml.apache.org/xslt}indent-amount" "2"))
|
308 |
| - dom-source (javax.xml.transform.dom.DOMSource. this)] |
309 |
| - (with-open [w (java.io.StringWriter.)] |
310 |
| - (let [stream-result (javax.xml.transform.stream.StreamResult. w)] |
| 328 | + dom-source (DOMSource. this)] |
| 329 | + (with-open [w (StringWriter.)] |
| 330 | + (let [stream-result (StreamResult. w)] |
311 | 331 | (.transform transformer dom-source stream-result))
|
312 | 332 | (.toString w))))
|
313 | 333 |
|
314 |
| - org.opensaml.core.xml.XMLObject |
| 334 | + XMLObject |
315 | 335 | (->xml-string [this]
|
316 | 336 | (->xml-string (.getDOM this))))
|
0 commit comments