cb5f0971bf43def7d544a68bde1da4aba6d0d71c
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / common / ShibBrowserProfile.java
1 /*
2  * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package edu.internet2.middleware.shibboleth.common;
18
19 import java.security.GeneralSecurityException;
20 import java.security.Key;
21 import java.security.KeyStore;
22 import java.security.cert.CertPathBuilder;
23 import java.security.cert.CertPathBuilderException;
24 import java.security.cert.CertStore;
25 import java.security.cert.CollectionCertStoreParameters;
26 import java.security.cert.PKIXBuilderParameters;
27 import java.security.cert.PKIXCertPathBuilderResult;
28 import java.security.cert.X509CertSelector;
29 import java.security.cert.X509Certificate;
30 import java.util.ArrayList;
31 import java.util.Iterator;
32 import java.util.Vector;
33
34 import javax.servlet.http.HttpServletRequest;
35
36 import org.apache.log4j.Logger;
37 import org.apache.log4j.NDC;
38 import org.apache.xml.security.signature.XMLSignature;
39 import org.opensaml.NoSuchProviderException;
40 import org.opensaml.ReplayCache;
41 import org.opensaml.SAMLBrowserProfile;
42 import org.opensaml.SAMLBrowserProfileFactory;
43 import org.opensaml.SAMLException;
44 import org.opensaml.SAMLSignedObject;
45 import org.opensaml.TrustException;
46
47 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
48 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
49 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderConfig;
50 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderContext;
51 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderConfig.ApplicationInfo;
52
53 // TODO: Suggest we implement a separation layer between the SP config pieces and the input needed
54 // for this class. As long as metadata/etc. are shared, this should work.
55
56 /**
57  * Basic Shibboleth POST browser profile implementation with basic support for signing
58  * 
59  * @author Scott Cantor @created April 11, 2002
60  */
61 public class ShibBrowserProfile implements SAMLBrowserProfile {
62
63
64
65         /** XML Signature algorithm to apply */
66         protected String                algorithm       = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
67
68         private static Logger   log                     = Logger.getLogger(ShibBrowserProfile.class.getName());
69
70     /** Policy URIs to attach or check against */
71     protected ArrayList     policies    = new ArrayList();
72
73     protected SAMLBrowserProfile profile = SAMLBrowserProfileFactory.getInstance(); 
74     private static ServiceProviderContext context = ServiceProviderContext.getInstance();
75
76     /*
77      * The C++ class is constructed by passing enumerations of Metadata
78      * providers, trust providers, etc from the <Application>. However,
79      * those providers can change dynamically. This version only keeps
80      * the applicationId that can be used to fetch the ApplicationInfo 
81      * object and, from it, get the collections of provider plugins.
82      * 
83      * TODO: The reason they were still dynamic in C++ was that this wrapper
84      * object was built dynamically. It's now contained within the application
85      * interface itself and so it's "scoped" within the application and shares
86      * the set of plugins from it. One reloads, the other is rebuilt.
87      */
88     private String applicationId = null;
89     
90     /**
91      * Identify the <Application> from which to get plugins.
92      * 
93      * @param applicationId 
94      */
95     public ShibBrowserProfile(String applicationId) throws NoSuchProviderException {
96         this.applicationId = applicationId;
97     }
98
99         /**
100      * Given a key from Trust associated with a HS Role from a Metadata Entity Descriptor,
101      * verify the SAML Signature.
102      * 
103      * TODO: Replace this with calls into pluggable Trust provider
104      * 
105      * @param obj           A signed SAMLObject
106      * @param signerName    The signer's ID
107      * @param ks            KeyStore [TrustProvider abstraction violation, may change]
108      * @param knownKey      Key from the Trust entry associated with the signer's Metadata
109      * @throws SAMLException
110      */
111     static void verifySignature(
112             SAMLSignedObject obj, 
113             String signerName, 
114             KeyStore ks, 
115             Key knownKey)
116         throws SAMLException {
117         try {
118             NDC.push("verifySignature");
119             
120             if (!obj.isSigned()) {
121                 log.error("unable to find a signature");
122                 throw new TrustException(SAMLException.RESPONDER,
123                 "ShibPOSTProfile.verifySignature() given an unsigned object");
124             }
125             
126             if (knownKey != null) {
127                 log.info("verifying signature with known key value, ignoring signature KeyInfo");
128                 obj.verify(knownKey);
129                 return;
130             }
131             
132             
133             log.info("verifying signature with embedded KeyInfo");
134             obj.verify();
135             
136             // This is pretty painful, and this is leveraging the supposedly
137             // automatic support in JDK 1.4.
138             // First we have to extract the certificates from the object.
139             Iterator certs_from_obj = obj.getX509Certificates();
140             if (!certs_from_obj.hasNext()) {
141                 log.error("need certificates inside object to establish trust");
142                 throw new TrustException(SAMLException.RESPONDER,
143                 "ShibPOSTProfile.verifySignature() can't find any certificates");
144             }
145             
146             // We assume the first one in the set is the end entity cert.
147             X509Certificate entity_cert = (X509Certificate) certs_from_obj.next();
148             
149             // Match the CN of the entity cert with the expected signer.
150             String dname = entity_cert.getSubjectDN().getName();
151             log.debug("found entity cert with DN: " + dname);
152             String cname = "CN=" + signerName;
153             if (!dname.equalsIgnoreCase(cname) && !dname.regionMatches(true, 0, cname + ',', 0, cname.length() + 1)) {
154                 log
155                 .error("verifySignature() found a mismatch between the entity certificate's DN and the expected signer: "
156                         + signerName);
157                 throw new TrustException(SAMLException.RESPONDER,
158                 "ShibPOSTProfile.verifySignature() found mismatch between entity certificate and expected signer");
159             }
160             
161             // Prep a chain between the entity cert and the trusted roots.
162             X509CertSelector targetConstraints = new X509CertSelector();
163             targetConstraints.setCertificate(entity_cert);
164             PKIXBuilderParameters params = new PKIXBuilderParameters(ks, targetConstraints);
165             params.setMaxPathLength(-1);
166             
167             Vector certbag = new Vector();
168             certbag.add(entity_cert);
169             while (certs_from_obj.hasNext())
170                 certbag.add(certs_from_obj.next());
171             CollectionCertStoreParameters ccsp = new CollectionCertStoreParameters(certbag);
172             CertStore store = CertStore.getInstance("Collection", ccsp);
173             params.addCertStore(store);
174             
175             // Attempt to build a path.
176             CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX");
177             PKIXCertPathBuilderResult result = (PKIXCertPathBuilderResult) cpb.build(params);
178         } catch (CertPathBuilderException e) {
179             log.error("caught a cert path builder exception: " + e.getMessage());
180             throw new TrustException(SAMLException.RESPONDER,
181                     "ShibPOSTProfile.verifySignature() unable to build a PKIX certificate path", e);
182         } catch (GeneralSecurityException e) {
183             log.error("caught a general security exception: " + e.getMessage());
184             throw new TrustException(SAMLException.RESPONDER,
185                     "ShibPOSTProfile.verifySignature() unable to build a PKIX certificate path", e);
186         } finally {
187             NDC.pop();
188         }
189     }
190
191
192
193     /**
194      * @see org.opensaml.SAMLBrowserProfile#receive(java.lang.StringBuffer, javax.servlet.http.HttpServletRequest, java.lang.String, int, org.opensaml.ReplayCache, org.opensaml.SAMLBrowserProfile.ArtifactMapper, int)
195      */
196     public BrowserProfileResponse receive(
197             StringBuffer issuer,
198             HttpServletRequest reqContext,
199             String recipient,
200             int supportedProfiles,
201             ReplayCache replayCache,
202             ArtifactMapper artifactMapper,
203             int minorVersion
204             ) throws SAMLException {
205         
206         String providerId = null;
207         issuer.setLength(0);
208         
209         // Let SAML do all the decoding and parsing
210         BrowserProfileResponse bpr = profile.receive(issuer, reqContext, recipient, supportedProfiles, replayCache, artifactMapper, minorVersion);
211         
212         /*
213          * Now find the Metadata for the Entity that send this assertion.
214          * From the C++, look first for issuer, then namequalifier (for 1.1 compat.)
215          */
216         EntityDescriptor entity = null;
217         String asn_issuer = bpr.assertion.getIssuer();
218         String qualifier = bpr.authnStatement.getSubject().getNameIdentifier().getNameQualifier();
219         ServiceProviderConfig config = context.getServiceProviderConfig();
220         ApplicationInfo appinfo = config.getApplication(applicationId);
221         
222         entity = appinfo.lookup(asn_issuer);
223         providerId=asn_issuer;
224         if (entity==null) {
225             providerId=qualifier;
226             entity= appinfo.lookup(qualifier);
227         }
228         if (entity==null) {
229             log.error("assertion issuer not found in metadata(Issuer ="+
230                     issuer+", NameQualifier="+qualifier);
231             throw new MetadataException("ShibBrowserProfile.receive() metadata lookup failed, unable to process assertion");
232         }
233         issuer.append(providerId);
234         
235         // TODO: finish profile extension using metadata/trust interfaces
236         
237         return bpr;
238     }
239 }