跳到主要内容

03 · 可信:签名与命名

本章讲 dir 怎么把“这条记录是谁发的、有没有被改、名字归不归他”做成可验证的。核心机制是 referrer 附件:可信信息挂在记录旁边,而不是塞进记录里。

1. 这章要解决的小问题

01 章 的 CID 已经保证了“内容没被改”(改了 CID 就变)。但还有两个问题它管不了:

  1. 是谁发的? —— CID 只证明内容完整,不证明来源。
  2. 名字归不归他? —— 记录里写 name: cisco.com/agent,凭什么相信它真是 cisco.com 发的?

这两件事靠签名域名所有权校验解决,且都设计成叠加层——不影响存储/发现主路径。

2. 核心机制:referrer(附件)

直觉: 记录本身不可变(CID 锁死),那签名往哪放?答案是放在旁边——一个指向记录 CID 的“附件对象”,叫 referrer。

referrer 的结构(proto/agntcy/dir/core/v1/record.proto:68 RecordReferrer):带一个 type(如 "agntcy.dir.sign.v1.Signature")、一个指向主记录的 record_ref、和实际数据 data。OCI 仓库天然支持这种“artifact 引用 artifact”的关系。

为什么这么设计: 同一条记录可以挂多个 referrer——多个签名者、一个公钥附件、未来还能挂别的证明——而主记录的 CID 永远不变。可信是“往记录上贴标签”,不是“改记录”。

3. 主线一:签名(Sign)

签名是客户端侧操作(proto 注释:“This is a client-side service and is not available on the server”,proto/agntcy/dir/sign/v1/sign_service.proto)。流程在 client/sign.go:19(Client.Sign):

下图怎么读:从左到右,被签的对象是 CID 字符串本身,不是整条记录。

record (已有 CID)


对 CID 字符串签名 ──┬── Key 方式: cosign.SignBlobWithKey(CID, privKey)
(cosign) └── OIDC 方式: cosign.SignBlobWithOIDC(CID, oidc) ← keyless


得到 {signature, publicKey}


pushReferrersToStore():
• PushReferrer(type=PublicKey, ...) ← 公钥作为附件
• PushReferrer(type=Signature, ...) ← 签名作为附件

关键点:被签的载荷是 req.GetRecordRef().GetCid()(client/sign.go:38/:41),即对“内容指纹”签名。因为 CID 已经唯一锁定了内容,对 CID 签名 == 对内容签名,但载荷只有几十字节。

两种 provider:

方式怎么用适合
KeyPEM 私钥(可带 passphrase)自管密钥的私有场景
OIDC(keyless)走 Sigstore Fulcio/Rekor/OIDC,无需长期持有私钥公开可审计的供应链签名

OIDC 默认指向 Sigstore 公共服务(fulcio.sigstore.devrekor.sigstore.dev 等,proto/.../sign_service.protoSignOptionsOIDC)。可设 skip_tlog 跳过透明日志(私有签名,但就无法对 Rekor 验证)。

4. 服务端怎么接住签名 referrer

签名/公钥通过 StoreService.PushReferrer 推到服务端(server/controller/store.go:254 pushReferrer)。这里有两个联动副作用值得注意:

  • 收到 Signature 类型 referrer → s.db.SetRecordSigned(recordCID) 标记该记录“已签名”,好让后台 name 任务知道要去校验名字(server/controller/store.go:291-298)。
  • 收到 Signature PublicKey → InvalidateSignatureVerificationsForRecord 清掉缓存的签名验证结果,让 reconciler 重新验证、把新签名者(比如后补的公钥签名者)都算进去(:302-308)。

5. 主线二:域名所有权校验(Naming)

问题: 记录声称 name: cisco.com/agent,怎么确认发布者真控制 cisco.com?

机制: 后台 reconciler 的 name 任务周期性校验。流程(reconciler/README.md “Name Task”):

  1. 查数据库里“已签名、带可验证名字(http:///https:// 前缀)、且校验缺失/过期”的记录。这个“可验证名字”的硬性前缀过滤就发生在这一步的 SQL 里(server/database/gorm/naming.go:133 GetRecordsNeedingVerification:name LIKE 'http://%' OR LIKE 'https://%')。
  2. 取记录名字 + 挂在记录上的公钥。
  3. 去名字对应的域名拉 JWKS(well-known 端点),核对公钥归属,确认名字所有权。
  4. 把校验结果缓存(带 TTL)。

校验是自动的、后台的(naming proto 注释:“Verification is performed automatically by the backend scheduler”,proto/agntcy/dir/naming/v1/naming_service.proto:11)。well-known 抓取器在服务端注入(server/server.go:300-304 wellknown.NewFetcher),校验 TTL 来自配置(server/server.go:318 Naming.GetTTL)。

客户端只读结果:NamingService.GetVerificationInfo 返回 verified 布尔 + 详情(proto/.../naming_service.proto:46)。

6. 命名解析(Resolve):Docker 风格的引用

Naming 还提供名字 → CID 的解析,语法刻意模仿 Docker(proto/agntcy/dir/naming/v1/naming_service.proto:18-25 Resolve):

引用形式含义
name该名字的所有版本(最新在前)
name:version指定版本
name@cid哈希校验过的查找(最新版本)
name:version@cid哈希校验过的指定版本

@cid 形式很妙:名字解析 + 内容校验合一——你既用人类可读名字找,又用 CID 锚定“必须是这块内容”,防止名字被劫持指向别的内容。

7. 关键细节 / 坑

  • 签名/校验都不挡主路径。 未签名、未校验名字的记录照样能存、能发、能搜。可信是消费方按需验证的叠加信息,不是准入门槛。这是“capability discovery 可能主观、但 integrity/provenance 可密码学保证”这一设计哲学的体现(README “Verifiable Claims”)。
  • 签名验证结果是缓存的、会失效重算。 别把它当一次性事实——后补公钥、新签名者都会触发重验(server/controller/store.go:302)。
  • 名字必须带 http(s):// 前缀才进入可验证流程。 普通名字不会被 reconciler 校验——强制点是 server/database/gorm/naming.go:133 的 SQL 过滤(name LIKE 'http://%' OR LIKE 'https://%'),reconciler/README.md Name Task 第 1 步也描述了这一条件。
  • 签名服务在客户端,不在服务端注册。 server/server.go:313 注册的 NewSignController 只读数据库里的签名验证状态;真正“签”的动作在 client/sign.go,用本地/OIDC 凭证完成。

8. 小结与下一步

记住:可信 = 挂在 CID 旁边的 referrer 附件;签名对 CID 字符串签(Key 或 Sigstore keyless);域名所有权由后台用 JWKS 周期校验并缓存;名字解析支持 name:version@cid 把“按名找”和“按内容锚定”合一。

下一章看本地数据库的通配搜索(区别于 02 的标签搜索)以及全局边界 → 04 · 搜索索引与边界

9. 本章代码地图

主题文件符号
referrer 数据结构proto/agntcy/dir/core/v1/record.protoRecordReferrer
客户端签名入口client/sign.go(*Client).Sign
签名/公钥推为 referrerclient/sign.go(*Client).pushReferrersToStore
服务端接收 referrer + 联动副作用server/controller/store.go(storeCtrl).pushReferrer
Sign 服务定义(客户端侧)proto/agntcy/dir/sign/v1/sign_service.protoSignService
名字解析 + 校验信息proto/agntcy/dir/naming/v1/naming_service.protoNamingService
可验证名字前缀过滤(强制点)server/database/gorm/naming.go(*DB).GetRecordsNeedingVerification
well-known 抓取器注入server/server.gowellknown.NewFetcher
名字校验后台任务reconciler/README.mdName Task