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