如何使用 TLS 连接与服务器通信?

How to communicate with a server using a TLS connection?

我正在使用 python 和 SSL 模块与服务器建立 TLS 连接。然后我想向这个服务器发送一些数据。为此,我在名为“cert.pem”的文件。尽管我的服务器代码工作正常,但我的客户端代码无法连接到服务器。我只是模仿了 SSL module example 但我不明白为什么它会失败。

工作正常的服务器代码

import socket, ssl

cert_chain  = 'cert.pem'
host_addr   = socket.gethostbyname(socket.gethostname())
host_port   = 10023

context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(certfile=cert_chain)
bindsocket = socket.socket()
bindsocket.bind((host_addr, host_port))
bindsocket.listen(5)

while True:
  print("Waiting for client")
  newsocket, fromaddr = bindsocket.accept()
  conn = context.wrap_socket(newsocket, server_side=True)
  print("SSL established. Peer: {}".format(conn.getpeercert()))
  buf = b''  # Buffer to hold received client data
  try:
    while True:
      data = conn.recv(4096)
      if data:      # Client sent us data. Append to buffer
        buf += data
      else:         # No more data from client. Show buffer and close connection.
        print("Received:", buf)
        break
  finally:
    print("Closing connection")
    conn.shutdown(socket.SHUT_RDWR)
    conn.close()

失败的客户端代码

import socket, ssl

server_addr = '**.***.***.***'
server_port = 3335
cert_chain  = 'cert.pem'

context = ssl.create_default_context()
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations("/etc/ssl/certs/ca-bundle.crt")
conn = context.wrap_socket(socket.socket(socket.AF_INET), server_hostname=server_addr)
conn.connect((server_addr, server_port))

conn.send(b"Hello, world!")
conn.close()

当我 运行 客户端代码时,我得到这个错误:

ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1091)

然而,当我简单地使用 时,我能够发送数据:

openssl s_client -connect 127.0.0.1:10023

问题

我非常感谢任何改进代码的建议。

我在这里看到两个问题:

a) 根据您的代码,您的服务器侦听端口 10023,但您的客户端尝试连接到 3335。但这可能只是您发布的代码中的一个问题。如果它在您的代码中 运行,您将根本无法建立连接。

b) 当客户端无法验证服务器证书时,您会得到一个 CERTIFICATE_VERIFY_FAILED。例如,当您浏览到 https://whosebug.com 时,您会在地址栏中看到某种 lock-symbol,表明您的连接是安全的。这是因为服务器提供的证书对域 whosebug.com 有效且受信任。在您的情况下,您的证书很可能既无效也不可信。
为此,证书需要将 IP 地址或主机名(无论您在 wrap_socket 方法中作为 server_hostname 提供什么)作为 SAN 并且证书需要由受信任的 CA。由于您可能使用的是 self-signed 证书,因此情况并非如此,因为您的 trust-store 是 /etc/ssl/certs/ca-bundle.crt。当您颁发 self-signed 证书时,您可以将 IP/hostname 添加到 SAN 和 CN,并且在您的代码中,您可以尝试将证书添加到 /etc/ssl/certs/ca-bundle.crt.
出于开发目的,您可以将 verify_mode 设置为 CERT_NONE(参见 doc),这应该可以暂时解决您的所有问题。不过,这不应在生产中使用。