Began the move to OpenSAML2-based trust.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / IdPProtocolSupport.java
1 /*
2  * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.] Licensed under the Apache License,
3  * Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy
4  * of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in
5  * writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
6  * OF ANY KIND, either express or implied. See the License for the specific language governing permissions and
7  * limitations under the License.
8  */
9
10 package edu.internet2.middleware.shibboleth.idp;
11
12 import java.net.URI;
13 import java.security.Principal;
14 import java.util.Arrays;
15 import java.util.Collection;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Map;
19
20 import javax.xml.namespace.QName;
21
22 import org.apache.log4j.Logger;
23 import org.apache.xml.security.signature.XMLSignature;
24 import org.opensaml.InvalidCryptoException;
25 import org.opensaml.SAMLAssertion;
26 import org.opensaml.SAMLAttribute;
27 import org.opensaml.SAMLException;
28 import org.opensaml.SAMLResponse;
29 import org.opensaml.saml2.metadata.EntitiesDescriptor;
30 import org.opensaml.saml2.metadata.EntityDescriptor;
31 import org.opensaml.saml2.metadata.RoleDescriptor;
32 import org.opensaml.saml2.metadata.provider.ChainingMetadataProvider;
33 import org.opensaml.saml2.metadata.provider.MetadataFilter;
34 import org.opensaml.saml2.metadata.provider.MetadataProvider;
35 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
36 import org.opensaml.security.TrustEngine;
37 import org.opensaml.security.X509EntityCredential;
38 import org.opensaml.xml.XMLObject;
39 import org.w3c.dom.Element;
40
41 import edu.internet2.middleware.shibboleth.aa.AAAttribute;
42 import edu.internet2.middleware.shibboleth.aa.AAException;
43 import edu.internet2.middleware.shibboleth.aa.arp.ArpEngine;
44 import edu.internet2.middleware.shibboleth.aa.arp.ArpProcessingException;
45 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
46 import edu.internet2.middleware.shibboleth.artifact.ArtifactMapper;
47 import edu.internet2.middleware.shibboleth.common.Credential;
48 import edu.internet2.middleware.shibboleth.common.NameMapper;
49 import edu.internet2.middleware.shibboleth.common.RelyingParty;
50 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapper;
51 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
52 import edu.internet2.middleware.shibboleth.common.provider.ShibbolethTrustEngine;
53 import edu.internet2.middleware.shibboleth.metadata.MetadataProviderFactory;
54
55 /**
56  * Delivers core IdP functionality (Attribute resolution, ARP filtering, Metadata lookup, Signing, Mapping between local &
57  * SAML identifiers, etc.) to components that process protocol-specific requests.
58  * 
59  * @author Walter Hoehn
60  */
61 public class IdPProtocolSupport implements MetadataProvider {
62
63         private static Logger log = Logger.getLogger(IdPProtocolSupport.class.getName());
64         private Logger transactionLog;
65         private IdPConfig config;
66         private NameMapper nameMapper;
67         private ServiceProviderMapper spMapper;
68         private ArpEngine arpEngine;
69         private AttributeResolver resolver;
70         private ArtifactMapper artifactMapper;
71         private Semaphore throttle;
72         private TrustEngine<X509EntityCredential> trust = new ShibbolethTrustEngine();
73         private ChainingMetadataProvider wrappedMetadataProvider = new ChainingMetadataProvider();
74
75         IdPProtocolSupport(IdPConfig config, Logger transactionLog, NameMapper nameMapper, ServiceProviderMapper spMapper,
76                         ArpEngine arpEngine, AttributeResolver resolver, ArtifactMapper artifactMapper)
77                         throws ShibbolethConfigurationException {
78
79                 this.transactionLog = transactionLog;
80                 this.config = config;
81                 this.nameMapper = nameMapper;
82                 this.spMapper = spMapper;
83                 spMapper.setMetadata(this);
84                 this.arpEngine = arpEngine;
85                 this.resolver = resolver;
86                 this.artifactMapper = artifactMapper;
87
88                 // Load a semaphore that throttles how many requests the IdP will handle at once
89                 throttle = new Semaphore(config.getMaxThreads());
90         }
91
92         public Logger getTransactionLog() {
93
94                 return transactionLog;
95         }
96
97         public IdPConfig getIdPConfig() {
98
99                 return config;
100         }
101
102         public NameMapper getNameMapper() {
103
104                 return nameMapper;
105         }
106
107         public ServiceProviderMapper getServiceProviderMapper() {
108
109                 return spMapper;
110         }
111
112         public void signAssertions(SAMLAssertion[] assertions, RelyingParty relyingParty) throws InvalidCryptoException,
113                         SAMLException {
114
115                 if (relyingParty.getIdentityProvider().getSigningCredential() == null
116                                 || relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() == null) { throw new InvalidCryptoException(
117                                 SAMLException.RESPONDER, "Invalid signing credential."); }
118
119                 for (int i = 0; i < assertions.length; i++) {
120                         String assertionAlgorithm;
121                         if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
122                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
123                         } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
124                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
125                         } else {
126                                 throw new InvalidCryptoException(SAMLException.RESPONDER,
127                                                 "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
128                         }
129
130                         try {
131                                 throttle.enter();
132                                 assertions[i].sign(assertionAlgorithm, relyingParty.getIdentityProvider().getSigningCredential()
133                                                 .getPrivateKey(), Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential()
134                                                 .getX509CertificateChain()));
135                         } finally {
136                                 throttle.exit();
137                         }
138                 }
139         }
140
141         public void signResponse(SAMLResponse response, RelyingParty relyingParty) throws SAMLException {
142
143                 // Make sure we have an appropriate credential
144                 if (relyingParty.getIdentityProvider().getSigningCredential() == null
145                                 || relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() == null) { throw new InvalidCryptoException(
146                                 SAMLException.RESPONDER, "Invalid signing credential."); }
147
148                 // Sign the response
149                 String responseAlgorithm;
150                 if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
151                         responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
152                 } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
153                         responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
154                 } else {
155                         throw new InvalidCryptoException(SAMLException.RESPONDER,
156                                         "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
157                 }
158                 try {
159                         throttle.enter();
160                         response.sign(responseAlgorithm, relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey(),
161                                         Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential().getX509CertificateChain()));
162                 } finally {
163                         throttle.exit();
164                 }
165         }
166
167         protected void addMetadataProvider(Element element) {
168
169                 log.debug("Found Metadata Provider configuration element.");
170                 if (!element.getTagName().equals("MetadataProvider")) {
171                         log.error("Error while attemtping to load Metadata Provider.  Malformed provider specificaion.");
172                         return;
173                 }
174
175                 try {
176                         wrappedMetadataProvider.addMetadataProvider(MetadataProviderFactory.loadProvider(element));
177                 } catch (MetadataProviderException e) {
178                         log.error("Unable to load Metadata Provider.  Skipping...");
179                 }
180
181         }
182
183         public Collection<? extends SAMLAttribute> getReleaseAttributes(Principal principal, RelyingParty relyingParty,
184                         String requester) throws AAException {
185
186                 try {
187                         Collection<URI> potentialAttributes = arpEngine.listPossibleReleaseAttributes(principal, requester);
188                         return getReleaseAttributes(principal, relyingParty, requester, potentialAttributes);
189
190                 } catch (ArpProcessingException e) {
191                         log.error("An error occurred while processing the ARPs for principal (" + principal.getName() + ") :"
192                                         + e.getMessage());
193                         throw new AAException("Error retrieving data for principal.");
194                 }
195         }
196
197         public Collection<? extends SAMLAttribute> getReleaseAttributes(Principal principal, RelyingParty relyingParty,
198                         String requester, Collection<URI> attributeNames) throws AAException {
199
200                 try {
201                         Map<String, AAAttribute> attributes = new HashMap<String, AAAttribute>();
202                         for (URI name : attributeNames) {
203
204                                 AAAttribute attribute = null;
205                                 if (relyingParty.wantsSchemaHack()) {
206                                         attribute = new AAAttribute(name.toString(), true);
207                                 } else {
208                                         attribute = new AAAttribute(name.toString(), false);
209                                 }
210                                 attributes.put(attribute.getName(), attribute);
211                         }
212
213                         Collection<URI> constraintAttributes = arpEngine.listRequiredConstraintAttributes(principal, requester,
214                                         attributeNames);
215                         for (URI name : constraintAttributes) {
216                                 if (!attributes.containsKey(name.toString())) {
217                                         // don't care about schema hack since these attributes won't be returned to SP
218                                         AAAttribute attribute = new AAAttribute(name.toString(), false);
219                                         attributes.put(attribute.getName(), attribute);
220                                 }
221                         }
222
223                         return resolveAttributes(principal, requester, relyingParty.getIdentityProvider().getProviderId(),
224                                         attributes);
225
226                 } catch (SAMLException e) {
227                         log.error("An error occurred while creating attributes for principal (" + principal.getName() + ") :"
228                                         + e.getMessage());
229                         throw new AAException("Error retrieving data for principal.");
230
231                 } catch (ArpProcessingException e) {
232                         log.error("An error occurred while processing the ARPs for principal (" + principal.getName() + ") :"
233                                         + e.getMessage());
234                         throw new AAException("Error retrieving data for principal.");
235                 }
236         }
237
238         public Collection<? extends SAMLAttribute> resolveAttributes(Principal principal, String requester,
239                         String responder, Map<String, AAAttribute> attributeSet) throws ArpProcessingException {
240
241                 resolver.resolveAttributes(principal, requester, responder, attributeSet);
242                 arpEngine.filterAttributes(attributeSet.values(), principal, requester);
243                 return attributeSet.values();
244         }
245
246         public Collection<? extends SAMLAttribute> resolveAttributesNoPolicies(Principal principal, String requester,
247                         String responder, Map<String, AAAttribute> attributeSet) {
248
249                 resolver.resolveAttributes(principal, requester, responder, attributeSet);
250                 return attributeSet.values();
251         }
252
253         /**
254          * Cleanup resources that won't be released when this object is garbage-collected
255          */
256         public void destroy() {
257
258                 resolver.destroy();
259                 arpEngine.destroy();
260         }
261
262         public ArtifactMapper getArtifactMapper() {
263
264                 return artifactMapper;
265         }
266
267         public TrustEngine<X509EntityCredential> getTrust() {
268
269                 return trust;
270         }
271
272         public boolean requireValidMetadata() {
273
274                 return wrappedMetadataProvider.requireValidMetadata();
275         }
276
277         public void setRequireValidMetadata(boolean requireValidMetadata) {
278
279                 wrappedMetadataProvider.setRequireValidMetadata(requireValidMetadata);
280         }
281
282         public MetadataFilter getMetadataFilter() {
283
284                 return wrappedMetadataProvider.getMetadataFilter();
285         }
286
287         public void setMetadataFilter(MetadataFilter newFilter) throws MetadataProviderException {
288
289                 wrappedMetadataProvider.setMetadataFilter(newFilter);
290         }
291
292         public XMLObject getMetadata() throws MetadataProviderException {
293
294                 return wrappedMetadataProvider.getMetadata();
295         }
296
297         public EntitiesDescriptor getEntitiesDescriptor(String name) throws MetadataProviderException {
298
299                 return wrappedMetadataProvider.getEntitiesDescriptor(name);
300         }
301
302         public EntityDescriptor getEntityDescriptor(String entityID) throws MetadataProviderException {
303
304                 return wrappedMetadataProvider.getEntityDescriptor(entityID);
305         }
306
307         public List<RoleDescriptor> getRole(String entityID, QName roleName) throws MetadataProviderException {
308
309                 return wrappedMetadataProvider.getRole(entityID, roleName);
310         }
311
312         public RoleDescriptor getRole(String entityID, QName roleName, String supportedProtocol)
313                         throws MetadataProviderException {
314
315                 return wrappedMetadataProvider.getRole(entityID, roleName, supportedProtocol);
316         }
317
318         public int providerCount() {
319
320                 return wrappedMetadataProvider.getProviders().size();
321         }
322
323         private class Semaphore {
324
325                 private int value;
326
327                 public Semaphore(int value) {
328
329                         this.value = value;
330                 }
331
332                 public synchronized void enter() {
333
334                         --value;
335                         if (value < 0) {
336                                 try {
337                                         wait();
338                                 } catch (InterruptedException e) {
339                                         // squelch and continue
340                                 }
341                         }
342                 }
343
344                 public synchronized void exit() {
345
346                         ++value;
347                         notify();
348                 }
349         }
350
351 }