私は、sslクライアントがサーバからの有効な証明書ペアをいくつか選択できるようにすることを目標にしています。クライアントには、サーバーからの証明書を検証するために使用するCA証明書があります。Pythonでssl context.set_servername_callbackを使用する
これを達成するために、ssl.SSLSocket.wrap_socket's parameter:
server_hostnameと組み合わせてサーバー上でssl.SSLContext.set_servername_callback()
を使用して、クライアントが使用するキーペアを指定できるようにします。
Serverコード:
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)
クライアントコード:
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)
しかし、それは働いていないここでは、コードは次のようになります。 servername_callback
が呼び出され、指定された「ホスト名」が取得され、コールバック内でcontext.load_cert_chain
への呼び出しが失敗していないと思われるものがあります(存在しないパスが指定されていると失敗します)。ただし、サーバーは常にcontext.wrap_socket(c, server_side=True)
を呼び出す前に読み込まれた証明書のペアを返します。だから私の質問は:servername_callback
の中には、sslコンテキストで使用される鍵ペアを変更し、その鍵ペアの証明書を接続に使用する方法がありますか?
また、servername_callback
関数が返されるまで、トラフィックがチェックされ、サーバーの証明書が送信されないことに注意してください。正常に完了しなかった場合、または「失敗」値を返した場合は送信されません。あなたのコールバックで
は 'set_servername_callback'は、サーバーではなく、クライアントによって使用されます。 C:[サーバー名表示(SNI)の実装方法](http://stackoverflow.com/q/5113333/608639)と[SNIを使用して複数のドメインを1つのボックスに表示する](http: /stackoverflow.com/q/22373332/608639)。サーバを 's_client'で接続することでテストできます:' openssl s_client -connect: -tls1 -servername '。 '-servername'はSNI拡張です。 –
jww