testssl.sh
testssl.sh copied to clipboard
Display whether server requests/requires a Client Certificate
One of the big challenges in debugging TLS/SSL connections is determining whether the server is requesting/requiring a client certificate. With openSSL and with testssl.sh, it is unclear. Both tools output nice information about the negotiated protocol, but neglect to complete the handshake sequence afterwards. openssl outputs a cryptic message (if any).
It would be great if testssl.sh could CLEARLY indicate whether: (a) server has requested a client certificate; and (b) whether the connection fully succeeded (i.e., did server connect anyway if no client cert was provided / specified)? Having spent hours troubleshooting TLS connections with "handshake_failure" messages (a Java 'catch-all'), having this detail would be great.
This topic mentions the issue with openssl. It is possble to detect that an CertificateRequest has been sent from the server. https://security.stackexchange.com/questions/101511/determine-if-a-server-is-asking-for-a-client-certificate-using-openssl-s-client
IMPLEMENTATION CONSIDERATION: By using s_client in openssl, the CA list can be viewed and checked. However some servers only request client authentication after a specific URL is requested. To obtain the list in this case it is necessary to use the -prexit command and send an HTTP request for an appropriate page.
???
Start 2020-08-27 17:27:01 -->> 213.154.225.246:443 (secure.cacert.org) <<--
Further IP addresses: 2001:7b8:3:9c::246
rDNS (213.154.225.246): 246.224-27.225.154.213.in-addr.arpa. secure.cacert.org.
Service detected: certificate-based authentication => skipping all HTTP checks
Hi Dirk,
As you noted, I believe that in most cases testssl.sh will show the "certificate-based authentication => skipping all HTTP checks" message if certificate-based authentication is required. However, my reading of the code is that this message won't appear if a STARTTLS protocol is specified, since service_detection()
is not called in that case.
Even if a STARTTLS protocol is specified, there is still a chance that a message will be displayed. For example, run_breach()
will indicate "cannot be tested (server side requires x509 authentication)" if client authentication is required. But, if a STARTTLS protocol is specified and not all tests are performed, then nothing may be displayed.
Basically, testssl.sh informs the user that certificate-based authentication is required, if testssl.sh is prevented from performing some test as a result of the requirement, but not otherwise. For that same reason, testssl.sh does not indicate if the server requested a certificate but successfully connects even if one is not provided, since the mere request for a certificate does not interfere with any of testssl.sh's testing.
If I understand correctly, the request here is to indicate (even when STARTTLS is specified) whether the server requests a certificate, and if so whether providing a certificate is required or optional. There may additionally be a request to display the CA list that the server sends when it requests a certificate.
Hi David,
However, my reading of the code is that this message won't appear if a STARTTLS protocol is specified, since service_detection() is not called in that case
In determine_optimal_proto()
we'll call sclient_auth()
and that populates CLIENT_AUTH
accordingly -- for both STARTTLS and plain TLS. So that should(TM) work. But honestly I can't remember myself testing STARTTLS connections using a certificate --as I've never seen a STARTTLS connection requiring a certificate on a TLS layer. Only ones which the application uses for authentication but that was way back. Are you talking about that type of certificate?
If I understand correctly, the request here is to indicate (even when STARTTLS is specified) whether the server requests a certificate, and if so whether providing a certificate is required or optional
Ok, I think we can somewhere add this more clearly.
What is optional in this context? Do you have an example or doc/RFC?
In
determine_optimal_proto()
we'll callsclient_auth()
and that populatesCLIENT_AUTH
accordingly -- for both STARTTLS and plain TLS. So that should(TM) work.
Yes, CLIENT_AUTH
should be set if client authentication is required whether STARTTLS is used or not. But, sclient_auth()
just sets CLIENT_AUTH
, it doesn't produce any output. There are a few places where output is produced if CLIENT_AUTH
is true, but they may not be clear, and the most obvious one (the one output by determine_service()
won't appear if STARTTLS is used.
But honestly I can't remember myself testing STARTTLS connections using a certificate --as I've never seen a STARTTLS connection requiring a certificate on a TLS layer. Only ones which the application uses for authentication but that was way back. Are you talking about that type of certificate?
I don't know of cases in which certificate-based client authentication is used along with STARTTLS. I just assumed it was a possibility, since the I thought the TLS protocol was the same whether STARTTLS is used or not.
I think the first, easy step to address this issue would be to modify sclient_auth()
. At the moment, the first line of sclient_auth()
returns 0 if the call to $OPENSSL
returned 0, so it will only detect if client authentication is required. If client authentication is optional, then OpenSSL's output will include CertificateRequest
, but the return value will be 0.
What is optional in this context? Do you have an example or doc/RFC?
Unfortunately, the CertificateRequest message differs between TLS 1.3 and TLS 1.2 and earlier.
In TLS 1.2 and earlier (RFC 5246, Section 7.4.4), the CertificateRequest consists of:
- The "types" of certificates the server will accept (e.g, RSA, ECDSA, DH);
- acceptable signature algorithms; and
- the distinguished names of acceptable CAs.
In TLS 1.3 (RFC 8446, ), the CertificateRequest contains an extensions field. The extensions can specify the distinguished names of acceptable CAs and the acceptable signature algorithms, but other information may be provided as well.
It seems that OpenSSL will print out information about contents of the CertificateRequest message, but LibreSSL will not. So, the easy way to get the CA list would be to just pull it from the output of $OPENSSL s_client
. However, if we want something that will work with LibreSSL, then we'll have to extract and parse the CertificateRequest message in order to obtain the list.
The same applies if we want to provide information about the signature algorithms the server lists as acceptable. OpenSSL prints out the information, but LibreSSL does not. Providing this information would be similar to the request #1519. It would allow testssl.sh to warn if the server listed weak algorithms as acceptable.
Thanks for your PR, David.
With what I said above I didn't want to discourage you from implementing that. And actually I was about to say that but it seems it wasn't needed (PR). :-)
Just remove the WIP if you're think it's ready. (I accidentally pressed the review button).
Besides looking at the code it would be great to try that in practice. However I lack servers to test against. So any input from you or others (also @lesterw1 ) would be appreciated.
Please see David's PR merged.
For those who haven't noticed: @dcooper16 provided a(nother) PR for those cases where a client authentication is required per URL.
if someone has a hostname handy to test against that would be great.
On nginx per-URL isn't that easily possible, see http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_client_certificate, otherwise I have helped here.
Hmm... this seems done when the PR #1990 was merged.
Thus closing