2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution, if any, must include
17 * the following acknowledgment: "This product includes software developed by
18 * the University Corporation for Advanced Internet Development
19 * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20 * may appear in the software itself, if and wherever such third-party
21 * acknowledgments normally appear.
23 * Neither the name of Shibboleth nor the names of its contributors, nor
24 * Internet2, nor the University Corporation for Advanced Internet Development,
25 * Inc., nor UCAID may be used to endorse or promote products derived from this
26 * software without specific prior written permission. For written permission,
27 * please contact shibboleth@shibboleth.org
29 * Products derived from this software may not be called Shibboleth, Internet2,
30 * UCAID, or the University Corporation for Advanced Internet Development, nor
31 * may Shibboleth appear in their name, without prior written permission of the
32 * University Corporation for Advanced Internet Development.
35 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36 * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39 * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40 * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41 * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50 package edu.internet2.middleware.shibboleth.common;
51 import java.security.GeneralSecurityException;
52 import java.security.Key;
53 import java.security.KeyStore;
54 import java.security.PrivateKey;
55 import java.security.cert.Certificate;
56 import java.security.cert.X509Certificate;
58 import java.util.Date;
59 import java.util.Enumeration;
60 import java.util.Iterator;
61 import javax.crypto.SecretKey;
62 import javax.xml.parsers.DocumentBuilder;
63 import javax.xml.parsers.ParserConfigurationException;
64 import org.apache.xml.security.exceptions.XMLSecurityException;
65 import org.apache.xml.security.keys.KeyInfo;
66 import org.apache.xml.security.signature.XMLSignature;
67 import org.opensaml.*;
72 * Basic Shibboleth POST browser profile implementation with basic support for
75 * @author Scott Cantor
76 * @created April 11, 2002
78 public class ShibPOSTProfile
80 /** XML Signature algorithm to apply */
81 protected String algorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
83 /** Policy URIs to attach or check against */
84 protected String[] policies = null;
86 /** Official name of issuing site */
87 protected String issuer = null;
89 /** Abstract interface into trust base */
90 protected OriginSiteMapper mapper = null;
92 /** The URL of the receiving SHIRE */
93 protected String receiver = null;
95 /** Seconds allowed to elapse from issuance of response */
96 protected int ttlSeconds = 0;
99 * SHIRE-side constructor for a ShibPOSTProfile object
101 * @param policies Array of policy URIs that the implementation
103 * @param mapper Interface between profile and trust base
104 * @param receiver URL of SHIRE
105 * @param ttlSeconds Length of time in seconds allowed to elapse
106 * from issuance of SAML response
107 * @exception SAMLException Raised if a profile implementation cannot be
108 * constructed from the supplied information
110 public ShibPOSTProfile(String[] policies, OriginSiteMapper mapper, String receiver, int ttlSeconds)
113 if (policies == null || policies.length == 0 || mapper == null ||
114 receiver == null || receiver.length() == 0 || ttlSeconds <= 0)
115 throw new SAMLException(SAMLException.REQUESTER, "ShibPOSTProfile() found a null or invalid argument");
117 this.mapper = mapper;
118 this.receiver = receiver;
119 this.ttlSeconds = ttlSeconds;
120 this.policies = new String[policies.length];
121 System.arraycopy(policies, 0, this.policies, 0, policies.length);
125 * HS-side constructor for a ShibPOSTProfile object
127 * @param policies Array of policy URIs that the implementation
129 * @param issuer "Official" name of issuing origin site
130 * @exception SAMLException Raised if a profile implementation cannot be
131 * constructed from the supplied information
133 public ShibPOSTProfile(String[] policies, String issuer)
136 if (policies == null || policies.length == 0 || issuer == null || issuer.length() == 0)
137 throw new SAMLException(SAMLException.REQUESTER, "ShibPOSTProfile() found a null or invalid argument");
138 this.issuer = issuer;
139 this.policies = new String[policies.length];
140 System.arraycopy(policies, 0, this.policies, 0, policies.length);
144 * Locates an assertion containing a "bearer" AuthenticationStatement in
145 * the response and validates the enclosing assertion with respect to the
148 * @param r The response to the accepting site
149 * @return An SSO assertion
151 public SAMLAssertion getSSOAssertion(SAMLResponse r)
153 return SAMLPOSTProfile.getSSOAssertion(r,policies);
157 * Locates a "bearer" AuthenticationStatement in the assertion and
158 * validates the statement with respect to the POST profile
160 * @param a The SSO assertion sent to the accepting site
161 * @return A "bearer" authentication statement
163 public SAMLAuthenticationStatement getSSOStatement(SAMLAssertion a)
165 return (a==null) ? null : SAMLPOSTProfile.getSSOStatement(a);
169 * Parse a Base-64 encoded buffer back into a SAML response and test its
170 * validity against the POST profile, including use of the default replay
173 * Also does trust evaluation based on the information available from the
174 * origin site mapper, in accordance with general Shibboleth processing
175 * semantics. Club-specific processing must be performed in a subclass.<P>
179 * @param buf A Base-64 encoded buffer containing a SAML
181 * @return SAML response sent by origin site
182 * @exception SAMLException Thrown if the response cannot be understood or
185 public SAMLResponse accept(byte[] buf)
188 // The built-in SAML functionality will do most of the basic non-crypto checks.
189 // Note that if the response only contains a status error, it gets tossed out
191 SAMLResponse r = SAMLPOSTProfile.accept(buf, receiver, ttlSeconds);
193 // Now we do some more non-crypto (ie. cheap) work to match up the origin site
194 // with its associated data. If we can't even find a SSO statement in the response
195 // we just return the response to the caller, who will presumably notice this.
196 SAMLAssertion assertion = getSSOAssertion(r);
197 if (assertion == null)
200 SAMLAuthenticationStatement sso = getSSOStatement(assertion);
204 // Examine the subject information.
205 SAMLSubject subject = sso.getSubject();
206 if (subject.getNameQualifier() == null)
207 throw new SAMLException(SAMLException.RESPONDER, "ShibPOSTProfile.accept() requires subject name qualifier");
209 String originSite = subject.getNameQualifier();
210 String handleService = assertion.getIssuer();
212 // Is this a trusted HS?
213 Iterator hsNames = mapper.getHandleServiceNames(originSite);
214 boolean bFound = false;
215 while (!bFound && hsNames != null && hsNames.hasNext())
216 if (hsNames.next().equals(handleService))
219 throw new SAMLException(SAMLException.RESPONDER, "ShibPOSTProfile.accept() detected an untrusted HS for the origin site");
221 Key hsKey = mapper.getHandleServiceKey(handleService);
222 KeyStore ks = mapper.getTrustedRoots();
224 // Signature verification now takes place. We check the assertion and the response.
225 // Assertion signing is optional, response signing is mandatory.
226 if (assertion.getSignature() != null && !verifySignature(assertion, handleService, ks, hsKey))
227 throw new SAMLException(SAMLException.RESPONDER, "ShibPOSTProfile.accept() detected an invalid assertion signature");
228 if (!verifySignature(r, handleService, ks, hsKey))
229 throw new SAMLException(SAMLException.RESPONDER, "ShibPOSTProfile.accept() detected an invalid response signature");
235 * Used by HS to generate a signed SAML response conforming to the POST
240 * @param recipient URL of intended consumer
241 * @param name Name of subject
242 * @param nameQualifier Federates or qualifies subject name (optional)
243 * @param subjectIP Client address of subject (optional)
244 * @param authMethod URI of authentication method being asserted
245 * @param authInstant Date and time of authentication being asserted
246 * @param bindings Array of SAML authorities the relying party
247 * may contact (optional)
248 * @param responseKey A secret or private key to use in response
250 * @param responseCert A public key certificate to enclose with the
251 * response (optional)
252 * @param assertionKey A secret or private key to use in assertion
253 * signature or MAC (optional)
254 * @param assertionCert A public key certificate to enclose with the
255 * assertion (optional)
256 * @return SAML response to send to accepting site
257 * @exception SAMLException Base class of exceptions that may be thrown
260 public SAMLResponse prepare(String recipient,
262 String nameQualifier,
266 SAMLAuthorityBinding[] bindings,
267 Key responseKey, X509Certificate responseCert,
268 Key assertionKey, X509Certificate assertionCert
272 if (responseKey == null || (!(responseKey instanceof PrivateKey) && !(responseKey instanceof SecretKey)))
273 throw new InvalidCryptoException(SAMLException.RESPONDER, "ShibPOSTProfile.prepare() requires a response key (private or secret)");
274 if (assertionKey != null && !(assertionKey instanceof PrivateKey) && !(assertionKey instanceof SecretKey))
275 throw new InvalidCryptoException(SAMLException.RESPONDER, "ShibPOSTProfile.prepare() detected an invalid type of assertion key");
277 DocumentBuilder builder = null;
280 builder = org.opensaml.XML.parserPool.get();
281 Document doc = builder.newDocument();
283 XMLSignature rsig = new XMLSignature(doc, null, algorithm);
284 XMLSignature asig = null;
285 if (assertionKey != null)
286 asig = new XMLSignature(doc, null, algorithm);
288 SAMLResponse r = SAMLPOSTProfile.prepare(
304 if (assertionCert != null)
305 asig.addKeyInfo(assertionCert);
306 if (assertionKey instanceof PrivateKey)
307 asig.sign((PrivateKey)assertionKey);
309 asig.sign((SecretKey)assertionKey);
311 if (responseCert != null)
312 rsig.addKeyInfo(responseCert);
313 if (responseKey instanceof PrivateKey)
314 rsig.sign((PrivateKey)responseKey);
316 rsig.sign((SecretKey)responseKey);
319 catch (ParserConfigurationException pce)
321 throw new SAMLException(SAMLException.RESPONDER, "ShibPOSTProfile.prepare() unable to obtain XML parser instance: " + pce.getMessage(), pce);
323 catch (XMLSecurityException e)
325 throw new InvalidCryptoException(SAMLException.RESPONDER, "ShibPOSTProfile.prepare() detected an XML security problem during signature creation", e);
330 org.opensaml.XML.parserPool.put(builder);
335 * Searches the replay cache for the specified assertion and inserts a
336 * newly seen assertion into the cache<P>
338 * Also performs garbage collection of the cache by deleting expired
341 * @param a The assertion to check
342 * @return true iff the assertion has not been seen before
344 public synchronized boolean checkReplayCache(SAMLAssertion a)
346 // Default implementation uses the basic replay cache implementation.
347 return SAMLPOSTProfile.checkReplayCache(a);
351 * Default signature verification algorithm uses an embedded X509
352 * certificate or an explicit key to verify the signature. The certificate
353 * is examined to insure the subject CN matches the signer, and that it is
354 * signed by a trusted CA
356 * @param obj The object containing the signature
357 * @param signerName The name of the signer
358 * @param ks A keystore containing trusted root certificates
359 * @param knownKey An explicit key to use if a certificate cannot be
361 * @return The result of signature verification
363 protected boolean verifySignature(SAMLSignedObject obj, String signerName, KeyStore ks, Key knownKey)
367 XMLSignature sig = (obj != null) ? obj.getSignature() : null;
370 KeyInfo ki = sig.getKeyInfo();
371 if (ks != null && ki != null)
373 X509Certificate cert = ki.getX509Certificate();
376 cert.checkValidity();
377 if (!sig.checkSignatureValue(cert))
379 if (signerName != null)
381 String dname = cert.getSubjectDN().getName();
382 String cname = "CN=" + signerName;
383 if (!dname.equalsIgnoreCase(cname) && !dname.regionMatches(true, 0, cname + ',', 0, cname.length() + 1))
387 String iname = cert.getIssuerDN().getName();
388 for (Enumeration aliases = ks.aliases(); aliases.hasMoreElements(); )
390 String alias = (String)aliases.nextElement();
391 if (!ks.isCertificateEntry(alias))
393 Certificate cacert = ks.getCertificate(alias);
394 if (!(cacert instanceof X509Certificate))
396 ((X509Certificate)cacert).checkValidity();
397 if (iname.equals(((X509Certificate)cacert).getSubjectDN().getName()))
399 cert.verify(cacert.getPublicKey());
407 return (knownKey != null) ? sig.checkSignatureValue(knownKey) : false;
409 catch (XMLSecurityException e)
414 catch (GeneralSecurityException e)