45df27879b6a4724f7b750f4d8bc178937bfeadb
[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.X509Certificate;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collections;
37 import java.util.Date;
38 import java.util.Iterator;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41
42 import javax.servlet.ServletException;
43 import javax.servlet.UnavailableException;
44 import javax.servlet.http.HttpServletRequest;
45 import javax.servlet.http.HttpServletResponse;
46
47 import org.apache.log4j.Logger;
48 import org.apache.log4j.MDC;
49 import org.apache.xml.security.exceptions.XMLSecurityException;
50 import org.apache.xml.security.keys.KeyInfo;
51 import org.apache.xml.security.signature.XMLSignature;
52 import org.opensaml.InvalidCryptoException;
53 import org.opensaml.SAMLAssertion;
54 import org.opensaml.SAMLAttribute;
55 import org.opensaml.SAMLAttributeQuery;
56 import org.opensaml.SAMLAttributeStatement;
57 import org.opensaml.SAMLAudienceRestrictionCondition;
58 import org.opensaml.SAMLBinding;
59 import org.opensaml.SAMLCondition;
60 import org.opensaml.SAMLException;
61 import org.opensaml.SAMLIdentifier;
62 import org.opensaml.SAMLRequest;
63 import org.opensaml.SAMLResponse;
64 import org.opensaml.SAMLStatement;
65 import org.opensaml.SAMLSubject;
66 import org.w3c.dom.Document;
67 import org.w3c.dom.Element;
68 import org.w3c.dom.NodeList;
69
70 import sun.misc.BASE64Decoder;
71 import edu.internet2.middleware.shibboleth.aa.arp.ArpEngine;
72 import edu.internet2.middleware.shibboleth.aa.arp.ArpException;
73 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
74 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolverException;
75 import edu.internet2.middleware.shibboleth.common.AuthNPrincipal;
76 import edu.internet2.middleware.shibboleth.common.Credential;
77 import edu.internet2.middleware.shibboleth.common.Credentials;
78 import edu.internet2.middleware.shibboleth.common.InvalidNameIdentifierException;
79 import edu.internet2.middleware.shibboleth.common.NameIdentifierMapping;
80 import edu.internet2.middleware.shibboleth.common.NameIdentifierMappingException;
81 import edu.internet2.middleware.shibboleth.common.NameMapper;
82 import edu.internet2.middleware.shibboleth.common.OriginConfig;
83 import edu.internet2.middleware.shibboleth.common.RelyingParty;
84 import edu.internet2.middleware.shibboleth.common.SAMLBindingFactory;
85 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapperException;
86 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
87 import edu.internet2.middleware.shibboleth.common.ShibbolethOriginConfig;
88 import edu.internet2.middleware.shibboleth.common.TargetFederationComponent;
89 import edu.internet2.middleware.shibboleth.metadata.AttributeConsumerRole;
90 import edu.internet2.middleware.shibboleth.metadata.KeyDescriptor;
91 import edu.internet2.middleware.shibboleth.metadata.Provider;
92 import edu.internet2.middleware.shibboleth.metadata.ProviderRole;
93
94 /**
95  * @author Walter Hoehn
96  */
97
98 public class AAServlet extends TargetFederationComponent {
99
100         private AAConfig                                configuration;
101         protected AAResponder                   responder;
102         private NameMapper                              nameMapper;
103         private SAMLBinding                             binding;
104         private static Logger                   transactionLog  = Logger.getLogger("Shibboleth-TRANSACTION");
105         private AAServiceProviderMapper targetMapper;
106
107         private static Logger                   log                             = Logger.getLogger(AAServlet.class.getName());
108         protected Pattern                               regex                   = Pattern.compile(".*CN=([^,/]+).*");
109
110         public void init() throws ServletException {
111                 super.init();
112
113                 MDC.put("serviceId", "[AA] Core");
114                 log.info("Initializing Attribute Authority.");
115
116                 try {
117                         nameMapper = new NameMapper();
118                         loadConfiguration();
119
120                         binding = SAMLBindingFactory.getInstance(SAMLBinding.SAML_SOAP_HTTPS);
121
122                         log.info("Attribute Authority initialization complete.");
123
124                 } catch (ShibbolethConfigurationException ae) {
125                         log.fatal("The AA could not be initialized: " + ae);
126                         throw new UnavailableException("Attribute Authority failed to initialize.");
127                 } catch (SAMLException se) {
128                         log.fatal("SAML SOAP binding could not be loaded: " + se);
129                         throw new UnavailableException("Attribute Authority failed to initialize.");
130                 }
131         }
132
133         protected void loadConfiguration() throws ShibbolethConfigurationException {
134
135                 Document originConfig = OriginConfig.getOriginConfig(this.getServletContext());
136
137                 //Load global configuration properties
138                 configuration = new AAConfig(originConfig.getDocumentElement());
139
140                 //Load name mappings
141                 NodeList itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
142                                 NameIdentifierMapping.mappingNamespace, "NameMapping");
143
144                 for (int i = 0; i < itemElements.getLength(); i++) {
145                         try {
146                                 nameMapper.addNameMapping((Element) itemElements.item(i));
147                         } catch (NameIdentifierMappingException e) {
148                                 log.error("Name Identifier mapping could not be loaded: " + e);
149                         }
150                 }
151
152                 //Load signing credentials
153                 itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(Credentials.credentialsNamespace,
154                                 "Credentials");
155                 if (itemElements.getLength() < 1) {
156                         log.error("No credentials specified.");
157                 }
158                 if (itemElements.getLength() > 1) {
159                         log.error("Multiple Credentials specifications found, using first.");
160                 }
161                 Credentials credentials = new Credentials((Element) itemElements.item(0));
162
163                 //Load metadata
164                 itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
165                                 ShibbolethOriginConfig.originConfigNamespace, "FederationProvider");
166                 for (int i = 0; i < itemElements.getLength(); i++) {
167                         addFederationProvider((Element) itemElements.item(i));
168                 }
169                 if (providerCount() < 1) {
170                         log.error("No Federation Provider metadata loaded.");
171                         throw new ShibbolethConfigurationException("Could not load federation metadata.");
172                 }
173
174                 //Load relying party config
175                 try {
176                         targetMapper = new AAServiceProviderMapper(originConfig.getDocumentElement(), configuration, credentials,
177                                         this);
178                 } catch (ServiceProviderMapperException e) {
179                         log.error("Could not load origin configuration: " + e);
180                         throw new ShibbolethConfigurationException("Could not load origin configuration.");
181                 }
182
183                 try {
184                         //Startup Attribute Resolver
185                         AttributeResolver resolver = new AttributeResolver(configuration);
186
187                         //Startup ARP Engine
188                         ArpEngine arpEngine = null;
189                         itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
190                                         ShibbolethOriginConfig.originConfigNamespace, "ReleasePolicyEngine");
191
192                         if (itemElements.getLength() > 1) {
193                                 log.warn("Encountered multiple <ReleasePolicyEngine> configuration elements.  Using first...");
194                         }
195                         if (itemElements.getLength() < 1) {
196                                 arpEngine = new ArpEngine();
197                         } else {
198                                 arpEngine = new ArpEngine((Element) itemElements.item(0));
199                         }
200
201                         //Startup responder
202                         responder = new AAResponder(arpEngine, resolver);
203
204                 } catch (ArpException ae) {
205                         log.fatal("The AA could not be initialized due to a problem with the ARP Engine configuration: " + ae);
206                         throw new ShibbolethConfigurationException("Could not load ARP Engine.");
207                 } catch (AttributeResolverException ne) {
208                         log.fatal("The AA could not be initialized due to a problem with the Attribute Resolver configuration: "
209                                         + ne);
210                         throw new ShibbolethConfigurationException("Could not load Attribute Resolver.");
211                 }
212
213         }
214
215         public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
216
217                 MDC.put("serviceId", "[AA] " + new SAMLIdentifier().toString());
218                 MDC.put("remoteAddr", req.getRemoteAddr());
219                 log.info("Handling request.");
220
221                 AARelyingParty relyingParty = null;
222
223                 //Parse SOAP request
224                 SAMLRequest samlRequest = null;
225
226                 try {
227
228                         try {
229                                 samlRequest = binding.receive(req);
230
231                         } catch (SAMLException e) {
232                                 log.fatal("Unable to parse request: " + e);
233                                 throw new AAException("Invalid request data.");
234                         }
235
236                         if (samlRequest.getQuery() == null || !(samlRequest.getQuery() instanceof SAMLAttributeQuery)) {
237                                 throw new SAMLException(SAMLException.REQUESTER,
238                                                 "This SAML authority only responds to attribute queries.");
239                         }
240                         SAMLAttributeQuery attributeQuery = (SAMLAttributeQuery) samlRequest.getQuery();
241
242                         if (!fromLegacyProvider(req)) {
243                                 log.info("Remote provider has identified itself as: (" + attributeQuery.getResource() + ").");
244                         }
245
246                         //This is the requester name that will be passed to subsystems
247                         String effectiveName = null;
248
249                         String credentialName = getCredentialName(req);
250                         if (credentialName == null || credentialName.toString().equals("")) {
251                                 log.info("Request is from an unauthenticated service provider.");
252                         } else {
253
254                                 //Identify a Relying Party
255                                 relyingParty = targetMapper.getRelyingParty(attributeQuery.getResource());
256
257                                 try {
258                                         effectiveName = getEffectiveName(req, relyingParty);
259                                 } catch (InvalidProviderCredentialException ipc) {
260                                         sendFailure(resp, samlRequest, new SAMLException(SAMLException.RESPONDER,
261                                                         "Invalid credentials for request."));
262                                         return;
263                                 }
264                         }
265
266                         if (effectiveName == null) {
267                                 log.debug("Using default Relying Party for unauthenticated provider.");
268                                 relyingParty = targetMapper.getRelyingParty(null);
269                         }
270
271                         //Fail if we can't honor SAML Subject Confirmation
272                         if (!fromLegacyProvider(req)) {
273                                 Iterator iterator = attributeQuery.getSubject().getConfirmationMethods();
274                                 boolean hasConfirmationMethod = false;
275                                 while (iterator.hasNext()) {
276                                         log.info("Request contains SAML Subject Confirmation method: (" + (String) iterator.next() + ").");
277                                 }
278                                 if (hasConfirmationMethod) {
279                                         throw new SAMLException(SAMLException.REQUESTER,
280                                                         "This SAML authority cannot honor requests containing the supplied SAML Subject Confirmation Method.");
281                                 }
282                         }
283
284                         //Map Subject to local principal
285                         if (relyingParty.getIdentityProvider().getProviderId() != null
286                                         && !relyingParty.getIdentityProvider().getProviderId().equals(
287                                                         attributeQuery.getSubject().getName().getNameQualifier())) {
288                                 log.error("The name qualifier (" + attributeQuery.getSubject().getName().getNameQualifier()
289                                                 + ") for the referenced subject is not valid for this identiy provider.");
290                                 throw new NameIdentifierMappingException("The name qualifier ("
291                                                 + attributeQuery.getSubject().getName().getNameQualifier()
292                                                 + ") for the referenced subject is not valid for this identiy provider.");
293                         }
294
295                         Principal principal = null;
296                         try {
297                                 // for testing
298                                 if (attributeQuery.getSubject().getName().getFormat().equals("urn:mace:shibboleth:test:nameIdentifier")) {
299                                         principal = new AuthNPrincipal("test-handle");
300                                 } else {
301                                         principal = nameMapper.getPrincipal(attributeQuery.getSubject().getName(), relyingParty,
302                                                         relyingParty.getIdentityProvider());
303                                 }
304                                 log.info("Request is for principal (" + principal.getName() + ").");
305
306                         } catch (InvalidNameIdentifierException invalidNameE) {
307                                 log.info("Could not associate the request subject with a principal: " + invalidNameE);
308                                 try {
309                                         if (relyingParty.passThruErrors()) {
310                                                 sendFailure(resp, samlRequest, new SAMLException(Arrays
311                                                                 .asList(invalidNameE.getSAMLErrorCodes()), "The supplied Subject was unrecognized.",
312                                                                 invalidNameE));
313
314                                         } else {
315                                                 sendFailure(resp, samlRequest, new SAMLException(Arrays
316                                                                 .asList(invalidNameE.getSAMLErrorCodes()), "The supplied Subject was unrecognized."));
317                                         }
318                                         return;
319                                 } catch (Exception ee) {
320                                         log.fatal("Could not construct a SAML error response: " + ee);
321                                         throw new ServletException("Attribute Authority response failure.");
322                                 }
323                         }
324
325                         SAMLAttribute[] attrs;
326                         Iterator requestedAttrsIterator = attributeQuery.getDesignators();
327                         if (requestedAttrsIterator.hasNext()) {
328                                 log.info("Request designates specific attributes, resolving this set.");
329                                 ArrayList requestedAttrs = new ArrayList();
330                                 while (requestedAttrsIterator.hasNext()) {
331                                         SAMLAttribute attribute = (SAMLAttribute) requestedAttrsIterator.next();
332                                         try {
333                                                 log.debug("Designated attribute: (" + attribute.getName() + ")");
334                                                 requestedAttrs.add(new URI(attribute.getName()));
335                                         } catch (URISyntaxException use) {
336                                                 log
337                                                                 .error("Request designated an attribute name that does not conform to the required URI syntax ("
338                                                                                 + attribute.getName() + ").  Ignoring this attribute");
339                                         }
340                                 }
341
342                                 attrs = responder.getReleaseAttributes(principal, effectiveName, null, (URI[]) requestedAttrs
343                                                 .toArray(new URI[0]));
344                         } else {
345                                 log.info("Request does not designate specific attributes, resolving all available.");
346                                 attrs = responder.getReleaseAttributes(principal, effectiveName, null);
347                         }
348
349                         log.info("Found " + attrs.length + " attribute(s) for " + principal.getName());
350                         sendResponse(resp, attrs, samlRequest, relyingParty, null);
351                         log.info("Successfully responded about " + principal.getName());
352
353                         if (effectiveName == null) {
354                                 if (fromLegacyProvider(req)) {
355                                         transactionLog.info("Attribute assertion issued to anonymous legacy provider at ("
356                                                         + req.getRemoteAddr() + ") on behalf of principal (" + principal.getName() + ").");
357                                 } else {
358                                         transactionLog.info("Attribute assertion issued to anonymous provider at (" + req.getRemoteAddr()
359                                                         + ") on behalf of principal (" + principal.getName() + ").");
360                                 }
361                         } else {
362                                 if (fromLegacyProvider(req)) {
363                                         transactionLog.info("Attribute assertion issued to legacy provider (" + effectiveName
364                                                         + ") on behalf of principal (" + principal.getName() + ").");
365                                 } else {
366                                         transactionLog.info("Attribute assertion issued to provider (" + effectiveName
367                                                         + ") on behalf of principal (" + principal.getName() + ").");
368                                 }
369                         }
370
371                 } catch (Exception e) {
372                         log.error("Error while processing request: " + e);
373                         try {
374                                 if (relyingParty != null && relyingParty.passThruErrors()) {
375                                         sendFailure(resp, samlRequest, new SAMLException(SAMLException.RESPONDER,
376                                                         "General error processing request.", e));
377                                 } else if (configuration.passThruErrors()) {
378                                         sendFailure(resp, samlRequest, new SAMLException(SAMLException.RESPONDER,
379                                                         "General error processing request.", e));
380                                 } else {
381                                         sendFailure(resp, samlRequest, new SAMLException(SAMLException.RESPONDER,
382                                                         "General error processing request."));
383                                 }
384                                 return;
385                         } catch (Exception ee) {
386                                 log.fatal("Could not construct a SAML error response: " + ee);
387                                 throw new ServletException("Attribute Authority response failure.");
388                         }
389
390                 }
391         }
392
393         protected String getEffectiveName(HttpServletRequest req, AARelyingParty relyingParty)
394                         throws InvalidProviderCredentialException {
395
396                 String credentialName = getCredentialName(req);
397                 if (credentialName == null || credentialName.toString().equals("")) {
398                         log.info("Request is from an unauthenticated service provider.");
399                         return null;
400
401                 } else {
402                         log.info("Request contains credential: (" + credentialName + ").");
403                         //Mockup old requester name for requests from < 1.2 targets
404                         if (fromLegacyProvider(req)) {
405                                 String legacyName = getLegacyRequesterName(credentialName);
406                                 log.info("Request from legacy service provider: (" + legacyName + ").");
407                                 return legacyName;
408
409                         } else {
410
411                                 //See if we have metadata for this provider
412                                 Provider provider = lookup(relyingParty.getProviderId());
413                                 if (provider == null) {
414                                         log.info("No metadata found for provider: (" + relyingParty.getProviderId() + ").");
415                                         log.info("Treating remote provider as unauthenticated.");
416                                         return null;
417                                 }
418
419                                 //Make sure that the suppplied credential is valid for the selected relying party
420                                 if (isValidCredential(provider, credentialName.toString())) {
421                                         log.info("Supplied credential validated for this provider.");
422                                         log.info("Request from service provider: (" + relyingParty.getProviderId() + ").");
423                                         return relyingParty.getProviderId();
424                                 } else {
425                                         log.error("Supplied credential (" + credentialName.toString() + ") is NOT valid for provider ("
426                                                         + relyingParty.getProviderId() + ").");
427                                         throw new InvalidProviderCredentialException("Invalid credential.");
428                                 }
429                         }
430                 }
431         }
432
433         private String getLegacyRequesterName(String credentialName) {
434                 Matcher matches = regex.matcher(credentialName);
435                 if (!matches.find() || matches.groupCount() > 1) {
436                         log.error("Unable to extract legacy requester name from certificate subject.");
437                         return null;
438                 }
439                 return matches.group(1);
440         }
441
442         public void destroy() {
443                 log.info("Cleaning up resources.");
444                 responder.destroy();
445                 nameMapper.destroy();
446         }
447
448         public void sendResponse(HttpServletResponse resp, SAMLAttribute[] attrs, SAMLRequest samlRequest,
449                         RelyingParty relyingParty, SAMLException exception) throws IOException {
450
451                 SAMLException ourSE = null;
452                 SAMLResponse samlResponse = null;
453
454                 try {
455                         if (attrs == null || attrs.length == 0) {
456                                 //No attribute found
457                                 samlResponse = new SAMLResponse(samlRequest.getId(), null, null, exception);
458                         } else {
459
460                                 if (samlRequest.getQuery() == null || !(samlRequest.getQuery() instanceof SAMLAttributeQuery)) {
461                                         throw new SAMLException(SAMLException.REQUESTER,
462                                                         "This SAML authority only responds to attribute queries");
463                                 }
464                                 SAMLAttributeQuery attributeQuery = (SAMLAttributeQuery) samlRequest.getQuery();
465
466                                 //Reference requested subject
467                                 SAMLSubject rSubject = (SAMLSubject) attributeQuery.getSubject().clone();
468
469                                 //Set appropriate audience
470                                 ArrayList audiences = new ArrayList();
471                                 if (relyingParty.getProviderId() != null) {
472                                         audiences.add(relyingParty.getProviderId());
473                                 }
474                                 if (relyingParty.getName() != null && !relyingParty.getName().equals(relyingParty.getProviderId())) {
475                                         audiences.add(relyingParty.getName());
476                                 }
477                                 SAMLCondition condition = new SAMLAudienceRestrictionCondition(audiences);
478
479                                 //Put all attributes into an assertion
480                                 SAMLStatement statement = new SAMLAttributeStatement(rSubject, Arrays.asList(attrs));
481
482                                 //Set assertion expiration to longest attribute expiration
483                                 long max = 0;
484                                 for (int i = 0; i < attrs.length; i++) {
485                                         if (max < attrs[i].getLifetime()) {
486                                                 max = attrs[i].getLifetime();
487                                         }
488                                 }
489                                 Date now = new Date();
490                                 Date then = new Date(now.getTime() + (max * 1000)); //max is in seconds
491
492                                 SAMLAssertion sAssertion = new SAMLAssertion(relyingParty.getIdentityProvider().getProviderId(), now,
493                                                 then, Collections.singleton(condition), null, Collections.singleton(statement));
494
495                                 samlResponse = new SAMLResponse(samlRequest.getId(), null, Collections.singleton(sAssertion), exception);
496                                 addSignatures(samlResponse, relyingParty);
497                         }
498                 } catch (SAMLException se) {
499                         ourSE = se;
500                 } catch (CloneNotSupportedException ex) {
501                         ourSE = new SAMLException(SAMLException.RESPONDER, ex);
502
503                 } finally {
504
505                         if (log.isDebugEnabled()) {
506                                 try {
507                                         log.debug("Dumping generated SAML Response:"
508                                                         + System.getProperty("line.separator")
509                                                         + new String(
510                                                                         new BASE64Decoder().decodeBuffer(new String(samlResponse.toBase64(), "ASCII")),
511                                                                         "UTF8"));
512                                 } catch (SAMLException e) {
513                                         log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
514                                 } catch (IOException e) {
515                                         log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
516                                 }
517                         }
518
519                         binding.respond(resp, samlResponse, ourSE);
520                 }
521         }
522
523         private void addSignatures(SAMLResponse reponse, RelyingParty relyingParty) throws SAMLException {
524
525                 //Sign the assertions, if appropriate
526                 if (relyingParty.getIdentityProvider().getAssertionSigningCredential() != null
527                                 && relyingParty.getIdentityProvider().getAssertionSigningCredential().getPrivateKey() != null) {
528
529                         String assertionAlgorithm;
530                         if (relyingParty.getIdentityProvider().getAssertionSigningCredential().getCredentialType() == Credential.RSA) {
531                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
532                         } else if (relyingParty.getIdentityProvider().getAssertionSigningCredential().getCredentialType() == Credential.DSA) {
533                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
534                         } else {
535                                 throw new InvalidCryptoException(SAMLException.RESPONDER,
536                                                 "ShibPOSTProfile.prepare() currently only supports signing with RSA and DSA keys.");
537                         }
538
539                         ((SAMLAssertion) reponse.getAssertions().next()).sign(assertionAlgorithm, relyingParty
540                                         .getIdentityProvider().getAssertionSigningCredential().getPrivateKey(), Arrays.asList(relyingParty
541                                         .getIdentityProvider().getAssertionSigningCredential().getX509CertificateChain()));
542                 }
543
544                 //Sign the response, if appropriate
545                 if (relyingParty.getIdentityProvider().getResponseSigningCredential() != null
546                                 && relyingParty.getIdentityProvider().getResponseSigningCredential().getPrivateKey() != null) {
547
548                         String responseAlgorithm;
549                         if (relyingParty.getIdentityProvider().getResponseSigningCredential().getCredentialType() == Credential.RSA) {
550                                 responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
551                         } else if (relyingParty.getIdentityProvider().getResponseSigningCredential().getCredentialType() == Credential.DSA) {
552                                 responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
553                         } else {
554                                 throw new InvalidCryptoException(SAMLException.RESPONDER,
555                                                 "ShibPOSTProfile.prepare() currently only supports signing with RSA and DSA keys.");
556                         }
557
558                         reponse.sign(responseAlgorithm, relyingParty.getIdentityProvider().getResponseSigningCredential()
559                                         .getPrivateKey(), Arrays.asList(relyingParty.getIdentityProvider().getResponseSigningCredential()
560                                         .getX509CertificateChain()));
561                 }
562         }
563
564         public void sendFailure(HttpServletResponse httpResponse, SAMLRequest samlRequest, SAMLException exception)
565                         throws IOException {
566                 try {
567                         SAMLResponse samlResponse = new SAMLResponse((samlRequest != null) ? samlRequest.getId() : null, null,
568                                         null, exception);
569                         if (log.isDebugEnabled()) {
570                                 try {
571                                         log.debug("Dumping generated SAML Error Response:"
572                                                         + System.getProperty("line.separator")
573                                                         + new String(
574                                                                         new BASE64Decoder().decodeBuffer(new String(samlResponse.toBase64(), "ASCII")),
575                                                                         "UTF8"));
576                                 } catch (IOException e) {
577                                         log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
578                                 }
579                         }
580                         binding.respond(httpResponse, samlResponse, null);
581                         log.debug("Returning SAML Error Response.");
582                 } catch (SAMLException se) {
583                         binding.respond(httpResponse, null, exception);
584                         log.error("AA failed to make an error message: " + se);
585                 }
586         }
587
588         protected boolean isValidCredential(Provider provider, String credentialName) {
589
590                 ProviderRole[] roles = provider.getRoles();
591                 if (roles.length == 0) {
592                         log.info("Inappropriate metadata for provider.");
593                         return false;
594                 }
595
596                 for (int i = 0; roles.length > i; i++) {
597                         if (roles[i] instanceof AttributeConsumerRole) {
598                                 KeyDescriptor[] descriptors = roles[i].getKeyDescriptors();
599                                 for (int j = 0; descriptors.length > j; j++) {
600                                         KeyInfo[] keyInfo = descriptors[j].getKeyInfo();
601                                         for (int k = 0; keyInfo.length > k; k++) {
602                                                 for (int l = 0; keyInfo[k].lengthKeyName() > l; l++) {
603                                                         try {
604                                                                 if (credentialName.equals(keyInfo[k].itemKeyName(l).getKeyName())) {
605                                                                         return true;
606                                                                 }
607                                                         } catch (XMLSecurityException e) {
608                                                                 log.error("Encountered an error reading federation metadata: " + e);
609                                                         }
610                                                 }
611                                         }
612                                 }
613                         }
614                 }
615                 log.info("Supplied credential not found in metadata.");
616                 return false;
617         }
618
619         protected boolean fromLegacyProvider(HttpServletRequest request) {
620                 String version = request.getHeader("Shibboleth");
621                 if (version != null) {
622                         log.debug("Request from Shibboleth version: " + version);
623                         return false;
624                 }
625                 log.debug("No version header found.");
626                 return true;
627         }
628
629         protected String getCredentialName(HttpServletRequest req) {
630                 X509Certificate[] certArray = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
631                 if (certArray != null && certArray.length > 0) {
632                         return certArray[0].getSubjectDN().getName();
633                 }
634                 return null;
635         }
636
637         class InvalidProviderCredentialException extends Exception {
638
639                 public InvalidProviderCredentialException(String message) {
640                         super(message);
641                 }
642         }
643
644 }