ForcePilot/backend/package/yuxi/channel/gateway/device_identity.py
Kris ecd3c90e80 feat(channel/gateway): 新增完整网关通道模块
新增设备身份管理、认证限流、并发通道、Webhook路由、RBAC权限控制、SSE/轮询降级等全套网关通道功能,包含:
1. 设备身份生成与签名验证
2. 设备令牌认证与速率限制
3. 内存+数据库双重设备注册表
4. 并发通道限流管理
5. Webhook安全处理与路由
6. RBAC权限校验系统
7. OpenAI API兼容适配层
8. Tailscale认证支持
9. HTTP轮询降级机制
2026-05-21 10:26:33 +08:00

69 lines
2.1 KiB
Python

import hashlib
import logging
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
logger = logging.getLogger(__name__)
DEVICE_ID_HEX_LENGTH = 16
def generate_device_identity() -> tuple[str, str, str]:
"""生成 Ed25519 设备身份。
Returns:
(device_id, public_key_pem, private_key_pem)
"""
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
public_key_pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
).decode("utf-8")
private_key_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(b"yuxi-device-key"),
).decode("utf-8")
device_id = _derive_device_id_from_key(public_key)
logger.info("Generated device identity: device_id=%s", device_id)
return device_id, public_key_pem, private_key_pem
def derive_device_id(public_key_pem: str) -> str:
public_key = serialization.load_pem_public_key(public_key_pem.encode("utf-8"))
return _derive_device_id_from_key(public_key)
_PRIVATE_KEY_PASSWORD = b"yuxi-device-key"
def sign_challenge(private_key_pem: str, challenge: bytes) -> bytes:
private_key = serialization.load_pem_private_key(
private_key_pem.encode("utf-8"),
password=_PRIVATE_KEY_PASSWORD,
)
return private_key.sign(challenge)
def verify_signature(public_key_pem: str, challenge: bytes, signature: bytes) -> bool:
try:
public_key = serialization.load_pem_public_key(public_key_pem.encode("utf-8"))
public_key.verify(signature, challenge)
return True
except Exception:
return False
def _derive_device_id_from_key(public_key) -> str:
public_key_raw = public_key.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw,
)
return hashlib.sha256(public_key_raw).hexdigest()[:DEVICE_ID_HEX_LENGTH]