Tekton Pipeline 依赖库 go-fed/httpsig:HTTP Signatures 请求/响应签名与验证实现解析
发布时间:2026/9/25 8:53:55 作者:尧图编辑部 阅读量:1,286

云原生CI/CDDevOps后端【免费下载链接】pipelineA cloud-native Pipeline resource.项目地址https://gitcode.com/gh_mirrors/pipelin/pipeline点击查看免费下载本文以 Tekton Pipeline 仓库中 vendored 的第三方库go-fed/httpsigv1.1.0的 README 为核心系统讲解 HTTP Signatures 方案的 Go 实现如何构造Signer对 HTTP 请求与响应进行签名、如何构造Verifier完成验签、支持哪些签名与摘要算法、签名串是如何拼接的并结合同仓库中 vendored 的 Gitea SDK 源码展示该库在真实工程里“用 SSH 密钥对 API 请求签名”的落地用法。一、这个库在 Tekton Pipeline 仓库中的位置go-fed/httpsig是 IETF HTTP Signatures 草案draft-cavage-http-signatures的 Go 语言实现其 README 明确给出的定位是对 HTTP 请求或响应进行签名与验证支持 MAC、HMAC 以及对摘要hash的 RSA 签名等多种算法组合。在本仓库中它以间接依赖的方式被 vendor 进来go.mod 第 114 行声明github.com/go-fed/httpsig v1.1.0 // indirect即 Tekton Pipeline 主模块本身不直接 import 该包而是经由其他依赖引入vendor/modules.txt 第 369–371 行同样记录了该模块的版本与包路径从源码结构看本仓库内直接 import 该库的代码是 vendored 的 Gitea SDKhttpsign.go 中以legacyhttpsig github.com/go-fed/httpsig命名导入与它的后继 forkgithub.com/42wim/httpsig并列使用。Tekton Pipeline 的 Gitea 集成测试如 test/resolvers_gitea_test.go、test/git-resolver/gitea.yaml依赖 Gitea SDK 与 Gitea 实例交互因此该签名库最终服务于“客户端对 Gitea API 请求做签名认证”这一场景。理解这一依赖关系很重要阅读本库时不应把它当作 Tekton 控制器的核心组件而应把它看作一条传递依赖链上的基础设施——它保证了与 Gitea尤其是较老版本 Gitea通信时API 请求可以通过 HTTP 签名而非仅靠 Bearer Token 完成身份认证。二、README 声明的设计目标README 开宗明义地列出了库的设计目标这些目标与源码实现一一对应提供非常简单、统一的签名与验证接口——对应Signer/Verifier两个接口支持多种签名算法及其组合——对应 httpsig.go 中的Algorithm常量族签名可以写入Authorization或Signature两个 HTTP 头之一——对应SignatureScheme类型参与签名的请求头集合是灵活配置的——对应NewSigner的headers参数同时支持 HTTP 请求与响应——对应SignRequest与SignResponse明确不支持已知密码学不安全的算法——对应 algorithms.go 中isForbiddenHash对 MD4、MD5、MD5SHA1 的封禁自动计算并签名Digest头保证消息体不被篡改——对应 digest.go 的实现。安装与导入方式按 README 说明为go get github.com/go-fed/httpsig代码中import github.com/go-fed/httpsig在本仓库中则通过 vendor 机制使用无需额外安装。三、签名Signer 的创建与使用3.1 README 的签名示例完整继承README 给出的请求签名示例是创建Signer后调用SignRequest。要点是签名前必须保证Date、Digest头与r.URL已设置并传入用于生成摘要的 body 副本func sign(privateKey crypto.PrivateKey, pubKeyId string, r *http.Request) error { prefs : []httpsig.Algorithm{httpsig.RSA_SHA512, httpsig.RSA_SHA256} digestAlgorithm : DigestSha256 // The Date and Digest headers must already be set on r, as well as r.URL. headersToSign : []string{httpsig.RequestTarget, date, digest} signer, chosenAlgo, err : httpsig.NewSigner(prefs, digestAlgorithm, headersToSign, httpsig.Signature) if err ! nil { return err } // To sign the digest, we need to give the signer a copy of the body... // ...but it is optional, no digest will be signed if given nil body : ... // If r were a http.ResponseWriter, call SignResponse instead. return signer.SignRequest(privateKey, pubKeyId, r, body) }响应签名的用法相同只是将SignRequest换成SignResponse第二个参数改为http.ResponseWriter。需要特别指出的是vendored 的 v1.1.0 源码中NewSigner的实际签名比 README 示例多了一个expiresIn int64参数定义为见 httpsig.gofunc NewSigner(prefs []Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (Signer, Algorithm, error)其语义是按prefs顺序尝试创建签名器第一个可用的算法生效并随返回值给出若所有偏好算法都不可用则回退到默认算法RSA_SHA256见 algorithms.go。传入未知或已知密码学不安全的Algorithm会直接报错。expiresIn非 0 时签名器会在签名时记录created时间戳并计算expires created expiresIn这两个值随后写入签名的created与expires参数见 signing.go。v1.1.0 还新增了NewSSHSignerhttpsig.go接受ssh.Signer而非裸crypto.PrivateKey目前仅支持 ed25519 与 RSA 两类 SSH 密钥getSSHAlgorithm的映射见 httpsig.go这为直接使用 OpenSSH 密钥文件做签名提供了更贴近运维习惯的入口。3.2 签名内部流程Digest、签名串与签名头以asymmSigner.SignRequestsigning.go为例一次签名分为三步写 Digest若传入的body非 nil则先调用addDigest计算 body 的摘要并写入Digest头digest.go。摘要格式为算法 base64(摘要)例如SHA-256 base64...若Digest头已存在会报错防止覆盖。body 为 nil 时跳过不生成摘要。构造签名串signatureStringsigning.go按headers列表顺序逐行拼接参与签名的字段。每一行形如小写头名: 值行与行之间用\n分隔同名头多值时用,连接。其中三个特殊项httpsig.RequestTarget值为(request-target)展开为小写化的请求方法、URL 路径与查询串如post /api/x?q1见addRequestTargetsigning.go(created)、(expires)展开为对应时间戳数值缺失时报错若headers列表为空则默认仅签名Date头defaultHeaderssigning.go响应签名不允许出现(request-target)requestTargetNotPermitted会返回错误因为方法/URI 只对请求有意义。写出签名头setSignatureHeadersigning.go把 keyId、算法、时间戳、头列表与 base64 编码的签名值拼成一个键值参数串写入目标头。生成的头形如Signature: keyIdmy-key,algorithmhs2019,created1234,expires1244,headers(request-target) date digest,signaturebase64...注意源码中algorithm参数固定写死为hs2019signing.go 有注释说明真实算法被隐藏、以适配较新的草案版本真实算法由验证方通过带外信息确定同时 README 也强调pubKeyId将在验证阶段被用来定位公钥。3.3 并发安全Signer 不是线程安全的README 用专门的篇幅提醒Signer不能在不加保护的情况下被多个 goroutine 共享并给出了用互斥锁守护的示例type server struct { signer httpsig.Signer mu *sync.Mutex } func (s *server) handlerFunc(w http.ResponseWriter, r *http.Request) { privateKey : ... pubKeyId : ... // Set headers and such on w s.mu.Lock() defer s.mu.Unlock() // To sign the digest, we need to give the signer a copy of the response body... // ...but it is optional, no digest will be signed if given nil body : ... err : s.signer.SignResponse(privateKey, pubKeyId, w, body) if err ! nil { ... } ... }这一约束来自实现Signer接口注释明确写着 “Signers are not safe to use between multiple goroutines”httpsig.go其内部持有headers、created/expires等可变状态签名过程会改写请求头。服务端的常见做法如 README 示例所示加锁或者干脆每次请求新建签名器。四、支持的算法与摘要清单与禁区从 httpsig.go 的Algorithm常量定义可以完整列出 v1.1.0 支持的算法MAC 类对称密钥密钥类型为[]byteHMAC_SHA224、HMAC_SHA256、HMAC_SHA384、HMAC_SHA512、HMAC_RIPEMD160、HMAC_SHA3_224/256/384/512、HMAC_SHA512_224/256、HMAC_BLAKE2S_256、HMAC_BLAKE2B_256/384/512此外 BLAKE2 系列还可作为裸 MAC 算法使用BLAKE2S_256、BLAKE2B_256/384/512验证时采用常量时间比较见 algorithms.goRSA 非对称类RSA_SHA1、RSA_SHA224、RSA_SHA256默认算法、RSA_SHA384、RSA_SHA512、RSA_RIPEMD160ECDSA 类ECDSA_SHA224/256/384/512、ECDSA_RIPEMD160签名结果按 ASN.1 编码ECDSASignature{R, S}algorithms.goED25519常量ED25519注释说明“只能配合 SHA512”ED25519 内部固定哈希algorithms.go。算法字符串解析入口signerFromString/macerFromStringalgorithms.go先按前缀rsa-、ecdsa-、ed25519、hmac-归类再解析哈希名哈希名必须命中内部表且通过isForbiddenHash检查algorithms.goMD4、MD5、MD5SHA1 被硬性封禁返回 “forbidden hash type” 错误。IsSupportedHttpSigAlgorithmalgorithms.go对外暴露“某算法是否可用”的查询接口其判定 表中存在 非禁用 平台可用crypto.Hash.Available()。摘要算法Digest头只开放两种digest.goDigestSha256SHA-256与DigestSha512SHA-512IsSupportedDigestAlgorithm负责校验。这体现了 README “明确不支持弱算法” 的目标在两层签名算法、摘要算法上的落地。五、验证Verifier 的构造、时间窗口与验签5.1 README 的验证示例完整继承验证侧的职责划分是应用通过KeyId()拿到 keyId再由应用自己负责据此取回公钥、确定算法然后调用Verifyfunc verify(r *http.Request) error { verifier, err : httpsig.NewVerifier(r) if err ! nil { return err } pubKeyId : verifier.KeyId() var algo httpsig.Algorithm ... var pubKey crypto.PublicKey ... // The verifier will verify the Digest in addition to the HTTP signature return verifier.Verify(pubKey, algo) }README 同时说明Verifier也不具备跨 goroutine 并发安全性但由于验证器是按请求/按响应创建的这一限制在实际中很少构成问题。5.2 验证器构造细节头探测、时间窗口与 Host 注入NewVerifierhttpsig.go与NewResponseVerifierhttpsig.go内部共用newVerifierverifying.go关键行为包括头位置探测getSignatureSchemeverifying.go检查Signature与Authorization两个头中哪一个携带签名参数以是否含keyId/headers/signature字段判断。两者都携带会报 “both ... have signature parameters”都不携带报 “neither ...”Authorization头会先剥掉Signature认证方案前缀。参数解析getSignatureComponentsverifying.go把参数串按,切分并解析keyId、created、expires、headers、signaturekeyId或signature缺失即报错headers缺省回落到Date不认识的参数一律忽略草案中的algorithm参数已废弃解析时直接忽略与签名端写死hs2019相呼应。10 秒时钟偏差容忍构造验证器时就做时间校验——created领先当前时间超过 10 秒报 “created is in the future”expires落后当前时间超过 10 秒报 “signature expired”verifying.go。因此签名方设置expiresIn时窗口必须明显大于 10 秒才可靠。Host 头补齐Go 服务端解析请求时通常不保留Host到 header mapNewVerifier会检测并用r.Host补写Host头httpsig.go使得把Host列入签名头列表在验证端也能取到值。验签Verify(pKey, algo)verifying.go先按算法前缀判断走非对称asymmVerify重算签名串 → base64 解码 signature → 调用signer.Verify还是 MAC 路径macVerify以[]byte密钥重算 MAC 并比较。键类型约束与签名侧一致MAC 用[]byteRSA 用*rsa.PublicKey/*rsa.PrivateKeyECDSA/ED25519 同理。六、仓库内真实用例Gitea SDK 用 SSH 密钥签名 API 请求vendored 的 Gitea SDK httpsign.go 的Client.SignRequest是该库在本仓库中最直接的调用现场完整展示了 README 所述“签名请求”流程的实战形态headersToSign : []string{httpsig.RequestTarget, (created), (expires)} // 若持有 SSH 证书把证书放入 x-ssh-certificate 头并纳入签名 if c.httpsigner.cert { // ... r.Header.Add(x-ssh-certificate, certString) headersToSign append(headersToSign, x-ssh-certificate) } // 若有 body则让库自动添加 Digest 头并把 Digest 纳入签名 if r.Body ! nil { contents, _ io.ReadAll(body) headersToSign append(headersToSign, Digest) } // 旧版 Gitea 1.23使用 legacy 版 httpsiggo-fed/httpsig // 签名有效期 10 秒写入 Signature 头 legacySigner, _, err : legacyhttpsig.NewSSHSigner( c.httpsigner, httpsig.DigestSha512, headersToSign, legacyhttpsig.Signature, 10) // keyID 取公钥的 SHA-256 指纹证书模式下用 gitea return legacySigner.SignRequest(keyID, r, contents)这段代码印证了前文各节的多个要点签名头列表包含(request-target)、(created)、(expires)与expiresIn10秒配合形成 10 秒有效窗口有 body 时把Digest加入签名列表从而把“摘要 签名”双保险地绑定到消息体keyID采用ssh.FingerprintSHA256(公钥)验证端Gitea 服务端即可据此定位用户。同时它揭示了 go-fed/httpsig 与 fork 版 42wim/httpsig 的分工checkServerVersionGreaterThanOrEqual(version1_23_0)判定服务器版本后老版本 Gitea 走legacyhttpsig即本 README 对应的库新版本走 fork——这是“legacy” 命名的由来也解释了该库在 go.mod 中以 indirect 身份存在的原因。七、使用要点与限制小结综合 README 与 v1.1.0 源码使用阅读该库时应注意算法偏好列表要给出可回退项NewSigner会跳过不可用/不安全的算法并在全部失败时回退RSA_SHA256想强制失败时不要依赖它而应自己检查返回的chosenAlgo键类型必须匹配MAC 算法要求crypto.PrivateKey/crypto.PublicKey底层是[]byteRSA 要求*rsa.PrivateKey/*rsa.PublicKey不匹配会得到显式错误signing.go、verifying.goDigest头只能由库设置一次addDigest在头已存在时报错调用方不要提前手写Digest时间窗口以 10 秒为容忍度设置expiresIn时应显著大于 10 秒验证端对created未来时间、expires过期时间都按 ±10 秒偏差判定签名头二选一Signature与Authorization带Signature认证方案前缀不能同时携带签名参数验证端会直接拒绝并发模型Signer与Verifier均非 goroutine 安全长期复用的签名器需加锁README 给出的sync.Mutex模式验证器则建议按请求新建README 示例与 vendored 版本的偏差README 中NewSigner为四参调用而 vendored v1.1.0 为五参新增expiresIn且新增了NewSSHSigner以本仓库 httpsig.go 的实际签名为准。本文所有结论均来自本仓库内 README 与 vendored 源码httpsig.go、signing.go、verifying.go、algorithms.go、digest.go、httpsign.go可直接按文中路径深入阅读原始实现。赞分享云原生CI/CDDevOps后端【免费下载链接】pipelineA cloud-native Pipeline resource.项目地址https://gitcode.com/gh_mirrors/pipelin/pipeline点击查看免费下载相关推荐maku-boot接口签名请求签名验证机制maku boot接口签名请求签名验证机制 概述 在分布式系统和微服务架构中接口安全性是至关重要的环节。maku boot作为企业级低代码平台提供了完善的低代码后端curl 请求签名组件定制--httpsig-headers 详解与 RFC 9421 HTTP Message Signatures 实践curl 请求签名组件定制 httpsig headers 详解与 RFC 9421 HTTP Message Signatures 实践 本篇指南以 curCLI网络通信Zoom Webhooks 验证指南URL 校验CRC与请求签名验签实战Zoom Webhooks 验证指南URL 校验CRC与请求签名验签实战 导读 本文围绕 Zoom Webhooks 的 身份验证机制 展开完整讲解端点AI 技能AI 插件上一篇Prisma 文档指南下一篇Simplefolio模板定制完全教程从颜色主题到内容替换创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考