Mantis - Resin
|
|||||
Viewing Issue Advanced Details | |||||
|
|||||
ID: | Category: | Severity: | Reproducibility: | Date Submitted: | Last Update: |
4044 | feature | always | 05-18-10 20:32 | 02-11-11 16:04 | |
|
|||||
Reporter: | alex | Platform: | |||
Assigned To: | ferg | OS: | |||
Priority: | normal | OS Version: | |||
Status: | closed | Product Version: | 3.1.10 | ||
Product Build: | Resolution: | fixed | |||
Projection: | none | ||||
ETA: | none | Fixed in Version: | 4.0.16 | ||
|
|||||
Summary: | 0004044: support for chained auth with client_cert and form auth | ||||
Description: |
I am wanting to implement client certificate authentication using Resin 3.1.8/9/10 and JSSE. I also need to make the site compatible for customers that don't require certificate auth. We have a custom authenticator that constructs and returns a subclass of Principal from its loginImpl method, and I would like to keep this behaviour. I quickly got basic Client Certificate authentication working, and thought I was home and hosed but when I put in our custom authenticator, I found that no matter what I returned from the loginImpl method, the result of request.getUserPrincipal() was always the same - an X500Name object. We use request.getUserPrincipal() throughout our code (an old style JSP based J2EE application) to get a Login object that contains the user's details - name, roles, etc. It is really imperative for us that this functionality continues. I tried overriding the ClientCertLogin class to provide a different version of authenticate and getUserPrincipal that returned our subclass of Principal, but even though I saw these methods being called, the request.getUserPrincipal was still an X500Name. This is the setup I tried: RESIN.CONF <http address="127.0.0.1" port="443" server-id=""> <jsse-ssl> <key-store-type>jks</key-store-type> <key-store-file>/Users/adam/resin-3.1.8/registry-adam.keystore</key-store-file> <password>changeit</password> <verify-client>required</verify-client> </jsse-ssl> </http> WEB.XML <!-- Authentication and Security Access Settings --> <!-- Uncomment for normal form based login --> <!-- <authenticator> <type>test.SiteAuthenticator</type> <init> <password-digest>none</password-digest> </init> </authenticator> <login-config auth-method='FORM'> <form-login-config> <form-login-page>/login.jsp</form-login-page> <form-error-page>/login.jsp?error</form-error-page> </form-login-config> </login-config> --> <!-- Uncomment for Certificate based login --> <login-config type="test.CertLogin"> <auth-method>CLIENT-CERT</auth-method> </login-config> <!-- End of authentication stuff --> CertLogin.java <snip> public Principal authenticate(HttpServletRequest request, HttpServletResponse response, ServletContext context) throws ServletException, IOException { Principal x = null; try { System.out.println("XXXXXXXXXXXXXXXXXXXXXXXXXXX - CertLogin authenticate called"); X509CertImpl cert = (X509CertImpl) request.getAttribute("javax.servlet.request.X509Certificate"); System.out.println("XXX - subject DN is " + cert.getSubjectDN()); System.out.println("XXX - subject DN name is " + cert.getSubjectDN().getName()); System.out.println("XXX - subject DN class is " + cert.getSubjectDN().getClass()); System.out.println("XXX - subject DN common name is " + ((X500Name) cert.getSubjectDN()).getCommonName()); // This bit does the authentication using our custom Authenticator and returns us a Login object if successful // This object should get stores as the session user and returned to the caller (AbstractHttpRequest.authenticate()) x = authenticator.login(request, response, context, ((X500Name) cert.getSubjectDN()).getCommonName(), null); if (x != null) { ((SessionImpl) request.getSession(true)).setUser(x); } } catch (Exception e) { e.printStackTrace(System.out); } finally { System.out.println("XXX - super authenticate returned a " + x); } return x; } <snip> Yeah, so this code and setup worked - in that each part of it was used, the code was called, the correct object was returned and put into the SessionImpl - but request.getUserPrincipal() stubbornly continued to return the X500Name!!! The reason for this is in the class HttpRequest (com.caucho.server.http.HttpRequest), on line 1548 (v3.1.10) - the method is called initAttributes, and it is responsible for initializing the attributes of the request before any processing by user code occurs. The method's source is as follows: /** * Initialize any special attributes. */ private void initAttributes() { _initAttributes = true; TcpConnection tcpConn = _tcpConn; if (! _isSecure || tcpConn == null) return; QSocket socket = tcpConn.getSocket(); String cipherSuite = socket.getCipherSuite(); super.setAttribute("javax.servlet.request.cipher_suite", cipherSuite); int keySize = socket.getCipherBits(); if (keySize != 0) super.setAttribute("javax.servlet.request.key_size", new Integer(keySize)); try { X509Certificate []certs = socket.getClientCertificates(); if (certs != null && certs.length > 0) { super.setAttribute("javax.servlet.request.X509Certificate", certs[0]); //super.setAttribute(com.caucho.server.security.AbstractAuthenticator.LOGIN_NAME, //certs[0].getSubjectDN()); } } catch (Exception e) { log.log(Level.FINER, e.toString(), e); } } The block where my problem is detects any client certificates and if found, sets the javax.servlet.request.X509Certificate attribute with the certificate. I have commented out line 1548 and 9 - where my problem occurs - because this sets the Certificate DN into the LOGIN_NAME attribute. This attribute is special - if we look at AbstractHttpRequest (the parent class) and the getUserPrincipal method: /** * Returns the Principal representing the logged in user. */ public Principal getUserPrincipal() { try { Principal user; user = (Principal) getAttribute(AbstractAuthenticator.LOGIN_NAME); if (user != null) return user; if (_session == null) getSession(false); // If the user object is already an attribute, return it. if (_session != null) { user = _session.getUser(); if (user != null) return user; } WebApp app = getWebApp(); if (app == null) return null; // If the authenticator can find the user, return it. AbstractLogin login = app.getLogin(); if (login != null) { user = login.getUserPrincipal(this, getResponse(), app); if (user != null) { getSession(true); _session.setUser(user); _response.setPrivateCache(true); } else { // server/123h, server/1920 // distinguishes between setPrivateCache and setPrivateOrResinCache // _response.setPrivateOrResinCache(true); } } return user; } catch (ServletException e) { log.log(Level.WARNING, e.toString(), e); return null; } } The line I have coloured RED will check the LOGIN_NAME attribute first and if that attribute is set, will not go on to check the Session's User - the Principal returned by the ClientCertLogin. I think that HttpRequest should not set the certificate DN into the LOGIN_USER attribute, but should allow the Session's user principal to be retrieved via the normal course of events. Commenting out line 1548 allows this to happen - I have made this modification and now I can set a subclass of Principal into the request as I would expect. It also allows developers to combine Client Cert authentication with other types, such as a form before authenticating. If the HttpRequest automatically populates its UserPrincipal from the SSL Certificate there's no way to make a decision about the authentication, or whether in fact the certificate is valid or the user allowed! Even if you return null from ClientCertLogin.authenticate, the next request will have the User Principal set from the Certificate. |
||||
Steps To Reproduce: | |||||
Additional Information: | |||||
Relationships | |||||
Attached Files: |
Notes | |||||
|
|||||
|
|
||||
|
|||||
|
|