Suite

模块 Ed25519 签名

第三方模块 module.yml 签名的生成、验证与服主配置——签名机制、密钥管理、验证流程。

模块 Ed25519 签名

AXS 支持对 modules/ 目录下的模块 Jar(含云端下发的内存加载)做 Ed25519 数字签名校验。开发者用私钥对 module.yml 元数据签名,服主在宿主 config.yml 配置作者公钥后,只有签名合法且公钥匹配的模块才会被加载。

与其他安全机制的区别

机制配置位置用途
模块签名(本文)module-signature-public-keys + module.ymlsignature验证模块作者身份,防 Jar 被篡改
云端服务器绑定cloud.qq / apiKey + 本地 Ed25519 密钥对服务器与云端通信、拉取 .axb
跨服 HMACcross-server.signature.secret子服之间消息的 HMAC-SHA256
版本检查公钥version-check-public-key校验云端返回的更新公告

请勿把跨服 signature.secret 或云端绑定公钥填进 module-signature-public-keys

何时需要签名

角色建议
模块开发者发布给服主的定制模块时,可选签名并附带公钥
服主仅在使用第三方/私人定制 Jar 且希望防篡改时,向作者索取公钥
仅用官方云端模块默认不要配置公钥列表(留空 []

开启校验后的行为

module-signature-public-keys 非空时,宿主对每一个待加载模块执行验签:

  • module.yml 缺少 signature → 拒绝加载
  • 签名与任一已配置公钥不匹配 → 拒绝加载
  • 验签通过 → 正常加载

这不是「只校验第三方」的开关,而是「只允许已签名且公钥在白名单内的模块」。

签名规则

待签名字符串(payload)

{id}:{version}:{mainClass}

示例:mymodule:1.0.0:com.example.mymodule.MyModule

  • 三段均来自 module.ymlidversionmain
  • 字符编码:UTF-8
  • 不含 Jar 文件内容、不含 depends 等其他字段

修改 id / version / main 任一项后,必须重新签名。

module.yml

id: mymodule
name: MyModule
version: 1.0.0
main: com.example.mymodule.MyModule
api-version: 1.0
signature: "xxxxxxxx..."  # Ed25519 签名(Base64)

公钥格式

  • 算法:Ed25519
  • 编码:X509 SubjectPublicKeyInfo(Java PublicKey.getEncoded() 的格式)
  • 传输:Base64 单行字符串
  • 不是裸 32 字节公钥,也不是 PEM 文件原文

开发者:生成密钥并签名

方式一:Python 脚本(推荐)

# 1. 首次:生成密钥对(私钥仅保存在本机,勿提交 Git)
python scripts/sign-module.py keygen --out-dir ./module-signing-keys
 
# 2. 对 module.yml 签名并写回 signature 字段
python scripts/sign-module.py sign \
  --module-yml src/main/resources/module.yml \
  --private-key module-signing-keys/ed25519-private.pem
 
# 3. 查看应发给服主的公钥(Base64 一行)
python scripts/sign-module.py pubkey --public-key module-signing-keys/ed25519-public.pem

打包前执行 sign,确保 Jar 内 module.yml 已含最新 signature

方式二:Java 17+ 工具类

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
 
public final class ModuleSignTool {
    public static void main(String[] args) throws Exception {
        String payload = "mymodule:1.0.0:com.example.mymodule.MyModule";
 
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519");
        KeyPair kp = kpg.generateKeyPair();
 
        Signature sig = Signature.getInstance("Ed25519");
        sig.initSign(kp.getPrivate());
        sig.update(payload.getBytes(StandardCharsets.UTF_8));
        String signatureB64 = Base64.getEncoder().encodeToString(sig.sign());
        String publicKeyB64 = Base64.getEncoder().encodeToString(kp.getPublic().getEncoded());
 
        System.out.println("signature: " + signatureB64);
        System.out.println("public key: " + publicKeyB64);
    }
}

生产环境应固定使用同一私钥多次签名,不要每次 keygen 换新钥。

方式三:OpenSSL(仅生成密钥)

openssl genpkey -algorithm Ed25519 -out ed25519-private.pem
openssl pkey -in ed25519-private.pem -pubout -out ed25519-public.pem
openssl pkey -in ed25519-public.pem -pubin -outform DER | base64 -w0

Gradle 构建集成(可选)

tasks.register<Exec>("signModuleYml") {
    val moduleYml = file("src/main/resources/module.yml")
    val key = file("module-signing-keys/ed25519-private.pem")
    commandLine(
        "python", "scripts/sign-module.py", "sign",
        "--module-yml", moduleYml.absolutePath,
        "--private-key", key.absolutePath
    )
    onlyIf { key.exists() }
}
 
tasks.jar {
    dependsOn("signModuleYml")
    from("src/main/resources") { include("module.yml") }
}

未配置私钥时跳过签名,便于本地调试;发布 CI 再注入私钥。

服主:开启校验

  1. 向模块作者索取 Base64 公钥(一行)
  2. 编辑 plugins/ArcartX-Suite/config.yml
module-signature-public-keys:
  - "作者的公钥Base64"
  1. 重启或 /axs reload all
  2. 控制台若出现 [ModuleSignature] 模块 xxx 缺少 signature签名验证失败,说明 Jar 未签名、公钥不对,或 module.yml 与签名时的 id/version/main 不一致

留空 [] 表示关闭模块签名校验(默认)。

与云端 .axb 的关系

云端下发的加密模块在内存中还原为 Jar 后,同样会走 module.yml 签名校验(若已配置公钥)。官方模块若未带 signature,请勿在生产环境开启此列表。

发布清单

发布定制模块时,建议 README 中写明:

## 模块签名校验(可选)
 
- 模块 ID:mymodule
- Ed25519 公钥(Base64,X509):MCowBQYDK2VwAyEA...
- 配置方式:在 config.yml 的 module-signature-public-keys 中加入上述公钥
- 注意:开启后仅加载带有效签名的模块

常见问题

公钥填了仍加载失败

  • 确认公钥是 X509 Base64,不是 PEM 头尾或裸 32 字节
  • 确认 module.ymlid / version / main 与签名时完全一致
  • 确认 Jar 根目录的 module.yml 已更新(重新打包)

开启校验后官方模块也挂了

预期行为:官方模块默认无 signature。仅在使用全部来自已签名第三方的场景下开启;或保持 module-signature-public-keys: []

和 Jar 内完整性校验的关系

宿主 core 本体有 Merkle + native 完整性校验;模块 Jar 的 module.yml Ed25519 是独立机制,由服主按需开启。