Fully enable Server Certificate Trust
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / serviceprovider / ShibHttpHook.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 /*
18  * ShibHttpHook - Receive callbacks from OpenSAML HTTP Session processing.
19  */
20 package edu.internet2.middleware.shibboleth.serviceprovider;
21
22 import java.net.HttpURLConnection;
23 import java.net.Socket;
24 import java.security.KeyManagementException;
25 import java.security.NoSuchAlgorithmException;
26 import java.security.Principal;
27 import java.security.PrivateKey;
28 import java.security.cert.CertificateException;
29 import java.security.cert.X509Certificate;
30
31 import javax.net.ssl.HttpsURLConnection;
32 import javax.net.ssl.KeyManager;
33 import javax.net.ssl.SSLContext;
34 import javax.net.ssl.SSLSocketFactory;
35 import javax.net.ssl.TrustManager;
36 import javax.net.ssl.X509KeyManager;
37 import javax.net.ssl.X509TrustManager;
38 import javax.servlet.http.HttpServletRequest;
39 import javax.servlet.http.HttpServletResponse;
40
41 import org.apache.log4j.Logger;
42 import org.opensaml.SAMLException;
43 import org.opensaml.SAMLSOAPHTTPBinding.HTTPHook;
44
45 import edu.internet2.middleware.shibboleth.common.Credential;
46 import edu.internet2.middleware.shibboleth.common.Credentials;
47 import edu.internet2.middleware.shibboleth.common.Trust;
48 import edu.internet2.middleware.shibboleth.metadata.AttributeAuthorityDescriptor;
49
50 /**
51  * During Attribute Query, SAML creates the HTTP(S) session with
52  * the AA. Objects of this class provide a callback for special
53  * processing of the Session that SAML has established before data
54  * is exchanged. This allows Shib to add its own Metadata and Trust
55  * processing to validate the AA identity.
56  
57  * @author Howard Gilbert
58  *
59  */
60 public class ShibHttpHook implements HTTPHook {
61
62     private static Logger log = Logger.getLogger(HTTPHook.class);
63     
64     ServiceProviderContext context = ServiceProviderContext.getInstance();
65     ServiceProviderConfig config = context.getServiceProviderConfig();
66     
67     // If we present a ClientCert, it will be this one.
68     Credentials credentials = config.getCredentials();
69     
70     // SAML Doesn't know the Shibboleth objects, so they have to be saved
71     // by the constructor so they can be used in callbacks without being
72     // passed as arguments
73     AttributeAuthorityDescriptor role; // The AA object from the Metadata
74     Trust trust; // A ShibbolethTrust object
75     
76     /**
77      * @param role
78      */
79     public ShibHttpHook(AttributeAuthorityDescriptor role, Trust trust) {
80         super();
81         this.role = role;  // Save the AA Role for the callback
82         this.trust = trust; // Save the ShibTrust for the callback
83     }
84
85     public boolean incoming(HttpServletRequest r, Object globalCtx,
86             Object callCtx) throws SAMLException {
87         log.error("ShibHttpHook method incoming-1 should not have been called.");
88         return true;
89     }
90
91     public boolean outgoing(HttpServletResponse r, Object globalCtx,
92             Object callCtx) throws SAMLException {
93         log.error("ShibHttpHook method outgoing-1 should not have been called.");
94         return true;
95     }
96
97     public boolean incoming(HttpURLConnection conn, Object globalCtx,
98             Object callCtx) throws SAMLException {
99         // Called with the AA response, but I have nothing to add here
100         return true;
101     }
102
103     /**
104      * After the URLConnection has been initialized and before 
105      * the connect() method is called, this exit has a chance to
106      * do additional processing.
107      * 
108      * <p>If this is an HTTPS session, configure the SocketFactory
109      * to use a custom TrustManager for Certificate processing.</p>
110      */
111     public boolean outgoing(HttpURLConnection conn, Object globalCtx,
112             Object callCtx) throws SAMLException {
113         if (!(conn instanceof HttpsURLConnection)) {
114             return true; // HTTP (non-SSL) sessions need no processing
115         }
116         // Cast to subclass with extra info
117         HttpsURLConnection sslconn = (HttpsURLConnection) conn;
118         
119         // To get your own Certificate Processing exits, you have 
120         // to create a custom SSLContext, configure it, and then
121         // obtain a SocketFactory
122         SSLContext sslContext = null;
123         try {
124             sslContext = SSLContext.getInstance("SSL");
125         } catch (NoSuchAlgorithmException e) {
126             // Cannot happen in code that is already doing SSL
127             log.error("Cannot find required SSL support");
128             return true;
129         }
130         
131         // Arrays with one element (for init)
132         TrustManager[] tms = new TrustManager[] {new ShibTrustManager()};
133         KeyManager[] kms = new KeyManager[] {new ShibKeyManager()};
134         
135         try {
136             // Attach the KeyManager and TrustManager to the Context
137             sslContext.init(kms,tms,new java.security.SecureRandom());
138         } catch (KeyManagementException e) {
139             return false;
140         }
141         
142         // Now we can get our own custom SocketFactory and replace
143         // the default factory in the caller's URLConnection
144         SSLSocketFactory socketFactory = sslContext.getSocketFactory();
145         sslconn.setSSLSocketFactory(socketFactory);
146         
147         // The KeyManager and TrustManager get callbacks from JSSE during
148         // the URLConnection.connect() call
149         return true;
150     }
151     
152     /**
153      * Called to select the Client Certificate the SP will present to 
154      * the AA.
155      * 
156      * <p>Normally a user KeyManager extends some class backed by a 
157      * KeyStore. It just chooses an alias, and lets the parent class 
158      * do the dirty work of extracting the Certificate chain from the 
159      * backing file. However, in Shibboleth the SP Credentials come
160      * from the configuration file and are in memory. There is no
161      * meaningful alias, so we make one up.
162      */
163     class ShibKeyManager implements X509KeyManager {
164         
165         public String fred ="Fred";
166         public String[] freds = {fred};
167
168         public String[] getClientAliases(String arg0, Principal[] arg1) {
169             return freds;
170         }
171
172         public String chooseClientAlias(String[] arg0, Principal[] arg1, Socket arg2) {
173             return fred;
174         }
175
176         public String[] getServerAliases(String arg0, Principal[] arg1) {
177             return freds;
178         }
179
180         public String chooseServerAlias(String arg0, Principal[] arg1, Socket arg2) {
181             return fred;
182         }
183
184         public X509Certificate[] getCertificateChain(String arg0) {
185             // Obtain the Client Cert from the Credentials object
186             // in the configuration file. Ignore argument "fred".
187             Credential credential = credentials.getCredential();
188             X509Certificate[] certificateChain = credential.getX509CertificateChain();
189             return certificateChain;
190         }
191
192         public PrivateKey getPrivateKey(String arg0) {
193             // Obtain the Private Key from the Credentials object.
194             Credential credential = credentials.getCredential();
195             PrivateKey privateKey = credential.getPrivateKey();
196             return privateKey;
197         }
198         
199     }
200     
201     /**
202      * Called to approve or reject an SSL Server Certificate.
203      * In practice this is the Certificate of the AA.
204      * 
205      * <p>A TrustManager handles Certificate approval at either end
206      * of an SSL connection, but this code is in the SP and is only 
207      * inserted into the Attribute Query to the AA. When the AA is
208      * configured to use HTTPS and presents an SSL Server Certficate,
209      * call the commmon code to validate that this Certificate is in
210      * the Metadata.</p>
211      */
212     class ShibTrustManager  implements X509TrustManager {
213
214         public X509Certificate[] getAcceptedIssuers() {
215             log.error("ShibHttpHook method getAcceptedIssuers should not have been called.");
216             return new X509Certificate[0]; 
217         }
218         
219         public void checkClientTrusted(X509Certificate[] arg0, String arg1) 
220             throws CertificateException {
221             log.error("ShibHttpHook method checkClientTrusted should not have been called.");
222         }
223
224         public void checkServerTrusted(X509Certificate[] certs, String arg1) 
225             throws CertificateException {
226             if (trust.validate(certs[0],certs,role)) {
227                 log.debug("ShibHttpHook accepted AA Server Certificate.");
228                 return;
229             }
230             log.info("ShibHttpHook rejected AA Server Certificate.");
231             throw new CertificateException("Cannot validate AA Server Certificate in Metadata");
232             
233         }
234         
235     }
236
237 }