Files
Aether/_deprecated_py_src/utils/ssl_utils.py

118 lines
3.4 KiB
Python
Raw Normal View History

"""
SSL 工具函数
提供统一的 SSL 上下文创建功能
"""
import ssl
from loguru import logger
def _create_default_ssl_context() -> ssl.SSLContext:
try:
import certifi
return ssl.create_default_context(cafile=certifi.where())
except ImportError:
return ssl.create_default_context()
try:
_SSL_CONTEXT = _create_default_ssl_context()
except Exception:
_SSL_CONTEXT = ssl.create_default_context()
_PROXY_SSL_CONTEXT: ssl.SSLContext | None = None
_PROFILE_SSL_CONTEXTS: dict[str, ssl.SSLContext] = {}
def get_ssl_context() -> ssl.SSLContext:
"""
获取 SSL 上下文
优先使用 certifi 证书包如果未安装则使用系统默认证书
返回模块级缓存的 SSL 上下文实例
Returns:
ssl.SSLContext: SSL 上下文
"""
return _SSL_CONTEXT
def get_proxy_ssl_context(expected_fingerprint: str | None = None) -> ssl.SSLContext:
"""
获取用于代理连接的 SSL 上下文连接 aether-proxy TLS 端口
当前使用 CERT_NONE不验证证书因为 aether-proxy 使用自签名证书
expected_fingerprint 参数预留供未来实现指纹校验
Args:
expected_fingerprint: 预期的证书 SHA-256 指纹hex预留参数
Returns:
ssl.SSLContext: 代理专用 SSL 上下文
"""
global _PROXY_SSL_CONTEXT
if expected_fingerprint:
logger.warning("TLS 证书指纹校验尚未实现, fingerprint={} 被忽略", expected_fingerprint)
if _PROXY_SSL_CONTEXT is None:
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
_PROXY_SSL_CONTEXT = ctx
# TODO: 实现基于 expected_fingerprint 的证书指纹校验
return _PROXY_SSL_CONTEXT
def _build_claude_code_ssl_context() -> ssl.SSLContext:
"""构建 Claude Code best-effort TLS 配置。
说明Python/OpenSSL 无法完整模拟 Node.js ClientHello
这里仅做可控项的尽力对齐ALPN/TLS 版本/常见 cipher 偏好
"""
ctx = _create_default_ssl_context()
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
try:
ctx.maximum_version = ssl.TLSVersion.TLSv1_3
except Exception:
pass
try:
ctx.set_alpn_protocols(["h2", "http/1.1"])
except Exception:
pass
try:
ctx.set_ciphers(
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES128-GCM-SHA256:"
"ECDHE-ECDSA-AES256-GCM-SHA384:"
"ECDHE-RSA-AES256-GCM-SHA384:"
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-RSA-CHACHA20-POLY1305"
)
except Exception:
pass
return ctx
def get_ssl_context_for_profile(tls_profile: str | None = None) -> ssl.SSLContext:
"""按 profile 返回 SSL 上下文。"""
profile = str(tls_profile or "").strip().lower()
if not profile:
return get_ssl_context()
if profile in _PROFILE_SSL_CONTEXTS:
logger.debug("复用 TLS profile SSL context: {}", profile)
return _PROFILE_SSL_CONTEXTS[profile]
if profile == "claude_code_nodejs":
logger.info("启用 TLS profile: {}best-effort", profile)
ctx = _build_claude_code_ssl_context()
else:
logger.warning("未知 TLS profile: {},回退默认 SSL context", profile)
ctx = get_ssl_context()
_PROFILE_SSL_CONTEXTS[profile] = ctx
return ctx