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