Added support for SubjectAltName and "SSL hostname" matching from credentials to...
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / aa / AAServlet.java
1 /*
2  * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
3  * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
4  * provided that the following conditions are met: Redistributions of source code must retain the above copyright
5  * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the
6  * above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other
7  * materials provided with the distribution, if any, must include the following acknowledgment: "This product includes
8  * software developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu> Internet2
9  * Project. Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
10  * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2,
11  * nor the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
12  * products derived from this software without specific prior written permission. For written permission, please
13  * contact shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2,
14  * UCAID, or the University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name,
15  * without prior written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS
16  * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES,
17  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
18  * NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS
19  * WITH LICENSEE. IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED
20  * INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
23  * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24  * POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 package edu.internet2.middleware.shibboleth.aa;
28
29 import java.io.IOException;
30 import java.net.URI;
31 import java.net.URISyntaxException;
32 import java.security.Principal;
33 import java.security.cert.CertificateParsingException;
34 import java.security.cert.X509Certificate;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.Collection;
38 import java.util.Collections;
39 import java.util.Date;
40 import java.util.Iterator;
41 import java.util.List;
42 import java.util.regex.Matcher;
43 import java.util.regex.Pattern;
44
45 import javax.security.auth.x500.X500Principal;
46 import javax.servlet.ServletException;
47 import javax.servlet.UnavailableException;
48 import javax.servlet.http.HttpServletRequest;
49 import javax.servlet.http.HttpServletResponse;
50
51 import org.apache.log4j.Logger;
52 import org.apache.log4j.MDC;
53 import org.apache.xml.security.exceptions.XMLSecurityException;
54 import org.apache.xml.security.keys.KeyInfo;
55 import org.apache.xml.security.signature.XMLSignature;
56 import org.opensaml.InvalidCryptoException;
57 import org.opensaml.SAMLAssertion;
58 import org.opensaml.SAMLAttribute;
59 import org.opensaml.SAMLAttributeQuery;
60 import org.opensaml.SAMLAttributeStatement;
61 import org.opensaml.SAMLAudienceRestrictionCondition;
62 import org.opensaml.SAMLBinding;
63 import org.opensaml.SAMLCondition;
64 import org.opensaml.SAMLException;
65 import org.opensaml.SAMLIdentifier;
66 import org.opensaml.SAMLRequest;
67 import org.opensaml.SAMLResponse;
68 import org.opensaml.SAMLStatement;
69 import org.opensaml.SAMLSubject;
70 import org.w3c.dom.Document;
71 import org.w3c.dom.Element;
72 import org.w3c.dom.NodeList;
73
74 import sun.misc.BASE64Decoder;
75 import edu.internet2.middleware.shibboleth.aa.arp.ArpEngine;
76 import edu.internet2.middleware.shibboleth.aa.arp.ArpException;
77 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
78 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolverException;
79 import edu.internet2.middleware.shibboleth.common.AuthNPrincipal;
80 import edu.internet2.middleware.shibboleth.common.Credential;
81 import edu.internet2.middleware.shibboleth.common.Credentials;
82 import edu.internet2.middleware.shibboleth.common.InvalidNameIdentifierException;
83 import edu.internet2.middleware.shibboleth.common.NameIdentifierMapping;
84 import edu.internet2.middleware.shibboleth.common.NameIdentifierMappingException;
85 import edu.internet2.middleware.shibboleth.common.NameMapper;
86 import edu.internet2.middleware.shibboleth.common.OriginConfig;
87 import edu.internet2.middleware.shibboleth.common.RelyingParty;
88 import edu.internet2.middleware.shibboleth.common.SAMLBindingFactory;
89 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapperException;
90 import edu.internet2.middleware.shibboleth.common.ShibPOSTProfile;
91 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
92 import edu.internet2.middleware.shibboleth.common.ShibbolethOriginConfig;
93 import edu.internet2.middleware.shibboleth.common.TargetFederationComponent;
94 import edu.internet2.middleware.shibboleth.metadata.AttributeConsumerRole;
95 import edu.internet2.middleware.shibboleth.metadata.KeyDescriptor;
96 import edu.internet2.middleware.shibboleth.metadata.Provider;
97 import edu.internet2.middleware.shibboleth.metadata.ProviderRole;
98
99 /**
100  * @author Walter Hoehn
101  */
102
103 public class AAServlet extends TargetFederationComponent {
104
105         private AAConfig                                configuration;
106         protected AAResponder                   responder;
107         private NameMapper                              nameMapper;
108         private SAMLBinding                             binding;
109         private static Logger                   transactionLog  = Logger.getLogger("Shibboleth-TRANSACTION");
110         private AAServiceProviderMapper targetMapper;
111
112         private static Logger                   log                             = Logger.getLogger(AAServlet.class.getName());
113
114         public void init() throws ServletException {
115                 super.init();
116
117                 MDC.put("serviceId", "[AA] Core");
118                 log.info("Initializing Attribute Authority.");
119
120                 try {
121                         nameMapper = new NameMapper();
122                         loadConfiguration();
123
124                         binding = SAMLBindingFactory.getInstance(SAMLBinding.SAML_SOAP_HTTPS);
125
126                         log.info("Attribute Authority initialization complete.");
127
128                 } catch (ShibbolethConfigurationException ae) {
129                         log.fatal("The AA could not be initialized: " + ae);
130                         throw new UnavailableException("Attribute Authority failed to initialize.");
131                 } catch (SAMLException se) {
132                         log.fatal("SAML SOAP binding could not be loaded: " + se);
133                         throw new UnavailableException("Attribute Authority failed to initialize.");
134                 }
135         }
136
137         protected void loadConfiguration() throws ShibbolethConfigurationException {
138
139                 Document originConfig = OriginConfig.getOriginConfig(this.getServletContext());
140
141                 //Load global configuration properties
142                 configuration = new AAConfig(originConfig.getDocumentElement());
143
144                 //Load name mappings
145                 NodeList itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
146                                 NameIdentifierMapping.mappingNamespace, "NameMapping");
147
148                 for (int i = 0; i < itemElements.getLength(); i++) {
149                         try {
150                                 nameMapper.addNameMapping((Element) itemElements.item(i));
151                         } catch (NameIdentifierMappingException e) {
152                                 log.error("Name Identifier mapping could not be loaded: " + e);
153                         }
154                 }
155
156                 //Load signing credentials
157                 itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(Credentials.credentialsNamespace,
158                                 "Credentials");
159                 if (itemElements.getLength() < 1) {
160                         log.error("No credentials specified.");
161                 }
162                 if (itemElements.getLength() > 1) {
163                         log.error("Multiple Credentials specifications found, using first.");
164                 }
165                 Credentials credentials = new Credentials((Element) itemElements.item(0));
166
167                 //Load metadata
168                 itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
169                                 ShibbolethOriginConfig.originConfigNamespace, "FederationProvider");
170                 for (int i = 0; i < itemElements.getLength(); i++) {
171                         addFederationProvider((Element) itemElements.item(i));
172                 }
173                 if (providerCount() < 1) {
174                         log.error("No Federation Provider metadata loaded.");
175                         throw new ShibbolethConfigurationException("Could not load federation metadata.");
176                 }
177
178                 //Load relying party config
179                 try {
180                         targetMapper = new AAServiceProviderMapper(originConfig.getDocumentElement(), configuration, credentials,
181                                         this);
182                 } catch (ServiceProviderMapperException e) {
183                         log.error("Could not load origin configuration: " + e);
184                         throw new ShibbolethConfigurationException("Could not load origin configuration.");
185                 }
186
187                 try {
188                         //Startup Attribute Resolver
189                         AttributeResolver resolver = new AttributeResolver(configuration);
190
191                         //Startup ARP Engine
192                         ArpEngine arpEngine = null;
193                         itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
194                                         ShibbolethOriginConfig.originConfigNamespace, "ReleasePolicyEngine");
195
196                         if (itemElements.getLength() > 1) {
197                                 log.warn("Encountered multiple <ReleasePolicyEngine> configuration elements.  Using first...");
198                         }
199                         if (itemElements.getLength() < 1) {
200                                 arpEngine = new ArpEngine();
201                         } else {
202                                 arpEngine = new ArpEngine((Element) itemElements.item(0));
203                         }
204
205                         //Startup responder
206                         responder = new AAResponder(arpEngine, resolver);
207
208                 } catch (ArpException ae) {
209                         log.fatal("The AA could not be initialized due to a problem with the ARP Engine configuration: " + ae);
210                         throw new ShibbolethConfigurationException("Could not load ARP Engine.");
211                 } catch (AttributeResolverException ne) {
212                         log.fatal("The AA could not be initialized due to a problem with the Attribute Resolver configuration: "
213                                         + ne);
214                         throw new ShibbolethConfigurationException("Could not load Attribute Resolver.");
215                 }
216
217         }
218
219         public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
220
221                 MDC.put("serviceId", "[AA] " + new SAMLIdentifier().toString());
222                 MDC.put("remoteAddr", req.getRemoteAddr());
223                 log.info("Handling request.");
224
225                 AARelyingParty relyingParty = null;
226
227                 //Parse SOAP request
228                 SAMLRequest samlRequest = null;
229
230                 try {
231
232                         try {
233                                 samlRequest = binding.receive(req);
234
235                         } catch (SAMLException e) {
236                                 log.fatal("Unable to parse request: " + e);
237                                 throw new AAException("Invalid request data.");
238                         }
239
240                         if (samlRequest.getQuery() == null || !(samlRequest.getQuery() instanceof SAMLAttributeQuery)) {
241                                 throw new SAMLException(SAMLException.REQUESTER,
242                                                 "This SAML authority only responds to attribute queries.");
243                         }
244                         SAMLAttributeQuery attributeQuery = (SAMLAttributeQuery) samlRequest.getQuery();
245
246                         if (!fromLegacyProvider(req)) {
247                                 log.info("Remote provider has identified itself as: (" + attributeQuery.getResource() + ").");
248                         }
249
250                         //This is the requester name that will be passed to subsystems
251                         String effectiveName = null;
252
253                         X509Certificate credential = getCredentialFromProvider(req);
254                         if (credential == null || credential.getSubjectX500Principal().getName(X500Principal.RFC2253).equals("")) {
255                                 log.info("Request is from an unauthenticated service provider.");
256                         } else {
257
258                                 //Identify a Relying Party
259                                 relyingParty = targetMapper.getRelyingParty(attributeQuery.getResource());
260
261                                 try {
262                                         effectiveName = getEffectiveName(req, relyingParty);
263                                 } catch (InvalidProviderCredentialException ipc) {
264                                         sendFailure(resp, samlRequest, new SAMLException(SAMLException.RESPONDER,
265                                                         "Invalid credentials for request."));
266                                         return;
267                                 }
268                         }
269
270                         if (effectiveName == null) {
271                                 log.debug("Using default Relying Party for unauthenticated provider.");
272                                 relyingParty = targetMapper.getRelyingParty(null);
273                         }
274
275                         //Fail if we can't honor SAML Subject Confirmation
276                         if (!fromLegacyProvider(req)) {
277                                 Iterator iterator = attributeQuery.getSubject().getConfirmationMethods();
278                                 boolean hasConfirmationMethod = false;
279                                 while (iterator.hasNext()) {
280                                         log.info("Request contains SAML Subject Confirmation method: (" + (String) iterator.next() + ").");
281                                 }
282                                 if (hasConfirmationMethod) {
283                                         throw new SAMLException(SAMLException.REQUESTER,
284                                                         "This SAML authority cannot honor requests containing the supplied SAML Subject Confirmation Method.");
285                                 }
286                         }
287
288                         //Map Subject to local principal
289                         if (relyingParty.getIdentityProvider().getProviderId() != null
290                                         && !relyingParty.getIdentityProvider().getProviderId().equals(
291                                                         attributeQuery.getSubject().getName().getNameQualifier())) {
292                                 log.error("The name qualifier (" + attributeQuery.getSubject().getName().getNameQualifier()
293                                                 + ") for the referenced subject is not valid for this identiy provider.");
294                                 throw new NameIdentifierMappingException("The name qualifier ("
295                                                 + attributeQuery.getSubject().getName().getNameQualifier()
296                                                 + ") for the referenced subject is not valid for this identiy provider.");
297                         }
298
299                         Principal principal = null;
300                         try {
301                                 // for testing
302                                 if (attributeQuery.getSubject().getName().getFormat().equals("urn:mace:shibboleth:test:nameIdentifier")) {
303                                         principal = new AuthNPrincipal("test-handle");
304                                 } else {
305                                         principal = nameMapper.getPrincipal(attributeQuery.getSubject().getName(), relyingParty,
306                                                         relyingParty.getIdentityProvider());
307                                 }
308                                 log.info("Request is for principal (" + principal.getName() + ").");
309
310                         } catch (InvalidNameIdentifierException invalidNameE) {
311                                 log.info("Could not associate the request subject with a principal: " + invalidNameE);
312                                 try {
313                                         if (relyingParty.passThruErrors()) {
314                                                 sendFailure(resp, samlRequest, new SAMLException(Arrays
315                                                                 .asList(invalidNameE.getSAMLErrorCodes()), "The supplied Subject was unrecognized.",
316                                                                 invalidNameE));
317
318                                         } else {
319                                                 sendFailure(resp, samlRequest, new SAMLException(Arrays
320                                                                 .asList(invalidNameE.getSAMLErrorCodes()), "The supplied Subject was unrecognized."));
321                                         }
322                                         return;
323                                 } catch (Exception ee) {
324                                         log.fatal("Could not construct a SAML error response: " + ee);
325                                         throw new ServletException("Attribute Authority response failure.");
326                                 }
327                         }
328
329                         SAMLAttribute[] attrs;
330                         Iterator requestedAttrsIterator = attributeQuery.getDesignators();
331                         if (requestedAttrsIterator.hasNext()) {
332                                 log.info("Request designates specific attributes, resolving this set.");
333                                 ArrayList requestedAttrs = new ArrayList();
334                                 while (requestedAttrsIterator.hasNext()) {
335                                         SAMLAttribute attribute = (SAMLAttribute) requestedAttrsIterator.next();
336                                         try {
337                                                 log.debug("Designated attribute: (" + attribute.getName() + ")");
338                                                 requestedAttrs.add(new URI(attribute.getName()));
339                                         } catch (URISyntaxException use) {
340                                                 log
341                                                                 .error("Request designated an attribute name that does not conform to the required URI syntax ("
342                                                                                 + attribute.getName() + ").  Ignoring this attribute");
343                                         }
344                                 }
345
346                                 attrs = responder.getReleaseAttributes(principal, effectiveName, null, (URI[]) requestedAttrs
347                                                 .toArray(new URI[0]));
348                         } else {
349                                 log.info("Request does not designate specific attributes, resolving all available.");
350                                 attrs = responder.getReleaseAttributes(principal, effectiveName, null);
351                         }
352
353                         log.info("Found " + attrs.length + " attribute(s) for " + principal.getName());
354                         sendResponse(resp, attrs, samlRequest, relyingParty, null);
355                         log.info("Successfully responded about " + principal.getName());
356
357                         if (effectiveName == null) {
358                                 if (fromLegacyProvider(req)) {
359                                         transactionLog.info("Attribute assertion issued to anonymous legacy provider at ("
360                                                         + req.getRemoteAddr() + ") on behalf of principal (" + principal.getName() + ").");
361                                 } else {
362                                         transactionLog.info("Attribute assertion issued to anonymous provider at (" + req.getRemoteAddr()
363                                                         + ") on behalf of principal (" + principal.getName() + ").");
364                                 }
365                         } else {
366                                 if (fromLegacyProvider(req)) {
367                                         transactionLog.info("Attribute assertion issued to legacy provider (" + effectiveName
368                                                         + ") on behalf of principal (" + principal.getName() + ").");
369                                 } else {
370                                         transactionLog.info("Attribute assertion issued to provider (" + effectiveName
371                                                         + ") on behalf of principal (" + principal.getName() + ").");
372                                 }
373                         }
374
375                 } catch (Exception e) {
376                         log.error("Error while processing request: " + e);
377                         try {
378                                 if (relyingParty != null && relyingParty.passThruErrors()) {
379                                         sendFailure(resp, samlRequest, new SAMLException(SAMLException.RESPONDER,
380                                                         "General error processing request.", e));
381                                 } else if (configuration.passThruErrors()) {
382                                         sendFailure(resp, samlRequest, new SAMLException(SAMLException.RESPONDER,
383                                                         "General error processing request.", e));
384                                 } else {
385                                         sendFailure(resp, samlRequest, new SAMLException(SAMLException.RESPONDER,
386                                                         "General error processing request."));
387                                 }
388                                 return;
389                         } catch (Exception ee) {
390                                 log.fatal("Could not construct a SAML error response: " + ee);
391                                 throw new ServletException("Attribute Authority response failure.");
392                         }
393
394                 }
395         }
396
397         protected String getEffectiveName(HttpServletRequest req, AARelyingParty relyingParty)
398                         throws InvalidProviderCredentialException {
399
400                 //X500Principal credentialName = getCredentialName(req);
401                 X509Certificate credential = getCredentialFromProvider(req);
402
403                 if (credential == null || credential.getSubjectX500Principal().getName(X500Principal.RFC2253).equals("")) {
404                         log.info("Request is from an unauthenticated service provider.");
405                         return null;
406
407                 } else {
408                         log.info("Request contains credential: ("
409                                         + credential.getSubjectX500Principal().getName(X500Principal.RFC2253) + ").");
410                         //Mockup old requester name for requests from < 1.2 targets
411                         if (fromLegacyProvider(req)) {
412                                 String legacyName = ShibPOSTProfile.getHostNameFromDN(credential.getSubjectX500Principal());
413                                 if (legacyName == null) {
414                                         log.error("Unable to extract legacy requester name from certificate subject.");
415                                 }
416
417                                 log.info("Request from legacy service provider: (" + legacyName + ").");
418                                 return legacyName;
419
420                         } else {
421
422                                 //See if we have metadata for this provider
423                                 Provider provider = lookup(relyingParty.getProviderId());
424                                 if (provider == null) {
425                                         log.info("No metadata found for provider: (" + relyingParty.getProviderId() + ").");
426                                         log.info("Treating remote provider as unauthenticated.");
427                                         return null;
428                                 }
429
430                                 //Make sure that the suppplied credential is valid for the selected relying party
431                                 if (isValidCredential(provider, credential)) {
432                                         log.info("Supplied credential validated for this provider.");
433                                         log.info("Request from service provider: (" + relyingParty.getProviderId() + ").");
434                                         return relyingParty.getProviderId();
435                                 } else {
436                                         log.error("Supplied credential ("
437                                                         + credential.getSubjectX500Principal().getName(X500Principal.RFC2253)
438                                                         + ") is NOT valid for provider (" + relyingParty.getProviderId() + ").");
439                                         throw new InvalidProviderCredentialException("Invalid credential.");
440                                 }
441                         }
442                 }
443         }
444
445         public void destroy() {
446                 log.info("Cleaning up resources.");
447                 responder.destroy();
448                 nameMapper.destroy();
449         }
450
451         public void sendResponse(HttpServletResponse resp, SAMLAttribute[] attrs, SAMLRequest samlRequest,
452                         RelyingParty relyingParty, SAMLException exception) throws IOException {
453
454                 SAMLException ourSE = null;
455                 SAMLResponse samlResponse = null;
456
457                 try {
458                         if (attrs == null || attrs.length == 0) {
459                                 //No attribute found
460                                 samlResponse = new SAMLResponse(samlRequest.getId(), null, null, exception);
461                         } else {
462
463                                 if (samlRequest.getQuery() == null || !(samlRequest.getQuery() instanceof SAMLAttributeQuery)) {
464                                         throw new SAMLException(SAMLException.REQUESTER,
465                                                         "This SAML authority only responds to attribute queries");
466                                 }
467                                 SAMLAttributeQuery attributeQuery = (SAMLAttributeQuery) samlRequest.getQuery();
468
469                                 //Reference requested subject
470                                 SAMLSubject rSubject = (SAMLSubject) attributeQuery.getSubject().clone();
471
472                                 //Set appropriate audience
473                                 ArrayList audiences = new ArrayList();
474                                 if (relyingParty.getProviderId() != null) {
475                                         audiences.add(relyingParty.getProviderId());
476                                 }
477                                 if (relyingParty.getName() != null && !relyingParty.getName().equals(relyingParty.getProviderId())) {
478                                         audiences.add(relyingParty.getName());
479                                 }
480                                 SAMLCondition condition = new SAMLAudienceRestrictionCondition(audiences);
481
482                                 //Put all attributes into an assertion
483                                 SAMLStatement statement = new SAMLAttributeStatement(rSubject, Arrays.asList(attrs));
484
485                                 //Set assertion expiration to longest attribute expiration
486                                 long max = 0;
487                                 for (int i = 0; i < attrs.length; i++) {
488                                         if (max < attrs[i].getLifetime()) {
489                                                 max = attrs[i].getLifetime();
490                                         }
491                                 }
492                                 Date now = new Date();
493                                 Date then = new Date(now.getTime() + (max * 1000)); //max is in seconds
494
495                                 SAMLAssertion sAssertion = new SAMLAssertion(relyingParty.getIdentityProvider().getProviderId(), now,
496                                                 then, Collections.singleton(condition), null, Collections.singleton(statement));
497
498                                 samlResponse = new SAMLResponse(samlRequest.getId(), null, Collections.singleton(sAssertion), exception);
499                                 addSignatures(samlResponse, relyingParty);
500                         }
501                 } catch (SAMLException se) {
502                         ourSE = se;
503                 } catch (CloneNotSupportedException ex) {
504                         ourSE = new SAMLException(SAMLException.RESPONDER, ex);
505
506                 } finally {
507
508                         if (log.isDebugEnabled()) {
509                                 try {
510                                         log.debug("Dumping generated SAML Response:"
511                                                         + System.getProperty("line.separator")
512                                                         + new String(
513                                                                         new BASE64Decoder().decodeBuffer(new String(samlResponse.toBase64(), "ASCII")),
514                                                                         "UTF8"));
515                                 } catch (SAMLException e) {
516                                         log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
517                                 } catch (IOException e) {
518                                         log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
519                                 }
520                         }
521
522                         binding.respond(resp, samlResponse, ourSE);
523                 }
524         }
525
526         private void addSignatures(SAMLResponse reponse, RelyingParty relyingParty) throws SAMLException {
527
528                 //Sign the assertions, if appropriate
529                 if (relyingParty.getIdentityProvider().getAssertionSigningCredential() != null
530                                 && relyingParty.getIdentityProvider().getAssertionSigningCredential().getPrivateKey() != null) {
531
532                         String assertionAlgorithm;
533                         if (relyingParty.getIdentityProvider().getAssertionSigningCredential().getCredentialType() == Credential.RSA) {
534                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
535                         } else if (relyingParty.getIdentityProvider().getAssertionSigningCredential().getCredentialType() == Credential.DSA) {
536                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
537                         } else {
538                                 throw new InvalidCryptoException(SAMLException.RESPONDER,
539                                                 "ShibPOSTProfile.prepare() currently only supports signing with RSA and DSA keys.");
540                         }
541
542                         ((SAMLAssertion) reponse.getAssertions().next()).sign(assertionAlgorithm, relyingParty
543                                         .getIdentityProvider().getAssertionSigningCredential().getPrivateKey(), Arrays.asList(relyingParty
544                                         .getIdentityProvider().getAssertionSigningCredential().getX509CertificateChain()));
545                 }
546
547                 //Sign the response, if appropriate
548                 if (relyingParty.getIdentityProvider().getResponseSigningCredential() != null
549                                 && relyingParty.getIdentityProvider().getResponseSigningCredential().getPrivateKey() != null) {
550
551                         String responseAlgorithm;
552                         if (relyingParty.getIdentityProvider().getResponseSigningCredential().getCredentialType() == Credential.RSA) {
553                                 responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
554                         } else if (relyingParty.getIdentityProvider().getResponseSigningCredential().getCredentialType() == Credential.DSA) {
555                                 responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
556                         } else {
557                                 throw new InvalidCryptoException(SAMLException.RESPONDER,
558                                                 "ShibPOSTProfile.prepare() currently only supports signing with RSA and DSA keys.");
559                         }
560
561                         reponse.sign(responseAlgorithm, relyingParty.getIdentityProvider().getResponseSigningCredential()
562                                         .getPrivateKey(), Arrays.asList(relyingParty.getIdentityProvider().getResponseSigningCredential()
563                                         .getX509CertificateChain()));
564                 }
565         }
566
567         public void sendFailure(HttpServletResponse httpResponse, SAMLRequest samlRequest, SAMLException exception)
568                         throws IOException {
569                 try {
570                         SAMLResponse samlResponse = new SAMLResponse((samlRequest != null) ? samlRequest.getId() : null, null,
571                                         null, exception);
572                         if (log.isDebugEnabled()) {
573                                 try {
574                                         log.debug("Dumping generated SAML Error Response:"
575                                                         + System.getProperty("line.separator")
576                                                         + new String(
577                                                                         new BASE64Decoder().decodeBuffer(new String(samlResponse.toBase64(), "ASCII")),
578                                                                         "UTF8"));
579                                 } catch (IOException e) {
580                                         log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
581                                 }
582                         }
583                         binding.respond(httpResponse, samlResponse, null);
584                         log.debug("Returning SAML Error Response.");
585                 } catch (SAMLException se) {
586                         binding.respond(httpResponse, null, exception);
587                         log.error("AA failed to make an error message: " + se);
588                 }
589         }
590
591         protected boolean isValidCredential(Provider provider, X509Certificate certificate) {
592
593                 ProviderRole[] roles = provider.getRoles();
594                 if (roles.length == 0) {
595                         log.info("Inappropriate metadata for provider.");
596                         return false;
597                 }
598
599                 for (int i = 0; roles.length > i; i++) {
600                         if (roles[i] instanceof AttributeConsumerRole) {
601                                 KeyDescriptor[] descriptors = roles[i].getKeyDescriptors();
602                                 for (int j = 0; descriptors.length > j; j++) {
603                                         KeyInfo[] keyInfo = descriptors[j].getKeyInfo();
604                                         for (int k = 0; keyInfo.length > k; k++) {
605                                                 for (int l = 0; keyInfo[k].lengthKeyName() > l; l++) {
606                                                         try {
607
608                                                                 //First, try to match DN against metadata
609                                                                 try {
610                                                                         if (certificate.getSubjectX500Principal().getName(X500Principal.RFC2253).equals(
611                                                                                         new X500Principal(keyInfo[k].itemKeyName(l).getKeyName())
612                                                                                                         .getName(X500Principal.RFC2253))) {
613                                                                                 log.debug("Matched against DN.");
614                                                                                 return true;
615                                                                         }
616                                                                 } catch (IllegalArgumentException iae) {
617                                                                         //squelch this runtime exception, since this might be a valid case
618                                                                 }
619
620                                                                 //If that doesn't work, we try matching against some Subject Alt Names
621                                                                 try {
622                                                                         Collection altNames = certificate.getSubjectAlternativeNames();
623                                                                         if (altNames != null) {
624                                                                                 for (Iterator nameIterator = altNames.iterator(); nameIterator.hasNext();) {
625                                                                                         List altName = (List) nameIterator.next();
626                                                                                         if (altName.get(0).equals(new Integer(2))
627                                                                                                         || altName.get(0).equals(new Integer(6))) { //2 is DNS, 6 is URI
628                                                                                                 if (altName.get(1).equals(keyInfo[k].itemKeyName(l).getKeyName())) {
629                                                                                                         log.debug("Matched against SubjectAltName.");
630                                                                                                         return true;
631                                                                                                 }
632                                                                                         }
633                                                                                 }
634                                                                         }
635                                                                 } catch (CertificateParsingException e1) {
636                                                                         log
637                                                                                         .error("Encountered an problem trying to extract Subject Alternate Name from supplied certificate: "
638                                                                                                         + e1);
639                                                                 }
640
641                                                                 //If that doesn't work, try to match using SSL-style hostname matching
642                                                                 if (ShibPOSTProfile.getHostNameFromDN(certificate.getSubjectX500Principal()).equals(
643                                                                                 keyInfo[k].itemKeyName(l).getKeyName())) {
644                                                                         log.debug("Matched against hostname.");
645                                                                         return true;
646                                                                 }
647
648                                                         } catch (XMLSecurityException e) {
649                                                                 log.error("Encountered an error reading federation metadata: " + e);
650                                                         }
651                                                 }
652                                         }
653                                 }
654                         }
655                 }
656                 log.info("Supplied credential not found in metadata.");
657                 return false;
658         }
659
660         protected boolean fromLegacyProvider(HttpServletRequest request) {
661                 String version = request.getHeader("Shibboleth");
662                 if (version != null) {
663                         log.debug("Request from Shibboleth version: " + version);
664                         return false;
665                 }
666                 log.debug("No version header found.");
667                 return true;
668         }
669
670         protected X509Certificate getCredentialFromProvider(HttpServletRequest req) {
671                 X509Certificate[] certArray = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
672                 if (certArray != null && certArray.length > 0) {
673                         return certArray[0];
674                 }
675                 return null;
676         }
677
678         class InvalidProviderCredentialException extends Exception {
679
680                 public InvalidProviderCredentialException(String message) {
681                         super(message);
682                 }
683         }
684
685 }