title: Using ssl context.set_servername_callback in Python
body: <p>I have a goal of allowing an ssl client to select from a number of valid certificate pairs from the server. The client has a CA certificate which it will use to validate the certificate coming from the server.</p>
  
  <p>So to try to accomplish this, I'm using the <code>ssl.SSLContext.set_servername_callback()</code> on the server in combination with <code>ssl.SSLSocket.wrap_socket's parameter:</code>server_hostname` to try to allow the client to specify which keypair to use. Here's what the code looks like:</p>
  
  <p>Server code:</p>
  
  <pre><code>import sys
  import pickle
  import ssl
  import socket
  import select
  
  request = {'msgtype': 0, 'value': 'Ping', 'test': [chr(i) for i in range(256)]}
  response = {'msgtype': 1, 'value': 'Pong'}
  
  def handle_client(c, a):
      print("Connection from {}:{}".format(*a))
      req_raw = c.recv(10000)
      req = pickle.loads(req_raw)
      print("Received message: {}".format(req))
      res = pickle.dumps(response)
      print("Sending message: {}".format(response))
      c.send(res)
  
  def run_server(hostname, port):
      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
      s.bind((hostname, port))
      s.listen(8)
      print("Serving on {}:{}".format(hostname, port))
  
      try:
          while True:
              (c, a) = s.accept()
  
              def servername_callback(sock, req_hostname, cb_context, as_callback=True):
                  print('Loading certs for {}'.format(req_hostname))
                  server_cert = "ssl/{}/server".format(req_hostname)  # NOTE: This use of socket input is INSECURE
                  cb_context.load_cert_chain(certfile="{}.crt".format(server_cert), keyfile="{}.key".format(server_cert))
  
                  # Seems like this is designed usage: https://github.com/python/cpython/blob/3.4/Modules/_ssl.c#L1469
                  sock.context = cb_context
                  return None
  
              context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
              context.set_servername_callback(servername_callback)
              default_cert = "ssl/3.1/server"
              context.load_cert_chain(certfile="{}.crt".format(default_cert), keyfile="{}.key".format(default_cert))
              ssl_sock = context.wrap_socket(c, server_side=True)
  
              try:
                  handle_client(ssl_sock, a)
              finally:
                  c.close()
  
      except KeyboardInterrupt:
          s.close()
  
  if __name__ == '__main__':
      hostname = ''
      port = 6789
      run_server(hostname, port)
  </code></pre>
  
  <p>Client code:</p>
  
  <pre><code>import sys
  import pickle
  import socket
  import ssl
  
  request = {'msgtype': 0, 'value': 'Ping', 'test': [chr(i) for i in range(256)]}
  response = {'msgtype': 1, 'value': 'Pong'}
  
  
  def client(hostname, port):
      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      print("Connecting to {}:{}".format(hostname, port))
      s.connect((hostname, port))
  
      ssl_sock = ssl.SSLSocket(sock=s, ca_certs="server_old.crt", cert_reqs=ssl.CERT_REQUIRED, server_hostname='3.2')
  
      print("Sending message: {}".format(request))
      req = pickle.dumps(request)
      ssl_sock.send(req)
  
      resp_raw = ssl_sock.recv(10000)
      resp = pickle.loads(resp_raw)
      print("Received message: {}".format(resp))
  
      ssl_sock.close()
  
  if __name__ == '__main__':
      hostname = 'localhost'
      port = 6789
      client(hostname, port)
  </code></pre>
  
  <p>But it's not working. What seems to be happening is <code>servername_callback</code> is getting called, is getting the specified "hostname", and the call to <code>context.load_cert_chain</code> within the callback is not failing (though it does fail if it's given path that doesn't exist). However, the server always returns the certificate pair that was loaded prior to calling <code>context.wrap_socket(c, server_side=True)</code>. So my question is: is there some way, within the <code>servername_callback</code>, to modify the keypair used by the ssl context, and get that keypair's certificate to be used for the connection?</p>
  
  <p>I should also note that I checked the traffic, and the server's certificate is NOT being sent until after the <code>servername_callback</code> function returns (and will never be sent if it fails to complete successfully, or returns a "failure" value).</p>