51f42d9f43b7c24c1638ea6c96502fa9efc72eb4
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / utils / ClientCertTrustFilter.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.utils;
18
19 import java.io.IOException;
20 import java.security.Principal;
21 import java.security.cert.X509Certificate;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24 import java.util.regex.PatternSyntaxException;
25
26 import javax.servlet.Filter;
27 import javax.servlet.FilterChain;
28 import javax.servlet.FilterConfig;
29 import javax.servlet.ServletException;
30 import javax.servlet.ServletRequest;
31 import javax.servlet.ServletResponse;
32 import javax.servlet.http.HttpServletRequest;
33 import javax.servlet.http.HttpServletRequestWrapper;
34 import javax.servlet.http.HttpServletResponse;
35
36 import org.apache.log4j.Logger;
37 import org.apache.log4j.MDC;
38
39 import edu.internet2.middleware.shibboleth.common.LocalPrincipal;
40
41 /**
42  * Simple Servlet Filter that populates the ServletRequest with data from a client certificate. Relies on external
43  * mechanisms to properly authorize the certificate.
44  * 
45  * @author Walter Hoehn
46  */
47 public class ClientCertTrustFilter implements Filter {
48
49         private static Logger log = Logger.getLogger(ClientCertTrustFilter.class.getName());
50         protected Pattern regex = Pattern.compile(".*CN=([^,/]+).*");
51         protected int matchGroup = 1;
52
53         /**
54          * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
55          */
56         public void init(FilterConfig config) throws ServletException {
57
58                 if (config.getInitParameter("regex") != null) {
59                         try {
60                                 regex = Pattern.compile(config.getInitParameter("regex"));
61                         } catch (PatternSyntaxException e) {
62                                 throw new ServletException(
63                                                 "Failed to start ClientCertTrustFilter: supplied regular expression fails to compile.");
64                         }
65                 }
66
67                 if (config.getInitParameter("matchGroup") != null) {
68                         try {
69                                 matchGroup = Integer.parseInt(config.getInitParameter("matchGroup"));
70                         } catch (NumberFormatException e) {
71                                 throw new ServletException(
72                                                 "Failed to start ClientCertTrustFilter: supplied matchGroup is not an integer.");
73                         }
74                 }
75         }
76
77         /**
78          * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse,
79          *      javax.servlet.FilterChain)
80          */
81         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
82                         ServletException {
83
84                 MDC.put("serviceId", "[Client Cert Trust Filter]");
85
86                 if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
87                         log.error("Only HTTP(s) requests are supported by the ClientCertTrustFilter.");
88                         return;
89                 }
90                 HttpServletRequest httpRequest = (HttpServletRequest) request;
91                 HttpServletResponse httpResponse = (HttpServletResponse) response;
92
93                 log.debug("Using regex: (" + regex.pattern() + ").");
94                 log.debug("Using matchGroup of (" + matchGroup + ")");
95
96                 X509Certificate[] certs = (X509Certificate[]) httpRequest.getAttribute("javax.servlet.request.X509Certificate");
97                 if (certs == null) {
98                         log.error("Processed a request that did not contain a client certificate.");
99                         httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Client certificate required.");
100                         return;
101                 }
102
103                 log.debug("Attempting to extract principal name from Subjet: (" + certs[0].getSubjectDN().getName() + ").");
104                 Matcher matches = regex.matcher(certs[0].getSubjectDN().getName());
105                 if (!matches.find()) {
106                         log.error("Principal could not be extracted from Certificate Subject.");
107                         httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN,
108                                         "Client certificate does not contain required data.");
109                         return;
110                 }
111                 String principalName;
112                 try {
113                         principalName = matches.group(matchGroup);
114                 } catch (IndexOutOfBoundsException e) {
115                         log.error("Principal could not be extracted from Certificate Subject: matchGroup out of bounds.");
116                         httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN,
117                                         "Client certificate does not contain required data.");
118                         return;
119                 }
120                 log.debug("Extracted principal name (" + principalName + ") from Subject.");
121                 chain.doFilter(new ClientCertTrustWrapper(httpRequest, new LocalPrincipal(principalName)), response);
122         }
123
124         /**
125          * @see javax.servlet.Filter#destroy()
126          */
127         public void destroy() {
128
129         // required by interface
130         // no resources to clean
131         }
132
133         /**
134          * <code>HttpServletRequest</code> wrapper class. Returns a locally specified principal and hardcoded authType.
135          */
136         private class ClientCertTrustWrapper extends HttpServletRequestWrapper {
137
138                 private Principal principal;
139
140                 private ClientCertTrustWrapper(HttpServletRequest request, Principal principal) {
141
142                         super(request);
143                         this.principal = principal;
144                 }
145
146                 /**
147                  * @see javax.servlet.http.HttpServletRequest#getAuthType()
148                  */
149                 public String getAuthType() {
150
151                         return HttpServletRequest.CLIENT_CERT_AUTH;
152                 }
153
154                 /**
155                  * @see javax.servlet.http.HttpServletRequest#getRemoteUser()
156                  */
157                 public String getRemoteUser() {
158
159                         return principal.getName();
160                 }
161
162                 /**
163                  * @see javax.servlet.http.HttpServletRequest#getUserPrincipal()
164                  */
165                 public Principal getUserPrincipal() {
166
167                         return principal;
168                 }
169         }
170
171 }