SSL Server socket want auth option

11,310

(Multiple edits, following a number of comments.)

setWantClientAuth is used to request client certificate authentication, but keep the connection if no authentication is provided. setNeedClientAuth is used to request and require client certificate authentication: the connection will terminate if no suitable client certificate is presented.

You can find more on this topic in the Client Certificate section of the TLS specification. For a bit of history:

  • In version 1.2, it says "If no suitable certificate is available, the client MUST send a certificate message containing no certificates.". Before that, it was just a "SHOULD", but the Sun JSSE clients send an empty list this case anyway.

  • Version 1.2 also added:

> Also, if some aspect of the certificate chain was unacceptable (e.g.,
> it was not signed by a known, trusted CA), the server MAY at its
> discretion either continue the handshake (considering the client
> unauthenticated) or send a fatal alert.

This gives some flexibility regarding what to do when a unacceptable certificate is sent. The JSSE chooses to send a fatal alert. (`setWantAuth` could in principle carry on with invalid certificates, but not treat the peer as authenticated, as if no client certificate was sent, but this isn't the case.)

Previous versions of the TLS spec said "*If client authentication is required by the server for the handshake to continue, it may respond with a fatal handshake failure alert.*". This is the difference between need or want as implemented in the JSSE: using "need", the server responds with a fatal handshake failure, whereas using "want", the server carries on with the connection, but doesn't treat it as authenticated.

I initially though that your client wasn't sending its certificate when you were using "need". Indeed, most clients won't send a client certificate at all if they can't find a client certificate that is issued by one of the issuers listed in the CA names send by the server during its request (or if the client can't build the chain themselves, which is a common problem). By default, the JSSE uses the CAs in the truststore to build that list. For this reason, your client would probably not send a client certificate at all if a suitable issuer isn't in the server's truststore.

You can check whether a client certificate is sent using Wireshark. If you're not running on a port normally used with SSL/TLS, you'll need to right click on a packet and choose "Decode As... -> Transport -> SSL".

There, you should see a Certificate Request message coming from the server. (For some reason, when I'm using the default JRE truststore with Wireshark, that message appears as an "Encrypted Handshake Message", just after the "Server Key Exchange" message. However, it's not encrypted: you can clearly see a number of CA names if you look at the ASCII rendering of the packet in the bottom panel. Perhaps this is because this message is too long, I'm not sure.) With a shorter list, for example, a trust store with a single CA, Wireshark you decode this properly as a Certificate Request message, and you should see the list of accepted CAs in the "Distinguished Names" section.

You should also see a Certificate message coming from the client (not the one coming from the server, of course). If the server requests (with want or need) a certificate, you should always see this message from the client anyway.

Assuming you have access to a test CA (with a client certificate issued by that CA), you can try the following experiments.

  • If you set up your trust store with that test CA cert, use setWantClientAuth(true), the client will send its client certificate, and the connection will proceed. The server can then get the client certificate from the SSLSession, as expected.

  • If you use the default trust store (that doesn't contain your test CA cert), use setWantClientAuth(true), the CA DN will not be in the Certificate Request. The client will send a Certificate message, but the certificate list will be empty (Certificates Length: 0 in Wireshark). Here, the client is actually not sending a client certificate, even if its keystore is configured to do so, simply because it can't find a suitable match. The connection will proceed (you may get an exception if you try to read the peer certificate from the SSLSession on the server, but that's not fatal). This is the use-case for setWantClientAuth(true); setNeedClientAuth(true) would have ended the connection immediately.

  • For the sake of this experiment, you can fake the list of DNs sent by the server in Java.

      KeyManagerFactory kmf = //... Initialise a KMF with your server's keystore
    
      TrustManagerFactory tmf = TrustManagerFactory
          .getInstance(TrustManagerFactory.getDefaultAlgorithm());
      tmf.init((KeyStore) null); // Use the default trust store
      TrustManager[] trustManagers = tmf.getTrustManagers();
      final X509TrustManager origTrustManager = (X509TrustManager) trustManagers[0];
      final X509Certificate caCert = // Load your test CA certificate here.
      X509TrustManager fakeTrustManager = new X509TrustManager() {
          public void checkClientTrusted(X509Certificate[] chain,
                  String authType) throws CertificateException {
              // Key the behaviour of the default trust manager.
              origTrustManager.checkClientTrusted(chain, authType);
          }
    
          public void checkServerTrusted(X509Certificate[] chain,
                  String authType) throws CertificateException {
              // Key the behaviour of the default trust manager.
              origTrustManager.checkServerTrusted(chain, authType);
          }
    
          public X509Certificate[] getAcceptedIssuers() {
              // This is only used for sending the list of acceptable CA DNs.
              return new X509Certificate[] { caCert };
          }
      };
      trustManagers = new X509TrustManager[] { fakeTrustManager };
    
      SSLContext sslContext = SSLContext.getInstance("TLS");
      sslContext.init(kmf.getKeyManagers(), trustManagers, null);
    

    In this case, the Certificate Request message sent by the server should contain the your test CA's DN. However, that CA isn't actually trusted by the trust manager, which still uses the default values.

    The client will send its certificate, but the server will reject it, saying "javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path validation failed", and this will end the connection. This is at least the implementation using the SunJSSE provider, using the PKIX or SunX509 trust managers. This is also consistent with the JSSE specification of the trust manager: "The primary responsibility of the TrustManager is to determine whether the presented authentication credentials should be trusted. If the credentials are not trusted, the connection will be terminated."

The key point here is that if you're in a position to get the client certificate from the SSLSession, that certificate should have been authenticated by the trust manager (by this I mean the SSLSession you get once the handshake has completed, with SSLSocket.getSession() not the one you get during the handshake using getHandshakeSession(), introduced in Java 7).

You seem to indicate in comments that you're using another JSSE provider, and that your client was sending a client certificate anyway, whether or not the CA cert was in the server's trust store, because you also had another different CA cert in your trust store with the same Subject DN. Assuming those two CA certificates have different keys (otherwise they would effectively be the same CA), this would be a rather serious bug: applications that use client-certificate authentication are entitled to expect the client certificate to have been verified by the trust manager (as specified in the JSSE reference guide). If you're using Apache Tomcat, once it gets the client certificate, the remote connection is considered as authenticated with this cert. If at that point the servlet is able to use a client certificate that can't have been verified, no authentication has actually been made, which would be a serious flaw.

Share:
11,310
Jim
Author by

Jim

Updated on July 18, 2022

Comments

  • Jim
    Jim almost 2 years

    Concerning SSLServerSocket.setWantClientAuth:
    If this is set to true if the client chooses to not send a certificate the negotiation continues.
    Also I noticed this also happens if the client sends a certificate but is not part of the truststore.The negotiation does not fail either in this case.

    So what is the use case of this setting?

  • Cratylus
    Cratylus about 11 years
    The OP says that if the client certificate is send and is not part of the truststore the negotiation also continues.I find this strange though.Shouldn't the negotiation fail?
  • Bruno
    Bruno about 11 years
    @Cratylus, the OP doesn't actually say the client certificate is sent. I've just made a client send a cert not trusted by the default trust store, by changing the accepted issuer list on the server, and as expected, you get a PKIX exception: the connection fails.
  • Cratylus
    Cratylus about 11 years
    You are right.It is not clear from the OP (although I think this is what OP means in I noticed this also happens if the client certificate is not part of the truststore).Let's wait for OP to clarify this
  • user207421
    user207421 about 11 years
    @Cratylus No, it shouldn't fail. You're thinking of 'needClientAuth'.
  • Jim
    Jim about 11 years
    @Bruno:When the client sends an untrusted certificate the negotiation does not fail.Made the question clearer.I noticed that you mention PKIX exception.Are you using PKIX as trustmanager factory?Perhaps that is the difference because I tried with SunX509 and the connection continues for an unknown certificate.
  • Jim
    Jim about 11 years
    My question is: since when the client sends an untrusted certificate and the connection continues despite that, when is this setting actually useful?
  • user207421
    user207421 about 11 years
    @Jim The client isn't allowed to send an untrusted certificate under RFC 2246. When the server asks for the cert, it sends its trusted root certs, and the client is only allowed to send a certificate if its signer chain contains one of those trusted root certs. One useful case is if the client has such a cert you needn't present him with a login form, but if he doesn't you allow the connection to continue and navigate to the login form.
  • Jim
    Jim about 11 years
    @Bruno:Yes I am sure.I will post a wireshark on Monday.Don't have access to the code now.BTW could it be due to you using PKIX and me using SunX509?For trustmanagerfactory?
  • Jim
    Jim about 11 years
    I see that the RFC says in 7.4.6:...If no suitable certificate is available, the client should send a certificate message containing no certificates.This is a SHOULD not a must.So I was wondering.Could it be that since the certificate is optional in wantClientAuth if the certificate send is not trusted it does not really matter?I mean the client might have send no certificate at all and be fine.Could it be the reason of what I am seeing?I.e. the connection to be accepted?
  • Jim
    Jim about 11 years
    Also it does not say what happens if the list of acceted issuer is empty.I mean I was just wondering what happens if we set the wantClientAuth and replace the trust manager with a "fake" trustmanager
  • Jim
    Jim about 11 years
    @Bruno:Just realized this!What essentially happens is this:I have 2 custom made issuing CAs with the same CN.I sign 2 different client certificates using each issuing CA.In the server I have as trusted one of the 2 CAs.So on Certificate Request the DN of this CA is requested.The client indeed has a certificate signed by a CA with this CN BUT it is actually signed by the other CA.The client sends the certificate.The verification should fail but the connection is established instead!I was thinking perhaps it makes sense since if the client did not send anything it would not matter
  • Jim
    Jim about 11 years
    Check the last comment I made to Bruno.I realized something about the scenario
  • Jim
    Jim about 11 years
    @Bruno:Why are you asking this?Yes I can via servletRequest.getAttribute("javax.servlet.request.X509Certi‌​ficate");.I run inside Tomcat and this api does not return null but the client certificate.And the connection is accepted. So SSLSession does return the client certificate.Not sure why wouldn't it.
  • Jim
    Jim about 11 years
    @Bruno:What are you refering to with it shouldn't really?a)To the fact that I can get the client cert?Why it shouldn't be provided or b)that the connection is accepted?Why it shouldn't?The RFC you link says that the server may accept/reject the connection.
  • Jim
    Jim about 11 years
    @Bruno:So you are saying that servletRequest.getAttribute("javax.servlet.request.X509Certi‌​ficate"); should not return the client cert.This is our only difference in our test case right?You also notice connection accepted but you don't get the client cert? And you are saying that is the correct thing to do?Why?I mean the client already connected.What makes the client considered "untrusted" just because you can't get the client cert.The client already has access to resources anyway right?He connected
  • Jim
    Jim about 11 years
    @Bruno:Just to understand.We both notice that if a client sends an untrusted certificate, JSSE does not break the connection.Right?The only difference we have is that I say that I can get the certificate from the SSLSession and you can not.Is this right?Also you consider very bad the fact that in an established connection with a client that send an untrusted certificate,I am able to get the certificate from the session.I don't understand the concern though.Why is it important?I mean he might as well have send nothing and still be connected (with want).So what if I can access this "untrusted"
  • Jim
    Jim about 11 years
    certificate?You also observe this behavior or not?(not enough space and continued here the comment)
  • Jim
    Jim about 11 years
    @Bruno:From another point of view:The RFC you linked says that the server could send an empty list of trusted DN in the certificate request.In this case the client may send any certificate.The connection with JSSE will succeed with want and now should I be able to access that certificate or not?I mean does the empty list of DN signify that the server trusts every CA/certificate?
  • Jim
    Jim about 11 years
    @Bruno:Ok.In the configuration we have a custom SSLImplementation set.As part of the implementation a code does:ssl.getSession().getPeerCertificateChain() where ssl is the client socket. This code I see in debugging has the client certificates.Later inside my servlet if I do:getAttribute("javax.servlet.request.X509Certificate"); I get the certificates.So the SSLSession does contain the peer certificates.You are saying it shouldn't have?Also it has clientAuth="want"
  • Jim
    Jim about 11 years
    @Bruno:There is a custom trustmanager but it doesn't do anything special but use a custom truststore.The only thing of interest I noticed is this: TrustManagerFactory.getInstance(algorithm, getJSSEProvider()); and the provider is not the default JSSE but a third party library.Is this what makes a difference?
  • Bruno
    Bruno about 11 years
    @Jim, probably. Which library is it? If it lets you get the client certificate from the SSLSession without checking that the certificate is actually trusted, it has a serious bug.
  • Jim
    Jim about 11 years
    @Bruno:RSA-BSAFE-SHARE. Why is this a bug though?
  • Jim
    Jim about 11 years
    @Bruno:I see what you mean.In your test you said you can not get it right?
  • Bruno
    Bruno about 11 years
    @Jim, I've edited my answer, and I'll remove most of my comments (I'll delete this one too), since it's quite messy. In my tests, the connection fails if I present a client cert that can't be verified against the trust store.
  • Jim
    Jim about 11 years
    @Bruno:This is not correct in the answer:The JSSE chooses to send a fatal alert. (setWantAuth could in principle carry on with invalid certificates, but not treat the peer as authenticated, as if no client certificate was sent, but this isn't the case.).As we saw in want the connection is not dropped hence no hanshake alert
  • Bruno
    Bruno about 11 years
    @Jim, this is correct: when no certificate is sent, "want" continues with the connection, but when an invalid cert is send, "want" drops the connection. The provider you're using might not do that, but it should, since the "the primary responsibility of the TrustManager is to determine whether the presented authentication credentials should be trusted. If the credentials are not trusted, the connection will be terminated."
  • user207421
    user207421 about 11 years
    @Jim If the list of accepted issuers is empty the client cannot send a complying certificate at all.