重构:核心迁移至 oss/core + NBPF 多重签名加密 + NIR 编译器 + README 全面升级

- 核心功能从 store/ 迁移至 oss/core/ 框架层
- 实现 NBPF 包格式:多重签名(Ed25519+RSA-PSS+HMAC)+ 多重加密(AES-256-GCM)
- 实现 NIR 编译器:基于 compile()+marshal 的跨平台中间表示
- 新增 nebula nbpf CLI 命令组(pack/unpack/verify/sign/keygen)
- 新增 19 个 NBPF 测试用例,覆盖全链路
- 彻底重写 README,大型项目标准框架风格,所有图表使用 SVG
- 更新 LICENSE 版权声明
- 清理旧版 store 插件目录(已迁移至 oss/core)
This commit is contained in:
Falck
2026-05-05 07:29:43 +08:00
parent 4441a968db
commit 3a096f59a9
184 changed files with 5715 additions and 10066 deletions

481
tests/test_nbpf.py Normal file
View File

@@ -0,0 +1,481 @@
"""NBPF 模块测试"""
import sys
import os
import json
import tempfile
import shutil
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
from oss.core.nbpf import (
NBPCrypto, NBPCryptoError,
NIRCompiler, NIRCompileError,
NBPFPacker, NBPFUnpacker, NBPFFormatter,
NBPFLoader, NBPFLoadError,
)
# ═══════════════════════════════════════════════════════════════
# 辅助函数
# ═══════════════════════════════════════════════════════════════
def _create_test_plugin(tmp_dir: Path) -> Path:
"""创建测试插件目录"""
plugin_dir = tmp_dir / "test-plugin"
plugin_dir.mkdir(parents=True)
# manifest.json
manifest = {
"metadata": {
"name": "test-plugin",
"version": "1.0.0",
"author": "test",
"description": "测试插件",
},
"dependencies": [],
}
(plugin_dir / "manifest.json").write_text(json.dumps(manifest, indent=2))
# main.py
main_py = '''"""测试插件"""
class New:
def __init__(self):
self.name = "test-plugin"
self.started = False
def start(self):
self.started = True
return True
def stop(self):
self.started = False
return True
def greet(self, name: str) -> str:
return f"Hello, {name}!"
'''
(plugin_dir / "main.py").write_text(main_py)
return plugin_dir
def _generate_keys(tmp_dir: Path) -> dict:
"""生成测试密钥,返回路径字典"""
keys = {}
# Ed25519
ed25519_private, ed25519_public = NBPCrypto.generate_ed25519_keypair()
keys["ed25519_private"] = ed25519_private
keys["ed25519_public"] = ed25519_public
# RSA
rsa_private, rsa_public = NBPCrypto.generate_rsa_keypair(key_size=2048) # 测试用 2048
keys["rsa_private"] = rsa_private
keys["rsa_public"] = rsa_public
return keys
# ═══════════════════════════════════════════════════════════════
# 测试NBPCrypto
# ═══════════════════════════════════════════════════════════════
def test_crypto_aes_encrypt_decrypt():
"""测试 AES-256-GCM 加密解密"""
data = b"Hello, NebulaShell!"
key = NBPCrypto.generate_aes_key()
assert len(key) == 32
nonce, ct, tag = NBPCrypto._aes_encrypt(data, key)
assert len(nonce) == 12
assert len(tag) == 16
assert isinstance(ct, bytes)
dec = NBPCrypto._aes_decrypt(ct, key, nonce, tag)
assert dec == data
def test_crypto_outer_encrypt_decrypt():
"""测试外层加密解密"""
data = b"Secret plugin data"
key = NBPCrypto.generate_aes_key()
enc = NBPCrypto.outer_encrypt(data, key)
assert isinstance(enc, dict)
dec = NBPCrypto.outer_decrypt(enc, key)
assert dec == data
def test_crypto_inner_encrypt_decrypt():
"""测试中层加密解密"""
data = b"NIR compiled data"
key = NBPCrypto.generate_aes_key()
enc = NBPCrypto.inner_encrypt(data, key)
assert isinstance(enc, dict)
dec = NBPCrypto.inner_decrypt(enc, key)
assert dec == data
def test_crypto_ed25519_sign_verify():
"""测试 Ed25519 签名验证"""
data = b"Package content hash"
private, public = NBPCrypto.generate_ed25519_keypair()
signature = NBPCrypto.outer_sign(data, private)
assert isinstance(signature, bytes)
assert NBPCrypto.outer_verify(data, signature, public) is True
assert NBPCrypto.outer_verify(b"tampered", signature, public) is False
def test_crypto_rsa_sign_verify():
"""测试 RSA-PSS 签名验证"""
data = b"NIR content hash"
private, public = NBPCrypto.generate_rsa_keypair(key_size=2048)
signature = NBPCrypto.inner_sign(data, private)
assert isinstance(signature, bytes)
assert NBPCrypto.inner_verify(data, signature, public) is True
assert NBPCrypto.inner_verify(b"tampered", signature, public) is False
def test_crypto_hmac_sign_verify():
"""测试 HMAC-SHA256 签名验证"""
data = b"Module content"
key1 = NBPCrypto.generate_aes_key()
key2 = NBPCrypto.generate_aes_key()
hmac_key = NBPCrypto.derive_hmac_key(key1, key2)
signature = NBPCrypto.module_sign(data, hmac_key)
assert isinstance(signature, str)
assert NBPCrypto.module_verify(data, signature, hmac_key) is True
assert NBPCrypto.module_verify(b"tampered", signature, hmac_key) is False
def test_crypto_rsa_oaep_key_encapsulation():
"""测试 RSA-OAEP 密钥封装"""
aes_key = NBPCrypto.generate_aes_key()
rsa_private, rsa_public = NBPCrypto.generate_rsa_keypair(key_size=2048)
encrypted = NBPCrypto.encrypt_key(aes_key, rsa_public)
assert isinstance(encrypted, str)
decrypted = NBPCrypto.decrypt_key(encrypted, rsa_private)
assert decrypted == aes_key
def test_crypto_anti_debug():
"""测试反调试检测(不触发,仅验证函数存在)"""
assert hasattr(NBPCrypto, "_anti_debug_check")
assert callable(NBPCrypto._anti_debug_check)
def test_crypto_secure_wipe():
"""测试内存擦除"""
buf = bytearray(b"secret_key_1234567890")
NBPCrypto._secure_wipe(buf)
assert all(b == 0 for b in buf)
# ═══════════════════════════════════════════════════════════════
# 测试NIRCompiler
# ═══════════════════════════════════════════════════════════════
def test_compiler_compile_source():
"""测试编译源码为 NIR"""
compiler = NIRCompiler()
source = "x = 1\ny = x + 2\nresult = y * 3\n"
nir_data = compiler.compile_source(source, "test.py")
assert isinstance(nir_data, bytes)
# 反序列化
code = NIRCompiler.deserialize_nir(nir_data)
assert code is not None
# 执行
ns = {}
exec(code, ns)
assert ns["result"] == 9
def test_compiler_create_function():
"""测试从 code object 创建函数"""
compiler = NIRCompiler()
source = "def add(a, b): return a + b"
nir_data = compiler.compile_source(source, "test.py")
code = NIRCompiler.deserialize_nir(nir_data)
ns = {}
exec(code, ns)
assert ns["add"](2, 3) == 5
def test_compiler_forbidden_modules():
"""测试禁止导入危险模块"""
compiler = NIRCompiler()
dangerous_sources = [
"import os",
"from os import path",
"import subprocess",
"import sys",
"import socket",
"import ctypes",
]
for source in dangerous_sources:
try:
compiler.compile_source(source, "test.py")
assert False, f"应该拒绝: {source}"
except NIRCompileError:
pass # 预期行为
def test_compiler_compile_plugin():
"""测试编译整个插件目录"""
compiler = NIRCompiler()
with tempfile.TemporaryDirectory() as tmp:
plugin_dir = _create_test_plugin(Path(tmp))
nir_data = compiler.compile_plugin(plugin_dir)
assert "main" in nir_data
assert isinstance(nir_data["main"], bytes)
# ═══════════════════════════════════════════════════════════════
# 测试NBPFPacker + NBPFUnpacker
# ═══════════════════════════════════════════════════════════════
def _create_packer():
"""创建 NBPFPacker 实例"""
return NBPFPacker(crypto=NBPCrypto(), compiler=NIRCompiler())
def test_pack_unpack():
"""测试打包和解包完整流程"""
packer = _create_packer()
unpacker = NBPFUnpacker()
with tempfile.TemporaryDirectory() as tmp:
tmp_path = Path(tmp)
plugin_dir = _create_test_plugin(tmp_path)
keys = _generate_keys(tmp_path)
# 打包
nbpf_path = tmp_path / "test-plugin.nbpf"
result = packer.pack(
plugin_dir=plugin_dir,
output_path=nbpf_path,
ed25519_private_key=keys["ed25519_private"],
rsa_private_key_pem=keys["rsa_private"],
rsa_public_key_pem=keys["rsa_public"],
signer_name="test",
)
assert result.exists()
assert result.suffix == ".nbpf"
# 解包
unpack_dir = tmp_path / "unpacked"
result_dir = unpacker.unpack(nbpf_path, unpack_dir)
assert result_dir.exists()
# 解包后 manifest 在 META-INF/MANIFEST.MF
assert (result_dir / "META-INF" / "MANIFEST.MF").exists()
def test_extract_manifest():
"""测试提取 manifest"""
packer = _create_packer()
unpacker = NBPFUnpacker()
with tempfile.TemporaryDirectory() as tmp:
tmp_path = Path(tmp)
plugin_dir = _create_test_plugin(tmp_path)
keys = _generate_keys(tmp_path)
nbpf_path = tmp_path / "test-plugin.nbpf"
packer.pack(
plugin_dir=plugin_dir,
output_path=nbpf_path,
ed25519_private_key=keys["ed25519_private"],
rsa_private_key_pem=keys["rsa_private"],
rsa_public_key_pem=keys["rsa_public"],
signer_name="test",
)
manifest = unpacker.extract_manifest(nbpf_path)
assert manifest["metadata"]["name"] == "test-plugin"
assert manifest["metadata"]["version"] == "1.0.0"
def test_verify_signature():
"""测试签名验证"""
packer = _create_packer()
unpacker = NBPFUnpacker()
with tempfile.TemporaryDirectory() as tmp:
tmp_path = Path(tmp)
plugin_dir = _create_test_plugin(tmp_path)
keys = _generate_keys(tmp_path)
nbpf_path = tmp_path / "test-plugin.nbpf"
packer.pack(
plugin_dir=plugin_dir,
output_path=nbpf_path,
ed25519_private_key=keys["ed25519_private"],
rsa_private_key_pem=keys["rsa_private"],
rsa_public_key_pem=keys["rsa_public"],
signer_name="test",
)
# 用正确的公钥验证
trusted = {"test": keys["ed25519_public"]}
valid, msg = unpacker.verify_signature(nbpf_path, trusted)
assert valid, f"验证失败: {msg}"
# 用错误的公钥验证
wrong_private, wrong_public = NBPCrypto.generate_ed25519_keypair()
wrong_trusted = {"wrong": wrong_public}
valid, msg = unpacker.verify_signature(nbpf_path, wrong_trusted)
assert not valid, "应该验证失败"
# ═══════════════════════════════════════════════════════════════
# 测试NBPFLoader
# ═══════════════════════════════════════════════════════════════
def test_loader_full_flow():
"""测试加载器完整流程"""
packer = _create_packer()
with tempfile.TemporaryDirectory() as tmp:
tmp_path = Path(tmp)
plugin_dir = _create_test_plugin(tmp_path)
keys = _generate_keys(tmp_path)
# 打包
nbpf_path = tmp_path / "test-plugin.nbpf"
packer.pack(
plugin_dir=plugin_dir,
output_path=nbpf_path,
ed25519_private_key=keys["ed25519_private"],
rsa_private_key_pem=keys["rsa_private"],
rsa_public_key_pem=keys["rsa_public"],
signer_name="test",
)
# 加载
loader = NBPFLoader(
crypto=NBPCrypto(),
compiler=NIRCompiler(),
trusted_ed25519_keys={"test": keys["ed25519_public"]},
trusted_rsa_keys={"test": keys["rsa_public"]},
rsa_private_key=keys["rsa_private"],
)
instance, info = loader.load(nbpf_path)
assert instance is not None
assert info["name"] == "test-plugin"
assert info["version"] == "1.0.0"
# 验证插件功能
assert instance.name == "test-plugin"
assert instance.greet("World") == "Hello, World!"
# 验证启动/停止
assert instance.start() is True
assert instance.started is True
assert instance.stop() is True
assert instance.started is False
def test_loader_wrong_signature():
"""测试加载器拒绝错误签名"""
packer = _create_packer()
with tempfile.TemporaryDirectory() as tmp:
tmp_path = Path(tmp)
plugin_dir = _create_test_plugin(tmp_path)
keys = _generate_keys(tmp_path)
nbpf_path = tmp_path / "test-plugin.nbpf"
packer.pack(
plugin_dir=plugin_dir,
output_path=nbpf_path,
ed25519_private_key=keys["ed25519_private"],
rsa_private_key_pem=keys["rsa_private"],
rsa_public_key_pem=keys["rsa_public"],
signer_name="test",
)
# 用错误的 Ed25519 公钥
_, wrong_public = NBPCrypto.generate_ed25519_keypair()
loader = NBPFLoader(
trusted_ed25519_keys={"wrong": wrong_public},
trusted_rsa_keys={"test": keys["rsa_public"]},
rsa_private_key=keys["rsa_private"],
)
try:
loader.load(nbpf_path)
assert False, "应该抛出 NBPFLoadError"
except NBPFLoadError:
pass
# ═══════════════════════════════════════════════════════════════
# 测试PluginManager 集成
# ═══════════════════════════════════════════════════════════════
def test_plugin_manager_nbpf_methods():
"""测试 PluginManager 的 NBPF 方法"""
from oss.core.engine import PluginManager
pm = PluginManager()
assert hasattr(pm, "load_nbpf")
assert hasattr(pm, "_init_nbpf")
assert pm._nbpf_initialized is False
# 初始化
pm._init_nbpf()
# 没有密钥文件时,加载器可能为 None
# 但方法应该存在且不崩溃
# ═══════════════════════════════════════════════════════════════
# 运行
# ═══════════════════════════════════════════════════════════════
if __name__ == "__main__":
tests = [
("NBPCrypto AES", test_crypto_aes_encrypt_decrypt),
("NBPCrypto 外层加密", test_crypto_outer_encrypt_decrypt),
("NBPCrypto 中层加密", test_crypto_inner_encrypt_decrypt),
("NBPCrypto Ed25519", test_crypto_ed25519_sign_verify),
("NBPCrypto RSA-PSS", test_crypto_rsa_sign_verify),
("NBPCrypto HMAC", test_crypto_hmac_sign_verify),
("NBPCrypto RSA-OAEP", test_crypto_rsa_oaep_key_encapsulation),
("NBPCrypto 反调试", test_crypto_anti_debug),
("NBPCrypto 内存擦除", test_crypto_secure_wipe),
("NIRCompiler 编译", test_compiler_compile_source),
("NIRCompiler 函数创建", test_compiler_create_function),
("NIRCompiler 禁止模块", test_compiler_forbidden_modules),
("NIRCompiler 编译目录", test_compiler_compile_plugin),
("NBPF 打包解包", test_pack_unpack),
("NBPF 提取 manifest", test_extract_manifest),
("NBPF 签名验证", test_verify_signature),
("NBPF 加载器完整流程", test_loader_full_flow),
("NBPF 加载器错误签名", test_loader_wrong_signature),
("PluginManager 集成", test_plugin_manager_nbpf_methods),
]
passed = 0
failed = 0
for name, test_fn in tests:
try:
test_fn()
print(f"{name}")
passed += 1
except Exception as e:
print(f"{name}: {type(e).__name__}: {e}")
failed += 1
print(f"\n结果: {passed} 通过, {failed} 失败, {passed + failed} 总计")
sys.exit(1 if failed > 0 else 0)