2026-01-19 17:56:02 +08:00
|
|
|
|
"""
|
|
|
|
|
|
SSL 工具函数
|
|
|
|
|
|
提供统一的 SSL 上下文创建功能
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import ssl
|
|
|
|
|
|
|
2026-02-07 23:18:58 +08:00
|
|
|
|
from loguru import logger
|
|
|
|
|
|
|
2026-01-19 17:56:02 +08:00
|
|
|
|
|
2026-02-27 13:54:46 +08:00
|
|
|
|
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:
|
2026-01-19 17:56:02 +08:00
|
|
|
|
_SSL_CONTEXT = ssl.create_default_context()
|
|
|
|
|
|
|
2026-02-07 23:18:58 +08:00
|
|
|
|
_PROXY_SSL_CONTEXT: ssl.SSLContext | None = None
|
2026-02-27 13:54:46 +08:00
|
|
|
|
_PROFILE_SSL_CONTEXTS: dict[str, ssl.SSLContext] = {}
|
2026-02-07 23:18:58 +08:00
|
|
|
|
|
2026-01-19 17:56:02 +08:00
|
|
|
|
|
|
|
|
|
|
def get_ssl_context() -> ssl.SSLContext:
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取 SSL 上下文
|
|
|
|
|
|
|
|
|
|
|
|
优先使用 certifi 证书包,如果未安装则使用系统默认证书。
|
|
|
|
|
|
返回模块级缓存的 SSL 上下文实例。
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
ssl.SSLContext: SSL 上下文
|
|
|
|
|
|
"""
|
|
|
|
|
|
return _SSL_CONTEXT
|
2026-02-07 23:18:58 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2026-02-27 13:54:46 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|