--- java/org/apache/catalina/authenticator/AuthenticatorBase.java (revision 1616257) +++ java/org/apache/catalina/authenticator/AuthenticatorBase.java (working copy) @@ -19,17 +19,6 @@ package org.apache.catalina.authenticator; -import java.io.IOException; -import java.security.Principal; -import java.security.cert.X509Certificate; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -import javax.servlet.ServletException; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletResponse; - import org.apache.catalina.Authenticator; import org.apache.catalina.Container; import org.apache.catalina.Context; @@ -47,11 +36,22 @@ import org.apache.catalina.util.DateTool; import org.apache.catalina.util.SessionIdGenerator; import org.apache.catalina.valves.ValveBase; +import org.apache.coyote.ActionCode; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.res.StringManager; +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.Principal; +import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + /** * Basic implementation of the Valve interface that enforces the * <security-constraint> elements in the web application @@ -561,8 +561,7 @@ } if (!authRequired && context.getPreemptiveAuthentication()) { - X509Certificate[] certs = (X509Certificate[]) request.getAttribute( - Globals.CERTIFICATES_ATTR); + final X509Certificate[] certs = getRequestCertificates(request); authRequired = certs != null && certs.length > 0; } @@ -614,7 +613,34 @@ // ------------------------------------------------------ Protected Methods + /** + * Look for the X509 certificate chain in the Request under the key + * javax.servlet.request.X509Certificate. + * + *

If not found, try extracting + * the certificate chain from the Coyote request and populate the Request so that we + * can find it. This is required when using SSL with a Tomcat connector.

+ * + * @param request Request to be processed + * + * @return The X509 certificate chain if found, null otherwise. + */ + protected X509Certificate[] getRequestCertificates(final Request request) { + X509Certificate certs[] = (X509Certificate[]) request.getAttribute(Globals.CERTIFICATES_ATTR); + if ((certs == null) || (certs.length < 1)) { + try { + request.getCoyoteRequest().action(ActionCode.REQ_SSL_CERTIFICATE, null); + + } catch (IllegalStateException ise) { + // Request body was too large for save buffer + return null; + } + certs = (X509Certificate[]) request.getAttribute(Globals.CERTIFICATES_ATTR); + } + return certs; + } + /** * Associate the specified single sign on identifier with the * specified Session. --- java/org/apache/catalina/authenticator/SSLAuthenticator.java (revision 1616257) +++ java/org/apache/catalina/authenticator/SSLAuthenticator.java (working copy) @@ -19,20 +19,17 @@ package org.apache.catalina.authenticator; +import org.apache.catalina.connector.Request; +import org.apache.catalina.deploy.LoginConfig; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.security.Principal; import java.security.cert.X509Certificate; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.apache.catalina.Globals; -import org.apache.catalina.connector.Request; -import org.apache.catalina.deploy.LoginConfig; -import org.apache.coyote.ActionCode; - - /** * An Authenticator and Valve implementation of authentication * that utilizes SSL certificates to identify client users. @@ -129,22 +126,8 @@ if (containerLog.isDebugEnabled()) containerLog.debug(" Looking up certificates"); - X509Certificate certs[] = (X509Certificate[]) - request.getAttribute(Globals.CERTIFICATES_ATTR); + final X509Certificate certs[] = getRequestCertificates(request); if ((certs == null) || (certs.length < 1)) { - try { - request.getCoyoteRequest().action - (ActionCode.REQ_SSL_CERTIFICATE, null); - } catch (IllegalStateException ise) { - // Request body was too large for save buffer - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, - sm.getString("authenticator.certificates")); - return false; - } - certs = (X509Certificate[]) - request.getAttribute(Globals.CERTIFICATES_ATTR); - } - if ((certs == null) || (certs.length < 1)) { if (containerLog.isDebugEnabled()) containerLog.debug(" No certificates included with this request"); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, @@ -169,7 +152,6 @@ } - @Override protected String getAuthMethod() { return HttpServletRequest.CLIENT_CERT_AUTH; --- test/org/apache/tomcat/util/net/TestClientCert.java (revision 1616257) +++ test/org/apache/tomcat/util/net/TestClientCert.java (working copy) @@ -16,17 +16,26 @@ */ package org.apache.tomcat.util.net; +import org.apache.catalina.Context; +import org.apache.catalina.authenticator.AuthenticatorBase; +import org.apache.catalina.authenticator.SSLAuthenticator; +import org.apache.catalina.startup.TestTomcat; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; +import org.apache.tomcat.util.buf.ByteChunk; +import org.junit.Assume; +import org.junit.Test; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; import java.util.Arrays; import static org.junit.Assert.assertEquals; -import org.junit.Assume; -import org.junit.Test; - -import org.apache.catalina.startup.Tomcat; -import org.apache.catalina.startup.TomcatBaseTest; -import org.apache.tomcat.util.buf.ByteChunk; - /** * The keys and certificates used in this file are all available in svn and were * generated using a test CA the files for which are in the Tomcat PMC private @@ -34,12 +43,34 @@ */ public class TestClientCert extends TomcatBaseTest { + private static final String HTTPS_PREFIX = "https://localhost:"; + private static final String CONTEXT_PATH_SSL = "/ssl"; + private static final String URI_UNPROTECTED = "/unprotected"; + public static final String USERNAME = "CN=user1, C=US"; + public static final String TESTROLE = "testrole"; + + private static final int SHORT_SESSION_TIMEOUT_MINS = 1; + + private Tomcat tomcat; + private Context sslContext; + @Test - public void testClientCertGet() throws Exception { + public void testPreemptiveClientCert() throws Exception { Assume.assumeTrue("SSL renegotiation has to be supported for this test", TesterSupport.isRenegotiationSupported(getTomcatInstance())); // Unprotected resource + ByteChunk res = getUrl(HTTPS_PREFIX + getPort() + CONTEXT_PATH_SSL + URI_UNPROTECTED); + assertEquals("OK", res.toString()); + + } + + @Test + public void testBug56825() throws Exception { + Assume.assumeTrue("SSL renegotiation has to be supported for this test", + TesterSupport.isRenegotiationSupported(getTomcatInstance())); + + // Unprotected resource ByteChunk res = getUrl("https://localhost:" + getPort() + "/unprotected"); assertEquals("OK", res.toString()); @@ -103,13 +134,46 @@ super.setUp(); - Tomcat tomcat = getTomcatInstance(); + tomcat = getTomcatInstance(); TesterSupport.configureClientCertContext(tomcat); + // add an SSL webapp with the SSLAuthenticator valve + setupSslContext(); + // Start Tomcat tomcat.start(); TesterSupport.configureClientSsl(); } + + private void setupSslContext() throws Exception { + // Must have a real docBase for webapps - just use temp + sslContext = tomcat.addContext(CONTEXT_PATH_SSL, System.getProperty("java.io.tmpdir")); + sslContext.setSessionTimeout(SHORT_SESSION_TIMEOUT_MINS); + sslContext.setPreemptiveAuthentication(true); + + // Add a servlet - the preemptive mode ensure the authentication will be done if a certificate + // is available whatever the resource accessed is private or public + // we don't need any servlet security configured here for this test + Tomcat.addServlet(sslContext, "TesterServlet", new HttpServlet() { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + // if the user has not been authenticated then it's going to fail + resp.setContentType("text/plain"); + PrintWriter out = resp.getWriter(); + out.print(req.isUserInRole(TESTROLE) ? "OK" : "KO"); + } + }); + sslContext.addServletMapping(URI_UNPROTECTED, "TesterServlet"); + + // Configure the Realm at context level + TestTomcat.MapRealm realm = new TestTomcat.MapRealm(); + realm.addUser(USERNAME, "not used for SSL"); + realm.addUserRole(USERNAME, TESTROLE); + sslContext.setRealm(realm); + + AuthenticatorBase basicAuthenticator = new SSLAuthenticator(); + sslContext.getPipeline().addValve(basicAuthenticator); + } }