You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by bu...@apache.org on 2003/01/13 17:56:59 UTC

DO NOT REPLY [Bug 16032] New: - Not all attributes get copied when the request is forwarded

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: tomcat-dev@jakarta.apache.org
        ReportedBy: fvoznika@hotmail.com


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:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>