Move the signing throttling into IdPProtocolSupport so that it effects all signing...
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / IdPProtocolSupport.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.net.URI;
29 import java.net.URL;
30 import java.security.Principal;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Iterator;
34
35 import javax.servlet.http.HttpServletRequest;
36
37 import org.apache.log4j.Logger;
38 import org.apache.xml.security.signature.XMLSignature;
39 import org.opensaml.InvalidCryptoException;
40 import org.opensaml.SAMLAssertion;
41 import org.opensaml.SAMLAttribute;
42 import org.opensaml.SAMLException;
43 import org.opensaml.SAMLResponse;
44 import org.opensaml.artifact.Artifact;
45 import org.w3c.dom.Element;
46
47 import edu.internet2.middleware.shibboleth.aa.AAAttribute;
48 import edu.internet2.middleware.shibboleth.aa.AAAttributeSet;
49 import edu.internet2.middleware.shibboleth.aa.AAException;
50 import edu.internet2.middleware.shibboleth.aa.arp.ArpEngine;
51 import edu.internet2.middleware.shibboleth.aa.arp.ArpProcessingException;
52 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
53 import edu.internet2.middleware.shibboleth.artifact.ArtifactMapper;
54 import edu.internet2.middleware.shibboleth.artifact.provider.MemoryArtifactMapper;
55 import edu.internet2.middleware.shibboleth.common.Credential;
56 import edu.internet2.middleware.shibboleth.common.NameMapper;
57 import edu.internet2.middleware.shibboleth.common.RelyingParty;
58 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapper;
59 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
60 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
61 import edu.internet2.middleware.shibboleth.metadata.Metadata;
62 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
63
64 /**
65  * Delivers core IdP functionality (Attribute resolution, ARP filtering, Metadata lookup, Signing, Mapping between local &
66  * SAML identifiers, etc.) to components that process protocol-specific requests.
67  * 
68  * @author Walter Hoehn
69  */
70 public class IdPProtocolSupport implements Metadata {
71
72         private static Logger log = Logger.getLogger(IdPProtocolSupport.class.getName());
73         private Logger transactionLog;
74         private IdPConfig config;
75         private ArrayList fedMetadata = new ArrayList();
76         private NameMapper nameMapper;
77         private ServiceProviderMapper spMapper;
78         private ArpEngine arpEngine;
79         private AttributeResolver resolver;
80         private ArtifactMapper artifactMapper;
81         private Semaphore throttle;
82
83         IdPProtocolSupport(IdPConfig config, Logger transactionLog, NameMapper nameMapper, ServiceProviderMapper spMapper,
84                         ArpEngine arpEngine, AttributeResolver resolver) throws ShibbolethConfigurationException {
85
86                 this.transactionLog = transactionLog;
87                 this.config = config;
88                 this.nameMapper = nameMapper;
89                 this.spMapper = spMapper;
90                 spMapper.setMetadata(this);
91                 this.arpEngine = arpEngine;
92                 this.resolver = resolver;
93                 // TODO make this pluggable... and clean up memory impl
94                 artifactMapper = new MemoryArtifactMapper();
95
96                 // Load a semaphore that throttles how many requests the IdP will handle at once
97                 throttle = new Semaphore(config.getMaxThreads());
98         }
99
100         public static void validateEngineData(HttpServletRequest req) throws InvalidClientDataException {
101
102                 // TODO this should be pulled out into handlers
103
104                 if ((req.getRemoteAddr() == null) || (req.getRemoteAddr().equals(""))) { throw new InvalidClientDataException(
105                                 "Unable to obtain client address."); }
106         }
107
108         public Logger getTransactionLog() {
109
110                 return transactionLog;
111         }
112
113         public IdPConfig getIdPConfig() {
114
115                 return config;
116         }
117
118         public NameMapper getNameMapper() {
119
120                 return nameMapper;
121         }
122
123         public ServiceProviderMapper getServiceProviderMapper() {
124
125                 return spMapper;
126         }
127
128         public void signAssertions(SAMLAssertion[] assertions, RelyingParty relyingParty) throws InvalidCryptoException,
129                         SAMLException {
130
131                 if (relyingParty.getIdentityProvider().getSigningCredential() == null
132                                 || relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() == null) {
133                         // TODO error out
134                 }
135
136                 for (int i = 0; i < assertions.length; i++) {
137                         String assertionAlgorithm;
138                         if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
139                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
140                         } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
141                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
142                         } else {
143                                 throw new InvalidCryptoException(SAMLException.RESPONDER,
144                                                 "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
145                         }
146
147                         try {
148                                 throttle.enter();
149                                 assertions[i].sign(assertionAlgorithm, relyingParty.getIdentityProvider().getSigningCredential()
150                                                 .getPrivateKey(), Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential()
151                                                 .getX509CertificateChain()));
152                         } finally {
153                                 throttle.exit();
154                         }
155                 }
156         }
157
158         public void signResponse(SAMLResponse response, RelyingParty relyingParty) throws SAMLException {
159
160                 // Make sure we have an appropriate credential
161                 if (relyingParty.getIdentityProvider().getSigningCredential() == null
162                                 || relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() == null) {
163
164                         // TODO error
165                 }
166
167                 // Sign the response
168                 String responseAlgorithm;
169                 if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
170                         responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
171                 } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
172                         responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
173                 } else {
174                         throw new InvalidCryptoException(SAMLException.RESPONDER,
175                                         "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
176                 }
177                 try {
178                         throttle.enter();
179                         response.sign(responseAlgorithm, relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey(),
180                                         Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential().getX509CertificateChain()));
181                 } finally {
182                         throttle.exit();
183                 }
184         }
185
186         protected void addFederationProvider(Element element) {
187
188                 log.debug("Found Federation Provider configuration element.");
189                 if (!element.getTagName().equals("FederationProvider")) {
190                         log.error("Error while attemtping to load Federation Provider.  Malformed provider specificaion.");
191                         return;
192                 }
193
194                 try {
195                         fedMetadata.add(FederationProviderFactory.loadProvider(element));
196                 } catch (MetadataException e) {
197                         log.error("Unable to load Federation Provider.  Skipping...");
198                 }
199         }
200
201         public int providerCount() {
202
203                 return fedMetadata.size();
204         }
205
206         public EntityDescriptor lookup(String providerId) {
207
208                 Iterator iterator = fedMetadata.iterator();
209                 while (iterator.hasNext()) {
210                         EntityDescriptor provider = ((Metadata) iterator.next()).lookup(providerId);
211                         if (provider != null) { return provider; }
212                 }
213                 return null;
214         }
215
216         public EntityDescriptor lookup(Artifact artifact) {
217
218                 Iterator iterator = fedMetadata.iterator();
219                 while (iterator.hasNext()) {
220                         EntityDescriptor provider = ((Metadata) iterator.next()).lookup(artifact);
221                         if (provider != null) { return provider; }
222                 }
223                 return null;
224         }
225
226         public SAMLAttribute[] getReleaseAttributes(Principal principal, String requester, URL resource) throws AAException {
227
228                 try {
229                         URI[] potentialAttributes = arpEngine.listPossibleReleaseAttributes(principal, requester, resource);
230                         return getReleaseAttributes(principal, requester, resource, potentialAttributes);
231
232                 } catch (ArpProcessingException e) {
233                         log.error("An error occurred while processing the ARPs for principal (" + principal.getName() + ") :"
234                                         + e.getMessage());
235                         throw new AAException("Error retrieving data for principal.");
236                 }
237         }
238
239         public SAMLAttribute[] getReleaseAttributes(Principal principal, String requester, URL resource,
240                         URI[] attributeNames) throws AAException {
241
242                 try {
243                         AAAttributeSet attributeSet = new AAAttributeSet();
244                         for (int i = 0; i < attributeNames.length; i++) {
245                                 AAAttribute attribute = new AAAttribute(attributeNames[i].toString());
246                                 attributeSet.add(attribute);
247                         }
248
249                         return resolveAttributes(principal, requester, resource, attributeSet);
250
251                 } catch (SAMLException e) {
252                         log.error("An error occurred while creating attributes for principal (" + principal.getName() + ") :"
253                                         + e.getMessage());
254                         throw new AAException("Error retrieving data for principal.");
255
256                 } catch (ArpProcessingException e) {
257                         log.error("An error occurred while processing the ARPs for principal (" + principal.getName() + ") :"
258                                         + e.getMessage());
259                         throw new AAException("Error retrieving data for principal.");
260                 }
261         }
262
263         private SAMLAttribute[] resolveAttributes(Principal principal, String requester, URL resource,
264                         AAAttributeSet attributeSet) throws ArpProcessingException {
265
266                 resolver.resolveAttributes(principal, requester, attributeSet);
267                 arpEngine.filterAttributes(attributeSet, principal, requester, resource);
268                 return attributeSet.getAttributes();
269         }
270
271         /**
272          * Cleanup resources that won't be released when this object is garbage-collected
273          */
274         public void destroy() {
275
276                 resolver.destroy();
277                 arpEngine.destroy();
278         }
279
280         public ArtifactMapper getArtifactMapper() {
281
282                 return artifactMapper;
283         }
284
285         private class Semaphore {
286
287                 private int value;
288
289                 public Semaphore(int value) {
290
291                         this.value = value;
292                 }
293
294                 public synchronized void enter() {
295
296                         --value;
297                         if (value < 0) {
298                                 try {
299                                         wait();
300                                 } catch (InterruptedException e) {
301                                         // squelch and continue
302                                 }
303                         }
304                 }
305
306                 public synchronized void exit() {
307
308                         ++value;
309                         notify();
310                 }
311         }
312 }