新增简易的8080面板😊
This commit is contained in:
8
store/@{FutureOSS}/signature-verifier/SIGNATURE
Normal file
8
store/@{FutureOSS}/signature-verifier/SIGNATURE
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"signature": "ymWLA+iMcmMAum/qQIJdTgk31K/dNejA9BtOe5yUjAi58yKKW4mPx8c+6x9I28XM+eG1KkSAMrb94NFVVARR/k/Lc4QnNwbJVoYheEfKscMih6G4meiVYPFGkjMwukNL+k8qon+HJwAtHtZ9DFWclfZEXDKVptbr+3juovmBGIYdGlZ9pS2AmrYLsxx5SjCtuJb+cuOE0U5S5GBxBsv7tvP5IQAZZP1Crf0Cxe9Op6+UzX0TYfzWswIqcYzZdgPEUbMorwUlRPVgHGiaYtoHqQGISjR2kAgk5XW3NCuPAAQoiY9XTe+YD+3wDfQ7ic0tkESIJBBt7Zq2VJMmh8lwWMTRi0+xVgZHua2HpLdHDatSggWoCXQiMakm7rA1Z/Xto30mx0Pk4fh2vcYeuThoY6a+GsPbMxG4Bj52jSzGwHu264cgnSe8wS+HmbU0Ch1t4qD0lAZh297qRBqdr4hW+Lzf/FxVd5kQiL3rYcq8mi3Kd4nmiQ4/gev5mX7uSf6fhByCrXqe4pvgAt5q7BgByC9C/krcTRNIwV+u9PQe7zboXZY7x0CzYzITKkWzIY98Fl+8qmvuanLoPQ3tqAdixJr5I1Lpk71l2B5cW5ht2iB48RL4b+oPNqksIweRmPSDyTGBf3jS2YQNY62tdt4yXkWHO00qIOU0UQkGF+BKRSw=",
|
||||
"signer": "FutureOSS",
|
||||
"algorithm": "RSA-SHA256",
|
||||
"timestamp": 1775969853.454207,
|
||||
"plugin_hash": "762549e109001210eb9fb1fbfc2b9a3c25cbb3ea899b796d07357d6ee32949d5",
|
||||
"author": "FutureOSS"
|
||||
}
|
||||
Binary file not shown.
337
store/@{FutureOSS}/signature-verifier/main.py
Normal file
337
store/@{FutureOSS}/signature-verifier/main.py
Normal file
@@ -0,0 +1,337 @@
|
||||
"""
|
||||
插件签名验证服务
|
||||
- 验证官方插件的完整性与来源真实性
|
||||
- 支持多签名者(Falck 独特性签名)
|
||||
- RSA-SHA256 非对称加密方案
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import hashlib
|
||||
import base64
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, List, Tuple
|
||||
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding, rsa
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
|
||||
from oss.plugin.types import Plugin
|
||||
|
||||
|
||||
# ========== 内置信任锚(Falck 公钥) ==========
|
||||
# 这是 Falck 的官方公钥,用于验证所有官方签名的插件
|
||||
FALCK_PUBLIC_KEY_PEM = """-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqN42I+etzCMHAt4XMLNm
|
||||
PMy9CgPuqSNgdwLIsrvYzIZ2nNUr351fLdC19PV1ou5G/rbQzX5TsxGDzJideQWh
|
||||
Cdi8G5AwvsoFNzfhLcTxc38YsQGTizj/iIBOkBWiIoLgBDAqyZxKfgAIJQWwhiuz
|
||||
vUbXqf6O12YZBzkV/XroszpM1AweZqE+TkmIPs9AbH7Pvi8kHme/avQwPmsQlKyE
|
||||
Lf+4CykIN0EnZLLaDGuwwT/V1FIPgOGGWdqVbOyyGB2wMuBwRUrPYJoBYrRISjjz
|
||||
KjLsSWfdQ7Id5snovjFnqwZ2qyijhgZVirKLmbEtn9rVAhEO2sPaSbrGzpwllxYp
|
||||
pZpSW6yXzO8Ty7XqwIzQg6dw7m8WnIv8pCGKrASSuRwdCDZLh7Wf5gs3kgGTEDmn
|
||||
e8imMXNbG1liVQSc5KyNlYdMd99oUCyPza014km/ci5Uw7lT7kgaE2N8BlEBq0vV
|
||||
/JzQBW5/rYvbR4CZXmoMCpynjJ5S7PoU8cLmgemFxVJ1RRZMLEj2aDyK11WA1qsV
|
||||
IJ02ZY5N7hSJG0mY5YdHeP8CVDKABEFAMMD/i6Jz53z7JPH/Aavw/8HNZFeDliXM
|
||||
aMGfNRV1niQLUHnYliDjNCBOxLWfB9pomha3qWfdt0R0obdFeJ+z2SgcTGSk7+zo
|
||||
Bgidq6CPfGimd7Wf9TP9J50CAwEAAQ==
|
||||
-----END PUBLIC KEY-----"""
|
||||
|
||||
# FutureOSS 官方公钥
|
||||
FUTUREOSS_PUBLIC_KEY_PEM = """-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoeXGPyd4z0wREb7+G85K
|
||||
Vabf+7PkmJRbICurqF0ZqBj+0hGMeOI9D8C+6J0AAja4xywq96Btbr/6POVfkhdf
|
||||
HGCwI1HO6dR+6es2gCYnvWI9XMGNV0GMXcc7nWz+upukNC0knx/KyjtM+vwYz6jC
|
||||
qXFD86msCdrDfK4VqPLOPm7K4oA7DIsjTO1ka+pE1wS8gSY7+Tv24hEe/jvZGFvC
|
||||
u/QIMOL4MoRE9j8xxgT3teR9pEwK3+VUvQPdFQkoI+6LAH8Dh749EEY2IGrb1F88
|
||||
5D5lWwTdtzEcrmiOb8KyvXtg1S8Gu01in3HLIkMmwCyCQM2p3/cz9qtmx/Yv6ETO
|
||||
vey6G3xZAqqA4mF9RMiymqY3l4CFPVkX1gpOtrfSPPWUDJCVx2iAQcHBl/oq8pz5
|
||||
jSjN6Njy+yF8Yq7deIBxU+ZWE3Lm+2ioImsozJxAELCMjpgc5vfxySHSPy5ite2r
|
||||
Bbt6b0lKyjbPRHRvcXGtc76E92jVzuEOoNVf2/LCThggE/sfxU53/SRekXH05+PE
|
||||
1k3wTMEpeVh0m8B8fp052+eW6DXVo0+uPkZPgCA7aA7yHIyqhtBAH4pSwXPFXgzS
|
||||
OHMoaLwKiNfFLV2tW8JG18oUIwO3sMyeyLazjHPP831WJ55dfJFsK+d7CfcD3OxM
|
||||
LhUSJrBP4sr2J0DyGi0GyjsCAwEAAQ==
|
||||
-----END PUBLIC KEY-----"""
|
||||
|
||||
|
||||
class SignatureError(Exception):
|
||||
"""签名验证失败异常"""
|
||||
pass
|
||||
|
||||
|
||||
class SignatureVerifier:
|
||||
"""签名验证器"""
|
||||
|
||||
def __init__(self, key_dir: str = "./data/signature-verifier/keys"):
|
||||
self.key_dir = Path(key_dir)
|
||||
self.key_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.public_keys: Dict[str, bytes] = {}
|
||||
self._load_builtin_keys()
|
||||
|
||||
def _load_builtin_keys(self):
|
||||
"""加载内置信任锚"""
|
||||
# Falck 官方公钥
|
||||
self.public_keys["Falck"] = FALCK_PUBLIC_KEY_PEM.encode()
|
||||
# FutureOSS 官方公钥
|
||||
self.public_keys["FutureOSS"] = FUTUREOSS_PUBLIC_KEY_PEM.encode()
|
||||
# 加载额外密钥
|
||||
self._load_extra_keys()
|
||||
|
||||
def _load_extra_keys(self):
|
||||
"""从密钥目录加载额外的公钥"""
|
||||
pub_dir = self.key_dir / "public"
|
||||
if not pub_dir.exists():
|
||||
return
|
||||
for key_file in pub_dir.glob("*.pem"):
|
||||
author_name = key_file.stem
|
||||
self.public_keys[author_name] = key_file.read_bytes()
|
||||
|
||||
def _compute_plugin_hash(self, plugin_dir: Path) -> str:
|
||||
"""
|
||||
计算插件目录的内容哈希
|
||||
包含所有文件的路径相对路径 + 内容
|
||||
"""
|
||||
hasher = hashlib.sha256()
|
||||
|
||||
# 收集所有需要哈希的文件
|
||||
files_to_hash = []
|
||||
for file_path in sorted(plugin_dir.rglob("*")):
|
||||
if file_path.is_file() and file_path.name != "SIGNATURE":
|
||||
rel_path = file_path.relative_to(plugin_dir)
|
||||
files_to_hash.append((str(rel_path), file_path))
|
||||
|
||||
# 按相对路径排序后依次哈希
|
||||
for rel_path, file_path in files_to_hash:
|
||||
hasher.update(rel_path.encode("utf-8"))
|
||||
hasher.update(file_path.read_bytes())
|
||||
|
||||
return hasher.hexdigest()
|
||||
|
||||
def verify_plugin(self, plugin_dir: Path, author: str = "Falck") -> Tuple[bool, str]:
|
||||
"""
|
||||
验证插件签名
|
||||
返回: (是否有效, 详细信息)
|
||||
"""
|
||||
signature_file = plugin_dir / "SIGNATURE"
|
||||
|
||||
if not signature_file.exists():
|
||||
return False, f"插件缺少签名文件: {plugin_dir}"
|
||||
|
||||
# 加载签名
|
||||
try:
|
||||
sig_data = json.loads(signature_file.read_text())
|
||||
except json.JSONDecodeError as e:
|
||||
return False, f"签名文件格式错误: {e}"
|
||||
|
||||
# 检查必需字段
|
||||
required_fields = ["signature", "signer", "algorithm", "timestamp"]
|
||||
for field in required_fields:
|
||||
if field not in sig_data:
|
||||
return False, f"签名文件缺少必需字段: {field}"
|
||||
|
||||
signer = sig_data["signer"]
|
||||
signature = base64.b64decode(sig_data["signature"])
|
||||
|
||||
# 获取对应公钥
|
||||
if signer not in self.public_keys:
|
||||
return False, f"未知签名者: {signer}"
|
||||
|
||||
try:
|
||||
public_key = serialization.load_pem_public_key(
|
||||
self.public_keys[signer],
|
||||
backend=default_backend()
|
||||
)
|
||||
except Exception as e:
|
||||
return False, f"公钥加载失败: {e}"
|
||||
|
||||
# 计算当前哈希
|
||||
current_hash = self._compute_plugin_hash(plugin_dir)
|
||||
|
||||
# 验证签名
|
||||
try:
|
||||
# 签名的是 "作者:哈希值" 的组合
|
||||
signed_data = f"{author}:{current_hash}".encode("utf-8")
|
||||
public_key.verify(
|
||||
signature,
|
||||
signed_data,
|
||||
padding.PSS(
|
||||
mgf=padding.MGF1(hashes.SHA256()),
|
||||
salt_length=padding.PSS.MAX_LENGTH
|
||||
),
|
||||
hashes.SHA256()
|
||||
)
|
||||
return True, f"签名验证通过 (签名者: {signer})"
|
||||
except InvalidSignature:
|
||||
return False, f"签名不匹配!插件可能已被篡改 (签名者: {signer})"
|
||||
except Exception as e:
|
||||
return False, f"签名验证异常: {e}"
|
||||
|
||||
def is_official_plugin(self, plugin_dir: Path) -> bool:
|
||||
"""检查是否是官方插件(Falck 或 FutureOSS 签名)"""
|
||||
return self.verify_plugin(plugin_dir, "Falck")[0] or \
|
||||
self.verify_plugin(plugin_dir, "FutureOSS")[0]
|
||||
|
||||
|
||||
class SignatureSigner:
|
||||
"""签名生成器(仅用于密钥持有者)"""
|
||||
|
||||
def __init__(self, private_key_path: Optional[str] = None):
|
||||
self.private_key = None
|
||||
if private_key_path:
|
||||
self.load_private_key(private_key_path)
|
||||
|
||||
def load_private_key(self, key_path: str):
|
||||
"""加载私钥"""
|
||||
with open(key_path, "rb") as f:
|
||||
self.private_key = serialization.load_pem_private_key(
|
||||
f.read(),
|
||||
password=None,
|
||||
backend=default_backend()
|
||||
)
|
||||
|
||||
def load_private_key_from_pem(self, pem_data: str):
|
||||
"""从 PEM 字符串加载私钥"""
|
||||
self.private_key = serialization.load_pem_private_key(
|
||||
pem_data.encode(),
|
||||
password=None,
|
||||
backend=default_backend()
|
||||
)
|
||||
|
||||
def sign_plugin(self, plugin_dir: Path, signer_name: str, author: str = "Falck") -> str:
|
||||
"""
|
||||
为插件生成签名
|
||||
返回: 签名的文件路径
|
||||
"""
|
||||
if not self.private_key:
|
||||
raise ValueError("未加载私钥")
|
||||
|
||||
# 计算插件哈希
|
||||
hasher = hashlib.sha256()
|
||||
files_to_hash = []
|
||||
for file_path in sorted(plugin_dir.rglob("*")):
|
||||
if file_path.is_file() and file_path.name not in ("SIGNATURE",):
|
||||
rel_path = file_path.relative_to(plugin_dir)
|
||||
files_to_hash.append((str(rel_path), file_path))
|
||||
|
||||
for rel_path, file_path in files_to_hash:
|
||||
hasher.update(rel_path.encode("utf-8"))
|
||||
hasher.update(file_path.read_bytes())
|
||||
|
||||
plugin_hash = hasher.hexdigest()
|
||||
|
||||
# 签名
|
||||
import time
|
||||
signed_data = f"{author}:{plugin_hash}".encode("utf-8")
|
||||
signature = self.private_key.sign(
|
||||
signed_data,
|
||||
padding.PSS(
|
||||
mgf=padding.MGF1(hashes.SHA256()),
|
||||
salt_length=padding.PSS.MAX_LENGTH
|
||||
),
|
||||
hashes.SHA256()
|
||||
)
|
||||
|
||||
# 写入签名文件
|
||||
sig_data = {
|
||||
"signature": base64.b64encode(signature).decode(),
|
||||
"signer": signer_name,
|
||||
"algorithm": "RSA-SHA256",
|
||||
"timestamp": time.time(),
|
||||
"plugin_hash": plugin_hash,
|
||||
"author": author
|
||||
}
|
||||
|
||||
signature_file = plugin_dir / "SIGNATURE"
|
||||
signature_file.write_text(json.dumps(sig_data, indent=2))
|
||||
|
||||
return str(signature_file)
|
||||
|
||||
|
||||
class SignatureVerifierPlugin(Plugin):
|
||||
"""签名验证插件入口"""
|
||||
|
||||
def __init__(self):
|
||||
self.storage = None
|
||||
self.verifier = None
|
||||
self.signer = None
|
||||
self.enforce_official = True
|
||||
|
||||
def set_plugin_storage(self, instance):
|
||||
self.storage = instance
|
||||
|
||||
def init(self, deps: dict = None):
|
||||
# 初始化验证器
|
||||
key_dir = "./data/signature-verifier/keys"
|
||||
self.verifier = SignatureVerifier(key_dir=key_dir)
|
||||
|
||||
# 初始化签名器(如果有私钥)
|
||||
private_key_path = "./data/signature-verifier/keys/private/falck_private.pem"
|
||||
if Path(private_key_path).exists():
|
||||
self.signer = SignatureSigner(private_key_path)
|
||||
|
||||
# 加载配置
|
||||
if self.storage:
|
||||
storage = self.storage.get_storage("signature-verifier")
|
||||
self.enforce_official = storage.get("enforce_official", True)
|
||||
|
||||
def start(self):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
# ========== 对外 API ==========
|
||||
|
||||
def verify(self, plugin_dir: Path, author: str = "Falck") -> Tuple[bool, str]:
|
||||
"""验证插件签名"""
|
||||
return self.verifier.verify_plugin(plugin_dir, author)
|
||||
|
||||
def is_official(self, plugin_dir: Path) -> bool:
|
||||
"""检查是否是官方插件"""
|
||||
return self.verifier.is_official_plugin(plugin_dir)
|
||||
|
||||
def sign(self, plugin_dir: Path, signer_name: str = "Falck", author: str = "Falck") -> str:
|
||||
"""为插件签名(需要私钥)"""
|
||||
if not self.signer:
|
||||
raise SignatureError("未加载私钥,无法签名")
|
||||
return self.signer.sign_plugin(plugin_dir, signer_name, author)
|
||||
|
||||
def generate_keypair(self, author: str, key_dir: str = None):
|
||||
"""生成新的密钥对"""
|
||||
if key_dir is None:
|
||||
key_dir = self.key_dir
|
||||
|
||||
key_path = Path(key_dir)
|
||||
key_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
private_key = rsa.generate_private_key(
|
||||
public_exponent=65537,
|
||||
key_size=4096,
|
||||
backend=default_backend()
|
||||
)
|
||||
public_key = private_key.public_key()
|
||||
|
||||
# 保存私钥
|
||||
private_pem = private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.PKCS8,
|
||||
encryption_algorithm=serialization.NoEncryption()
|
||||
)
|
||||
priv_file = key_path / "private" / f"{author.lower()}_private.pem"
|
||||
priv_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
priv_file.write_bytes(private_pem)
|
||||
|
||||
# 保存公钥
|
||||
public_pem = public_key.public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
)
|
||||
pub_file = key_path / "public" / f"{author}.pem"
|
||||
pub_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
pub_file.write_bytes(public_pem)
|
||||
|
||||
return str(priv_file), str(pub_file)
|
||||
|
||||
|
||||
def New():
|
||||
return SignatureVerifierPlugin()
|
||||
18
store/@{FutureOSS}/signature-verifier/manifest.json
Normal file
18
store/@{FutureOSS}/signature-verifier/manifest.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"metadata": {
|
||||
"name": "signature-verifier",
|
||||
"version": "1.0.0",
|
||||
"author": "FutureOSS",
|
||||
"description": "插件签名验证服务 - 验证官方插件完整性与来源真实性",
|
||||
"type": "core"
|
||||
},
|
||||
"config": {
|
||||
"enabled": true,
|
||||
"args": {
|
||||
"enforce_official": true,
|
||||
"key_dir": "data/signature-verifier/keys"
|
||||
}
|
||||
},
|
||||
"dependencies": ["plugin-storage"],
|
||||
"permissions": ["plugin-storage"]
|
||||
}
|
||||
Reference in New Issue
Block a user