Files
NebulaShell/tools/sign_plugins.py
2026-04-17 23:15:15 +08:00

194 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
密钥生成与插件签名工具
- 生成 Falck 官方密钥对
- 为所有官方插件签名
"""
import sys
import json
import base64
import hashlib
import time
from pathlib import Path
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.backends import default_backend
# ========== 配置 ==========
PROJECT_ROOT = Path(__file__).parent.parent # 修复tools 的上级目录
KEY_DIR = PROJECT_ROOT / "data" / "signature-verifier" / "keys"
STORE_DIR = PROJECT_ROOT / "store"
# 官方作者目录
OFFICIAL_AUTHORS = ["FutureOSS", "Falck"]
def generate_keypair(author: str):
"""生成 4096 位 RSA 密钥对"""
print(f"\n{'='*60}")
print(f"生成 {author} 的密钥对...")
print(f"{'='*60}")
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_dir = KEY_DIR / "private"
priv_dir.mkdir(parents=True, exist_ok=True)
priv_file = priv_dir / f"{author.lower()}_private.pem"
priv_file.write_bytes(private_pem)
print(f"私钥已保存: {priv_file}")
# 保存公钥
public_pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
pub_dir = KEY_DIR / "public"
pub_dir.mkdir(parents=True, exist_ok=True)
pub_file = pub_dir / f"{author}.pem"
pub_file.write_bytes(public_pem)
print(f"公钥已保存: {pub_file}")
# 显示公钥(用于嵌入代码)
print(f"\n--- 公钥 PEM (用于嵌入 main.py) ---")
print(public_pem.decode())
print(f"--- END ---\n")
return private_key, public_key
def compute_plugin_hash(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 sign_plugin(plugin_dir: Path, private_key, signer_name: str, author: str):
"""为插件生成签名"""
plugin_hash = compute_plugin_hash(plugin_dir)
# 签名
signed_data = f"{author}:{plugin_hash}".encode("utf-8")
signature = 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))
print(f" ✓ 已签名: {plugin_dir.name} (哈希: {plugin_hash[:16]}...)")
def sign_all_plugins(private_key):
"""为所有官方插件签名"""
for author in OFFICIAL_AUTHORS:
author_dir = STORE_DIR / f"@{{{author}}}"
if not author_dir.exists():
print(f"\n警告: 作者目录不存在: {author_dir}")
continue
print(f"\n{'='*60}")
print(f"为 @{{{author}}} 的插件签名...")
print(f"{'='*60}")
count = 0
for plugin_dir in sorted(author_dir.iterdir()):
if plugin_dir.is_dir() and (plugin_dir / "manifest.json").exists():
sign_plugin(plugin_dir, private_key, author, author)
count += 1
print(f"\n完成: 已签名 {count} 个 @{author} 插件")
def main():
print("="*60)
print("FutureOSS 插件签名工具")
print("="*60)
# 检查 cryptography
try:
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.backends import default_backend
except ImportError:
print("错误: 未安装 cryptography 库")
print("运行: pip install cryptography")
sys.exit(1)
# 步骤 1: 生成密钥对
print("\n步骤 1: 生成 Falck 官方密钥对...")
falck_priv, falck_pub = generate_keypair("Falck")
print("\n步骤 1b: 生成 FutureOSS 官方密钥对...")
foss_priv, foss_pub = generate_keypair("FutureOSS")
# 步骤 2: 为所有官方插件签名(使用对应的密钥)
print("\n步骤 2: 为所有官方插件签名...")
# Falck 的插件用 Falck 密钥签名
falck_dir = STORE_DIR / "@{Falck}"
if falck_dir.exists():
print(f"\n{'='*60}")
print("为 @{Falck} 的插件使用 Falck 密钥签名...")
print(f"{'='*60}")
for plugin_dir in sorted(falck_dir.iterdir()):
if plugin_dir.is_dir() and (plugin_dir / "manifest.json").exists():
sign_plugin(plugin_dir, falck_priv, "Falck", "Falck")
# FutureOSS 的插件用 FutureOSS 密钥签名
foss_dir = STORE_DIR / "@{FutureOSS}"
if foss_dir.exists():
print(f"\n{'='*60}")
print("为 @{FutureOSS} 的插件使用 FutureOSS 密钥签名...")
print(f"{'='*60}")
for plugin_dir in sorted(foss_dir.iterdir()):
if plugin_dir.is_dir() and (plugin_dir / "manifest.json").exists():
sign_plugin(plugin_dir, foss_priv, "FutureOSS", "FutureOSS")
print("\n" + "="*60)
print("全部完成!")
print("="*60)
print(f"\n密钥位置: {KEY_DIR}")
print("请将公钥嵌入 signature-verifier/main.py 的 FALCK_PUBLIC_KEY_PEM 变量")
print("并妥善保管私钥,不要提交到版本控制系统!")
if __name__ == "__main__":
main()