You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用自签名SSL CA证书搭建带mTLS的aiohttp服务器?

问题:私有Python API服务器客户端证书认证失败

背景

在Ubuntu 22服务器搭建仅允许指定设备访问的Python Web/API服务器,无域名和第三方CA证书,目标是通过分发.pfx证书实现客户端认证——仅安装证书的设备能访问,无证书的客户端即使忽略安全警告也无法访问。

已生成自签名CA、服务器证书及客户端.pfx,但客户端安装后访问报错,服务器日志提示peer did not return a certificate。

现有证书生成步骤

# 生成CA密钥和证书
openssl genrsa -des3 -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -sha256 -days 824 -out ca.crt

# 生成服务器证书
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 824 -sha256 -extfile server.ext

# 生成客户端证书及pfx
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 824 -sha256 -extfile client.ext

openssl pkcs12 -export -out client.p12 -inkey client.key -in client.crt -certfile ca.crt

注:因OpenSSL 3.2.1存在密码bug导致iOS无法安装pfx,当前使用OpenSSL 1.1.1

错误信息

  • 客户端浏览器报错:ERR_CONNECTION_REFUSED
  • 服务器日志报错:
Traceback (most recent call last):
  File "/usr/lib/python3.10/asyncio/selector_events.py", line 213, in _accept_connect
    await waiter
  File "/usr/lib/python3.10/asyncio/sslproto.py", line 534, in data_received
    ssldata, appdata = self._sslpipe.feed_ssldata(data)
  File "/usr/lib/python3.10/asyncio/sslproto.py", line 188, in feed_ssldata
    self._sslobj.do_handshake()
  File "/usr/lib/python3.10/ssl.py", line 975, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: PEER_DID_NOT_RETURN_A_CERTIFICATE] peer did not return a certificate

服务器代码

import asyncio
from aiohttp import web
import json
import ssl
import logging

async def handler(req):
    packed = await req.read()
    data = json.loads(packed)
    return web.Response(text=f"Thank you for your {data}")

async def create_server():
    app = web.Application()

    ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    ssl_context.load_cert_chain(certfile='server.crt', keyfile='server.key')
    ssl_context.load_verify_locations('ca.crt')
    ssl_context.verify_mode = ssl.CERT_REQUIRED

    app.add_routes([
        web.get('/', handler),
    ])

    runner = web.AppRunner(app)
    await runner.setup()
    site = web.TCPSite(runner, '0.0.0.0', 2343, ssl_context=ssl_context)
    await site.start()

    return app

async def main():
    app = await create_server()
    await asyncio.sleep(10_000)

if __name__ == '__main__':
    asyncio.run(main(), debug=True)

解决方案

1. 修正证书扩展配置(关键)

服务器和客户端的.ext文件必须正确配置,否则客户端不会主动发送证书:

server.ext 内容

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
# 替换为服务器IP或主机名,可添加多个
IP.1 = 192.168.1.100
DNS.1 = your-server-hostname

client.ext 内容

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = clientAuth

必须添加extendedKeyUsage = clientAuth,否则客户端无法将此证书用于身份认证

2. 重新生成证书

删除旧的服务器/客户端证书、csr、serial文件,重新执行生成命令(确保使用上述.ext文件)。

3. 客户端证书安装验证

  • Windows:将client.p12安装到当前用户-个人-证书存储,确认证书“预期目的”包含“客户端身份验证”。
  • iOS:安装后需到「设置-通用-VPN与设备管理」信任该证书,部分浏览器访问时需手动指定使用该证书。

4. 服务器SSL上下文补充配置

在现有代码基础上添加协议与加密套件配置:

ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_context.load_cert_chain(certfile='server.crt', keyfile='server.key')
ssl_context.load_verify_locations('ca.crt')
ssl_context.verify_mode = ssl.CERT_REQUIRED
# 添加以下配置,确保服务器正确请求客户端证书
ssl_context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1  # 禁用不安全的旧协议
ssl_context.set_ciphers('ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384')  # 指定安全加密套件

证书使用场景与分发说明

  • CA证书(ca.crt):仅在服务器端存储,用于验证客户端/服务器证书的合法性,无需分发给客户端。
  • 服务器证书(server.crt + server.key):仅部署在服务器端,用于标识服务器身份,不对外分发。
  • 客户端证书(client.p12/pfx):分发给授权访问的设备,安装后设备可向服务器提供身份凭证;建议为每个设备生成唯一证书,便于权限管理。

内容的提问来源于stack exchange,提问作者Fabian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 15:15:06