2

Update 2022-05-17: As TLS 1.2 will become more and more obsolete I'd really be interested in an answer for this question

Question and Title updated 2021-04-02:

I posted a question of how to get the list of permitted CAs for a given https server And one person suggested using pyopenssl, which helped me solving my problem at the time.

So I tried out following code:

import socket
from OpenSSL import SSL

def get_client_cert_cas(hostname, port):
    ctx = SSL.Context(SSL.SSLv23_METHOD)
    sock = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
    # next line for SNI
    sock.set_tlsext_host_name(hostname.encode("utf-8"))
    sock.connect((hostname, port))
    return sock.get_client_ca_list()

It always returned no result. After some playing around I came up with following solution: Before calling sock.get_client_ca_list()) I perform a handshake with sock.do_handshake()

And this worked for all web servers, that I encountered up to this point.

Now however I encountered an https server where the SSL endpoint is on an F5 and now the code

def get_client_cert_cas(hostname, port):
    ctx = SSL.Context(SSL.SSLv23_METHOD)
    sock = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
    # next line for SNI
    sock.set_tlsext_host_name(hostname.encode("utf-8"))
    sock.connect((hostname, port))
    sock.do_handshake() # now client_ca_list will not be empty
    return sock.get_client_ca_list()

returns an empty result.

The openssl command openssl s_client -connect <hostname>:<port> -servername <hostname> returns well the correct result.

After Further investigation I found out, that the F5 uses TLSv1.3 and the 'working' servers used TLSv1.2

Now my last fix which is working is to force TLSv1.2

def get_client_cert_cas(hostname, port):
    ctx = SSL.Context(SSL.SSLv23_METHOD)
    # It seems I must ensure to not use TLSV1.3
    ctx.set_options(SSL.OP_NO_TLSv1_3)
    sock = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
    # next line for SNI
    sock.set_tlsext_host_name(hostname.encode("utf-8"))
    sock.connect((hostname, port))
    sock.do_handshake() # now client_ca_list will not be empty
    return sock.get_client_ca_list()

Is it to be expected, that get_client_ca_list() does not work with TLSV1.3 or is there an additional action to perform in the TLS1.3 case to have results for get_client_ca_list()?

Please note, that I don't insist on pyopenssl It is just the only solution, that I found so far.

If the problem can be solved with cryptography or any other package, then this is also fine with me.

gelonida
  • 5,327
  • 2
  • 23
  • 41
  • What *OpenSSL* versions are you using (from *Python* and command line)? Also could it be that in the command line same thing happens, but fallback is automatically attempted? Check `openssl s_client -no_tls1_3 -connect ...` and see if you get the same result. – CristiFati Apr 12 '21 at 05:01
  • @CristiFati you might be onto something: my openssl client was rather old and didn't support TLS1.3 (OpenSSL 1.0.2g 1 Mar 2016) When running a newer openssl client (OpenSSL 1.1.1f 31 Mar 2020), then openssl displays `No client certificate CA names sent` And only if I call it with `-no_tls1_3` I get the desired `Acceptable client certificate CA names` output. This seems to indicate that my real question is now whether it is normal, that TLS1.3 does not send client certificate CA names or whether this is a specific bug of the given F5 server – gelonida Apr 12 '21 at 12:04
  • Good we have that sorted out. Now what about *OpenSSL* version used from *Python*? It should be a newer one since you experience the error. I don't know what the *F5* is, but seems like this server doesn't support *TLSv1.3*. So, the next logical step would be that in the code to apply the same principle that did the trick in command line: `ctx.set_options(OpenSSL.SSL.OP_NO_TLSv1_3)`, or add some logic: try getting the *CA* list and if empty, try with the above flag set. – CristiFati Apr 12 '21 at 12:23

0 Answers0