Added servlet filter that trusts previous client_auth. For use with the HS.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / utils / ClientCertTrustFilter.java
1 /*
2  * The Shibboleth License, Version 1.
3  * Copyright (c) 2002
4  * University Corporation for Advanced Internet Development, Inc.
5  * All rights reserved
6  *
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * Redistributions of source code must retain the above copyright notice, this
12  * list of conditions and the following disclaimer.
13  *
14  * Redistributions in binary form must reproduce the above copyright notice,
15  * this list of conditions and the following disclaimer in the documentation
16  * and/or other materials provided with the distribution, if any, must include
17  * the following acknowledgment: "This product includes software developed by
18  * the University Corporation for Advanced Internet Development
19  * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20  * may appear in the software itself, if and wherever such third-party
21  * acknowledgments normally appear.
22  *
23  * Neither the name of Shibboleth nor the names of its contributors, nor
24  * Internet2, nor the University Corporation for Advanced Internet Development,
25  * Inc., nor UCAID may be used to endorse or promote products derived from this
26  * software without specific prior written permission. For written permission,
27  * please contact shibboleth@shibboleth.org
28  *
29  * Products derived from this software may not be called Shibboleth, Internet2,
30  * UCAID, or the University Corporation for Advanced Internet Development, nor
31  * may Shibboleth appear in their name, without prior written permission of the
32  * University Corporation for Advanced Internet Development.
33  *
34  *
35  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36  * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39  * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40  * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  */
49
50 package edu.internet2.middleware.shibboleth.utils;
51
52 import java.io.IOException;
53 import java.security.Principal;
54 import java.security.cert.X509Certificate;
55 import java.util.regex.Matcher;
56 import java.util.regex.Pattern;
57 import java.util.regex.PatternSyntaxException;
58
59 import javax.servlet.Filter;
60 import javax.servlet.FilterChain;
61 import javax.servlet.FilterConfig;
62 import javax.servlet.ServletException;
63 import javax.servlet.ServletRequest;
64 import javax.servlet.ServletResponse;
65 import javax.servlet.http.HttpServletRequest;
66 import javax.servlet.http.HttpServletRequestWrapper;
67 import javax.servlet.http.HttpServletResponse;
68
69 import org.apache.log4j.Logger;
70 import org.apache.log4j.MDC;
71
72 import sun.security.acl.PrincipalImpl;
73
74 /**
75  * Simple Servlet Filter that populates the ServletRequest with data from a client certificate.  Relies
76  * on external mechanisms to properly authorize the certificate.
77  *
78  * @author Walter Hoehn
79  */
80 public class ClientCertTrustFilter implements Filter {
81
82         private static Logger log = Logger.getLogger(ClientCertTrustFilter.class.getName());
83         protected Pattern regex = Pattern.compile(".*CN=([^,/]+).*");
84         protected int matchGroup = 1;
85
86         /**
87          * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
88          */
89         public void init(FilterConfig config) throws ServletException {
90                 if (config.getInitParameter("regex") != null) {
91                         try {
92                                 regex = Pattern.compile(config.getInitParameter("regex"));
93                         } catch (PatternSyntaxException e) {
94                                 throw new ServletException("Failed to start ClientCertTrustFilter: supplied regular expression fails to compile.");
95                         }
96                 }
97
98                 if (config.getInitParameter("matchGroup") != null) {
99                         try {
100                                 matchGroup = Integer.parseInt(config.getInitParameter("matchGroup"));
101                         } catch (NumberFormatException e) {
102                                 throw new ServletException("Failed to start ClientCertTrustFilter: supplied matchGroup is not an integer.");
103                         }
104                 }
105         }
106
107         /**
108          * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
109          */
110         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
111                 throws IOException, ServletException {
112                 MDC.put("serviceId", "[Client Cert Trust Filter]");
113                 if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
114                         log.error("Only HTTP(s) requests are supported by the ClientCertTrustFilter.");
115                         return;
116                 }
117                 HttpServletRequest httpRequest = (HttpServletRequest) request;
118                 HttpServletResponse httpResponse = (HttpServletResponse) response;
119
120                 log.debug("Using regex: (" + regex.pattern() + ").");
121                 log.debug("Using matchGroup of (" + matchGroup + ")");
122
123                 X509Certificate[] certs = (X509Certificate[]) httpRequest.getAttribute("javax.servlet.request.X509Certificate");
124                 if (certs == null) {
125                         log.error("Processed a request that did not contain a client certificate.");
126                         httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Client certificate required.");
127                         return;
128                 }
129
130                 log.debug("Attempting to extract principal name from Subjet: (" + certs[0].getSubjectDN().getName() + ").");
131                 Matcher matches = regex.matcher(certs[0].getSubjectDN().getName());
132                 boolean found = matches.find();
133                 if (!found) {
134                         log.error("Principal could not be extracted from Certificate Subject.");
135                         httpResponse.sendError(
136                                 HttpServletResponse.SC_FORBIDDEN,
137                                 "Client certificate does not contain required data.");
138                         return;
139                 }
140                 String principalName;
141                 try {
142                         principalName = matches.group(matchGroup);
143                 } catch (IndexOutOfBoundsException e) {
144                         log.error("Principal could not be extracted from Certificate Subject: matchGroup out of bounds.");
145                         httpResponse.sendError(
146                                 HttpServletResponse.SC_FORBIDDEN,
147                                 "Client certificate does not contain required data.");
148                         return;
149                 }
150                 log.debug("Extracted principal name (" + principalName + ") from Subject.");
151                 chain.doFilter(new ClientCertTrustWrapper(httpRequest, new PrincipalImpl(principalName)), response);
152         }
153
154         /**
155          * @see javax.servlet.Filter#destroy()
156          */
157         public void destroy() {
158         }
159
160         class ClientCertTrustWrapper extends HttpServletRequestWrapper implements HttpServletRequest {
161
162                 Principal principal;
163
164                 ClientCertTrustWrapper(HttpServletRequest request, Principal principal) {
165                         super(request);
166                         this.principal = principal;
167                 }
168
169                 /**
170                  * @see javax.servlet.http.HttpServletRequest#getAuthType()
171                  */
172                 public String getAuthType() {
173                         return HttpServletRequest.CLIENT_CERT_AUTH;
174                 }
175
176                 /**
177                  * @see javax.servlet.http.HttpServletRequest#getRemoteUser()
178                  */
179                 public String getRemoteUser() {
180                         return principal.getName();
181                 }
182
183                 /**
184                  * @see javax.servlet.http.HttpServletRequest#getUserPrincipal()
185                  */
186                 public Principal getUserPrincipal() {
187                         return principal;
188                 }
189         }
190
191 }