// signature.go - SignatureVerifier interface + NoopVerifier + DefaultVerifier. // // 本文件只保留**接口和零成本默认实现**. 实际的 SHA-256 完整性校验实现 // (SHA256IntegrityVerifier / RejectUnsignedVerifier / ComputeChecksum) 见 integrity.go. // // # 历史背景 (为什么文件名叫 "signature" 却只放完整性校验?) // // 原 scaffolding (commit 58c3ab5, 2026-04-14) 先写了这个文件, 里面有一个 // placeholderEd25519Verifier 占位 + SecurityMetadata 字段. 次日对话经过 // 8 轮反向推敲后认定: // // - 市场老大 OpenClaw 3.22 不做密码学签名 (只做运行时安全硬化) // - Anthropic .mcpb PKCS#7 是桌面应用范式, 不适合 Go 引擎 // - Flyto plugin = "本地目录 + 配置 + markdown", 100% 是数据/配置没有 // 可执行代码, 威胁模型是**本地篡改**而非**远程伪造** // - SHA-256 sidecar 完整性校验足以覆盖本地篡改场景 // // 于是删除了 placeholder 和 SecurityMetadata, 真实现放在 integrity.go, 本文件 // 仅保留 interface 契约. 文件名没改回 "verifier.go" 是因为未来真要加 // Ed25519 / Sshsig / PKCS#7 实现时, 它们仍然是"签名验证器", signature.go // 这个名字会回到字面意思. // // # 未来扩展点 // // 如果真需要密码学签名 (marketplace 上线 / 企业合规 / 跨机器分发), 在同一 // SignatureVerifier interface 下新增实现: // // - Ed25519Verifier: 用开发者公钥 + ~/.flyto/trust/ 信任根 // - SshsigVerifier: 复用 OpenSSH 的 sshsig 格式 + allowed_signers 文件 // - SigstoreVerifier: 和 Sigstore/cosign 生态对接 (OIDC 身份 + Rekor) // - Pkcs7Verifier: 和 MCPB 官方签名对齐 (PKCS#7 + X.509 + OS 证书库) // // 这些都不需要改 loader.go 或 interface, 是纯粹的"新增实现"事件. // // # 精妙之处 (CLEVER) // // 接口只有一个方法 Verify, 符合 Go 惯例的最小接口. 返回 error 而非 // (bool, error) - "验证失败" 和 "验证出错" 在安全语义上是同一件事, 都必须 // 拒绝加载. 拆成 (bool, error) 反而诱导 "if err != nil return" 漏掉 // "if !valid return" 的双重判断 bug. 这是一种典型的"API 诱导反模式". // // 替代方案: <返回 (bool, error)> - 否决, 增加消费层心智负担且易写 bug. package plugin import "errors" // ErrInvalidSignature 表示 plugin 完整性 / 签名验证失败. // 所有 SignatureVerifier 实现失败时都应 wrap 此 sentinel, 便于调用方 errors.Is 判断. var ErrInvalidSignature = errors.New("plugin integrity verification failed") // SignatureVerifier 是 plugin 完整性 / 签名验证器接口. // // 调用时机: LoadPluginWithVerifier 在加载 manifest 后, 加载 skills/hooks/MCP 前 // 调用 Verify. 返回非 nil 即拒绝加载整个 plugin. // // 实现必须**无状态 + 线程安全**, 因为同一个 Verifier 会被 LoadAll 并发调用多次. // // **宽松 vs 严格策略不在 interface 层预设**, 由实现自己决定: // - NoopVerifier: 永远放行 (零校验) // - SHA256IntegrityVerifier: plugin.checksum 不存在放行, 存在则校验 // - RejectUnsignedVerifier: 包装 Inner, 无 checksum 时直接拒绝 (decorator 模式) // // 调用方通过**组合 verifier** 来表达策略, 而不是在 interface 里开 bool 参数. // 这种 "verifier 自治策略" 的设计让 loader 保持简单 (只管调 Verify), // 同时支持未来任意策略组合 (e.g. "宽松 + 白名单" / "严格 + Sigstore fallback"). // // Shape: synchronous callback. Loader calls Verify synchronously before // activating a plugin; verifier consults checksum / signature / trust store // as needed and returns accept or rejection reason. // // 形态: 同步回调. Loader 在激活 plugin 前同步调 Verify; verifier 按需查 // checksum / 签名 / 信任存储, 返回接受或拒绝原因. type SignatureVerifier interface { // Verify 验证 plugin 目录的完整性 / 签名. // // 参数: // - pluginDir: plugin 目录的绝对路径 // - manifest: 已加载的 Manifest (可能为 nil, 虽然 LoadPluginWithVerifier // 总是传非 nil, 但 verifier 应该容错处理) // // 返回 nil 表示通过, 非 nil 表示拒绝加载. Verify(pluginDir string, manifest *Manifest) error } // NoopVerifier 是"零校验"实现, 对任何输入都返回 nil. // // 用途: // - DefaultVerifier() 默认返回此实现 (保持向后兼容) // - 单元测试中不关心完整性的场景 // - 本地开发时临时禁用校验 // // 注意: 生产部署中如果启用了完整性校验, 应该用 SHA256IntegrityVerifier (宽松) // 或 RejectUnsignedVerifier (严格), 不要直接用 NoopVerifier. // // 精妙之处 (CLEVER): NoopVerifier 是空 struct, 零内存占用, 天然线程安全. // 即便 LoadAll 并发加载 100 个 plugin, 共用一个实例也毫无代价. type NoopVerifier struct{} // Verify 对 NoopVerifier 永远返回 nil. func (NoopVerifier) Verify(pluginDir string, manifest *Manifest) error { return nil } // DefaultVerifier 返回默认的 SignatureVerifier. // // 当前默认是 NoopVerifier (向后兼容, 不强制完整性校验). // 消费层若想启用完整性校验, 应显式传入 NewSHA256IntegrityVerifier() 到 // LoadPluginWithVerifier. // // 未来可能切换默认为 SHA256IntegrityVerifier (宽松模式): 当生态成熟到大部分 // plugin 都会 ship plugin.checksum 文件时, 默认启用宽松模式完整性校验是 // 无摩擦的 (没 checksum 的 plugin 继续放行, 有的被校验). 那个时点的切换由 // 产品决策, 不是代码决策, 改这里一行即可. // // 替代方案: <硬编码 NoopVerifier 到 LoadPlugin 内部> - 否决, 独立函数让 // 未来切换默认值只需改一行, 不需要在 loader.go 里做 search-replace. func DefaultVerifier() SignatureVerifier { return NoopVerifier{} }