DO NOT REPLY TO THIS EMAIL, BUT PLEASE POST YOUR BUG RELATED COMMENTS THROUGH THE WEB INTERFACE AVAILABLE AT <http://nagoya.apache.org/bugzilla/show_bug.cgi?id=16032>. ANY REPLY MADE TO THIS MESSAGE WILL NOT BE COLLECTED AND INSERTED IN THE BUG DATABASE.
http://nagoya.apache.org/bugzilla/show_bug.cgi?id=16032 Not all attributes get copied when the request is forwarded Summary: Not all attributes get copied when the request is forwarded Product: Tomcat 4 Version: 4.1.12 Platform: All OS/Version: All Status: NEW Severity: Major Priority: Other Component: Connector:Coyote HTTP/1.1 AssignedTo: [EMAIL PROTECTED] ReportedBy: [EMAIL PROTECTED] The attribute "javax.servlet.request.X509Certificate" doesn't get copied with the request when the request is forwarded. To reproduce it, follow these steps: - Create an HTTPS connector and enable client authentication. <Connector className="org.apache.coyote.tomcat4.CoyoteConnector" port="8443" minProcessors="5" maxProcessors="75" enableLookups="true" acceptCount="10" debug="0" scheme="https" secure="true" useURIValidationHack="false"> <Factory className="org.apache.coyote.tomcat4.CoyoteServerSocketFactory" keystoreFile="conf/server.jks" keystorePass="changeit" clientAuth="true" protocol="TLS" /> </Connector> - Generate a CA certificate for the server: > openssl -genrsa -out ca.key 1024 > openssl req -new -key ca.key -out ca.csr > openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt - Import the CA certificate (ca.crt) to the tru sted CA certificates keystore in your JVM: > keytool -import -file ca.crt -keystore <JAVA_HOME>/lib/security/cacerts - Generate a certificate to the client: > openssl -genrsa -out client.key 1024 > openssl req -new -key client.key -out client.csr > openssl x509 -req -days 365 -CA ca.crt -CAkey ca.key -CAcreateserial -in client.csr -out client.crt (you may want to leave -CAcreateserial out). > openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12 - Import the client certificate and key (client.p12) into the browser. - Import the CA certificate (ca.crt) into the browser. - Generate a certificate for the server: > keytool -genkey -keystore server.jks -alias tomcat - Run the following servlet: package test; import javax.servlet.ServletException; import javax.servlet.http.*; import java.io.*; public class TestCertServlet extends HttpServlet { protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { Integer curTmp = (Integer)req.getAttribute("value"); int cur = curTmp == null ? 0 : curTmp.intValue()+1; Object certs = req.getAttribute("javax.servlet.request.X509Certificate"); //System.out.println("Cur: "+cur+" - certs: "+certs); if(cur==5) { printInfo(res, req, cur); return; } req.setAttribute("Other"+cur, "Set"); req.setAttribute("Certs"+cur, certs); req.setAttribute("value", new Integer(cur)); this.getServletContext().getRequestDispatcher("/TestCert").forward(req, res); } private void printInfo(HttpServletResponse res, HttpServletRequest req, int cur) throws IOException { PrintWriter w = res.getWriter(); w.write("<html><title>Testing...</title><body>"); for(int i=0; i<cur; i++) { w.write("<p><b>"+i+"</b><br>"); w.write("Other"+i+"="+req.getAttribute("Other"+i)+"<br>"); w.write("Certs"+i+"="+req.getAttribute("Certs"+i)+"<br>"); } w.write("</body></html>"); } } The output will be this: 0 Other0=Set Certs0=[Ljava.security.cert.X509Certificate;@1ae939f 1 Other1=Set Certs1=null ... The attribute "javax.servlet.request.X509Certificate" is not set after the request is forwarded. This makes our login schema fail, as we would always forward the request to the login servlet if the user has not been authenticated yet. Note that optionally we could redirect the page to the login servlet and then redirect it back to the requested page after authentication, however as we don't need any information from the user other than the certificate (already sent in SSL handshake), there would be no need for redirection, as it would have been the case if the user had to type his/her username and password. Workaround A quick fix for this problem is to set the attribute "again" in a filter for everypage (refer to Solution to understand why): public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { req.setAttribute("javax.servlet.request.X509Certificate", req.getAttribute("javax.servlet.request.X509Certificate")); chain.doFilter(req, res); } Solution The attribute "javax.servlet.request.X509Certificate" is set in a Request object. This object is wrapped by a CoyoteRequest object, which is wrapped by CoyoteRequestFacade. CoyoteRequestFacade only forwards all calls. CoyoteRequest stores attribute sets to this object and getAttribute(String) first looks up on its own attribute map and then forward the call to the Request. When the request is forwarded, a ApplicationHttpRequest is created as the new request and it's wrapped around the CoyoteRequestFacade. The ApplicationHttpRequest works a little differently, instead of forwarding the calls to get attribute, it copies all attributes to its own attribute map, making minor modifications to it (see setRequest(HttpServletRequest)). The problem is that CoyoteRequest.getAttributeNames() doesn't take the underlying request into account and return only its own names (which in this case, doesn't include "javax.servlet.request.X509Certificate"). Setting the attribute again, will make CoyoteRequest stores it in its own map and make getAttributeNames() works for this attribute. The fix would be something like: public class CoyoteRequest implements HttpRequest, HttpServletRequest { ... public Enumeration getAttributeNames() { HashSet keys = new HashSet(this.attributes.keySet()); keys.add(Constants.SSL_CERTIFICATE_ATTR); // Check whether this is needed/right. keys.addAll(this.coyoteRequest.getAttributes().keySet()); return (new Enumerator(keys)); } ... public void removeAttribute(String name) { if(attributes.remove(name) == null) { this.coyoteRequest.getAttributes().remove(name); } } ... } I would also suggest to add comments to getAttribute(String) advising to update getAttributeNames() in case of change. Thought: I'm not sure why CoyoteRequest is not using the Request to store it's own attributes. The only thing that comes to my mind is that, maybe the Request is being shared with other objects (it's not thread-safe though). If this is the case, removeAttribute(String) would affect all others, so changes would have to be made to that method. If not, wouldn't it just be much easier to use the Request or to not use it at? NOTE: It's the first time I debug Tomcat and report a bug, so I apologize for any mistake I might have made. Please, let me know if there is any other information you need. -- To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]> For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>