Moby 仓库内嵌 PKCS7 库源码剖析:签名、验签、加解密全流程与签名镜像应用
Moby 仓库内嵌 PKCS7 库源码剖析签名、验签、加解密全流程与签名镜像应用【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby本指南以 MobyDocker仓库 vendor 目录下内嵌的 github.com/digitorus/pkcs7 库原 Mozilla Services pkcs7 的衍生版本为主体逐层讲解 PKCS#7 SignedData / EnvelopedData 的创建与解析API、附加式与分离式签名工作流以及基于证书公钥信封加密与 PSK 加密等全部能力。读完本文你将掌握在 Go 中实现签名 DER 输出 解析回验闭环的完整代码范式并能顺藤摸瓜理解该库在镜像签名等应用中的底层数据形态。一文读懂这个依赖它是什么、出现在哪里PKCS#7公钥密码标准第 7 号定义了一类用于数据封装的通用语法典型用途包括数字签名SignedData、信封加密EnvelopedData与加密数据EncryptedData。本仓库 vendor 目录下引入的 github.com/digitorus/pkcs7 是一个纯 Go 实现功能定位在 README 中一句话即可概括implements parsing and creating signed and enveloped messages即同时支持“解析”与“创建”两类 PKCS#7 消息。它是 fullsailor/pkcs7 的衍生 fork见 README Credits 一节在 Moby 仓库中该依赖以间接依赖indirect形式出现在根 go.mod版本为 v0.0.0-20230818184609模块替换记录在 vendor/modules.txt同时它也被 sigstore/timestamp-authority 与 digitorus/timestamp 引用服务于时间戳/签名验证链路Moby 业务侧真正消费该库能力的代码分布在 daemon/containerd/image_pull.go含 sigstore 相关引用与 api/types/image/signer_identity.go 等文件这印证了 PKCS#7 签名结构在镜像供应链安全场景中的实际应用。库内文件清单vendor/github.com/digitorus/pkcs7/如下它们是后续每一节的源码依据文件职责pkcs7.go顶层结构体、OID 常量、DER 入口 Parse、ASN.1 编解码辅助sign.goSignedData 构建NewSignedData / AddSigner / AddSignerChain / Detach / Finishverify.go验签与证书链校验Verify / VerifyWithChain / VerifyWithOptsencrypt.goEnvelopedData / EncryptedData 加密decrypt.go与加密对应的解密能力ber.go容错的 BER→DER 转换供 Parse 预处理输入签名与验签的权威示例从 README 出发的完整闭环README 给出了一个可直接运行的签名-验签闭环函数这是理解整个库工作流的“最小完整代码”。为符合本仓库的模块导入路径vendor 化后为github.com/digitorus/pkcs7下面做了等价改写并保留全部语义package main import ( bytes crypto/rsa crypto/x509 encoding/pem fmt os github.com/digitorus/pkcs7 ) func SignAndDetach(content []byte, cert *x509.Certificate, privkey *rsa.PrivateKey) (signed []byte, err error) { // 1) 初始化 SignedData默认摘要算法为 SHA1 toBeSigned, err : pkcs7.NewSignedData(content) if err ! nil { err fmt.Errorf(Cannot initialize signed data: %s, err) return } // 2) 加入签名者证书 私钥 空配置 if err toBeSigned.AddSigner(cert, privkey, pkcs7.SignerInfoConfig{}); err ! nil { err fmt.Errorf(Cannot add signer: %s, err) return } // 3) 转为分离式签名若省略 Detach() 则为内嵌内容签名 toBeSigned.Detach() signed, err toBeSigned.Finish() if err ! nil { err fmt.Errorf(Cannot finish signing data: %s, err) return } // 4) 输出 PEM 并以 DER 方式解析回读 pem.Encode(os.Stdout, pem.Block{Type: PKCS7, Bytes: signed}) p7, err : pkcs7.Parse(signed) if err ! nil { err fmt.Errorf(Cannot parse our signed data: %s, err) return } // 5) 分离式签名下内容不在包内需要手动“重新挂回”再比对 p7.Content content if bytes.Compare(content, p7.Content) ! 0 { err fmt.Errorf(Our content was not in the parsed data:\n\tExpected: %s\n\tActual: %s, content, p7.Content) return } // 6) 验签默认使用空信任库仅验证密码学签名 if err p7.Verify(); err ! nil { err fmt.Errorf(Cannot verify our signed data: %s, err) return } return signed, nil }该示例串起了六步典型流程也正好对应源码中的核心 API初始化NewSignedData(content)在 sign.go 中先把内容做 ASN.1 编码放入ContentInfo内容类型为OIDDataSignedData 版本设为 1并将摘要算法默认置为 SHA1添加签名者AddSigner在 sign.go 中只是AddSignerChain传入空父证书链的包装分离内容Detach()sign.go把ContentInfo重置为只剩OIDData的空结构从而生成 S/MIME 中常见的application/pkcs7-signature式分离签名封包输出Finish()sign.go把证书集与整个 signedData 做 ASN.1 编码再包上OIDSignedData的外层ContentInfo解析回读Parse依据外层 ContentType 分发见下文验签Verify()以空信任库验签只做密码学校验不做 CA 信任链校验。注意README 原示例使用go.mozilla.org/pkcs7的导入路径本仓库 vendor 目录下实际路径为github.com/digitorus/pkcs7实际编译时以仓库内 go.mod 与 modules.txt 记录为准。底层数据模型与算法家族PKCS7 顶层结构与支持的 ContentTypePKCS7 是解析结果的门面结构体公开字段包括Content、Certificates、CRLs与Signers另有不可导出的raw字段保存原始 ASN.1 载荷验签/解密时按需还原type PKCS7 struct { Content []byte Certificates []*x509.Certificate CRLs []pkix.CertificateList Signers []signerInfo raw interface{} }外层contentInfo通过ContentType区分消息种类。库在 pkcs7.go 中定义了一系列 OID 常量其中内容类型包括OIDData1.2.840.113549.1.7.1裸数据OIDSignedData.7.2签名数据OIDEnvelopedData.7.3信封数据OIDEncryptedData.7.6加密数据。对应地Parse 在完成 BER→DER 转换后用asn1.Unmarshal解出ContentType再分流给parseSignedData、parseEnvelopedData、parseEncryptedData遇到不认识的内容类型则返回 ErrUnsupportedContentType错误文案注明“当前仅支持 Data、Signed Data 与 Enveloped Data”。签名相关属性 OID 与算法矩阵签名侧被三类 OID 覆盖在 pkcs7.go 中成组定义签名属性 OIDOIDAttributeContentType1.2.840.113549.1.9.3、OIDAttributeMessageDigest.9.4内容摘要、OIDAttributeSigningTime.9.5签名时刻摘要算法 OIDSHA1 / SHA256 / SHA384 / SHA512以及 DSA、ECDSA 对应变体签名签名者信息中的加密算法 OIDRSA 及其RSASHA1/256/384/512变体、ECDSA P256/P384/P521、Ed25519内容加密算法 OIDDES-CBC、DES-EDE3-CBC3DES、AES-128/256-CBC、AES-128/256-GCM。摘要到crypto.Hash的映射由 getHashForOID 完成证书签名算法到摘要 OID 的转换由 GetDigestOIDForSignatureAlgorithm 完成。私钥类型到DigestEncryptionAlgorithmOID 的推断在 getOIDForEncryptionAlgorithmRSA 依据摘要 OID 选择RSASHA*变体ECDSA 选择ECDSASHA*变体Ed25519 则直接映射到OIDEncryptionAlgorithmEDDSA25519。需要注意的是DSA 私钥由于未实现crypto.Signer代码中为其做了特判。签发从 NewSignedData 到 Finish 的链路拆解签发侧的核心类型是 SignedData它持有内部 ASN.1 结构signedData、待签证书、原始内容、消息摘要以及摘要/加密算法的 OID。链式签名与签名者信息NewSignedData构造版本 1 的 SignedData内容类型固定为OIDData摘要算法默认 SHA1——如 README 提示“digest algorithm is set to SHA1 by default”可通过 SetDigestAlgorithm 或 SetEncryptionAlgorithm 在 AddSigner 之前调整AddSignerChain真正的实现逻辑。它依据 RFC 2315 9.2 节把末端实体证书的 issuerserial 写入issuerAndSerialNumber无父链时 issuer 即证书自身RawIssuer有父链时先用 verifyPartialChain 校验父子签名再取第一个父证书的RawSubject计算内容摘要messageDigest按顺序构造三类认证属性ContentType、MessageDigest、SigningTimeUTC now随后追加config.ExtraSignedAttributes中的自定义属性通过 signAttributes 对属性集合的 DER 编码签名——这是 PKCS#7 标准做法签名作用于认证属性而非裸内容将signerInfo并入SignerInfos证书按需写入certsSkipCertificates可跳过。SignerInfoConfig 三个可选字段ExtraSignedAttributes、ExtraUnsignedAttributes附加在签名外的属性与SkipCertificates。为兼容旧 APK 而生的 SignWithoutAttrSignWithoutAttr 是一种不带任何认证属性的签名直接对内容做摘要并签名注释明确说明它服务于“旧 Android APK”签名场景仅当需要向后兼容旧应用时才应使用。它同样为 DSA 与 Ed25519 做了特殊分支处理。辅助能力证书链、内容类型与属性清理AddCertificate向载荷追加证书常用于父证书SetContentType修改内容类型例如按 RFC 3161 2.4.2 指定时间戳令牌的内容类型RemoveAuthenticatedAttributes / RemoveUnauthenticatedAttributes清除认证/非认证属性语义对应 OpenSSL 的PKCS7_NOATTR/-noattr标志DegenerateCertificate构造“仅含证书/证书链的 SignedData”结构即 PKCS#7 证书分发文件如.p7b的底层形态。解析与验签信任链如何被接入Parse 的 BER→DER 容错预处理Parse 先调用 ber2der 把输入统一转成严格 DER再交给encoding/asn1解码。这种容错处理使库能消化部分 BER 编码的不规范输入例如支持 indefinite-length 结构、多字节长度域等编码规则详见 ber.go 的 encodeLength随后才进入parseSignedDataverify.go完成证书解析、内容还原与 SignerInfos 收集。Verify 家族的三个层次验签在 verify.go 中按信任要求递增提供三个入口Verify()等价于VerifyWithChain(nil)信任库为空只验签名、不验 CA 链VerifyWithChain(truststore)把消息内证书装入Intermediates池Roots 为调用方传入的x509.CertPool若消息含 SigningTime 属性则证书在“该时刻 UTC 现在”分别校验有效性VerifyWithChainAtTime显式传入校验时刻且不使用 SigningTime 属性VerifyWithOpts(opts)最底层入口直接接受 x509.VerifyOptions允许完全自定义 Roots、Intermediates、KeyUsages 与 CurrentTimeKeyUsages 未设置时默认ExtKeyUsageAny。无论走哪个入口单签名者的核心校验逻辑verifySignature/verifySignatureAtTime都包含四个动作verify.go依据 signerInfo 的 issuerserial 在消息证书中定位签名者证书用subtle.ConstantTimeCompare常数时间比较还原内容摘要与认证属性中 MessageDigest 的一致性避免时序侧信道若消息带 SigningTime 属性校验签名时刻落在证书 NotBefore/NotAfter 区间通过 getSignatureAlgorithm 把摘要加密算法 摘要算法映射回x509.SignatureAlgorithm最后ee.CheckSignature做真正的密码学验签。摘要不一致时会返回带期望/实际十六进制摘要的 MessageDigestMismatchError。此外还有两个便捷方法GetOnlySigner 在恰有一个签名者时返回其证书UnmarshalSignedAttribute 从第一个 signer 的认证属性中解码指定属性。信封加密与解密Encrypt / Decrypt / PSK 三个视角除签名外库还实现 S/MIME 信封加密。包级变量 ContentEncryptionAlgorithm 决定全局默认对称算法初始值为EncryptionAlgorithmDESCBC可用常量切换为 AES-128/256-CBC 与 AES-128/256-GCMencrypt.go。代码注释对 CBC 变体明确给出“除非互操作必须否则使用 AES-GCM”的安全建议。公开 API 一览Encrypt(content, recipients)生成 EnvelopedData。流程为——先按当前全局算法生成随机对称内容密钥并加密内容再用 RSA-OAEPPKCS#1 v1.5把内容密钥逐一加密给每个收件人公钥encryptKeyRSA 是当前唯一受支持的密钥封装算法最终组装成 Version 0 的envelopedData并包上OIDEnvelopedData外层EncryptUsingPSK(content, key)调用者直接提供预共享密钥PSK得到OIDEncryptedData消息key 为 nil 时报 ErrPSKNotProvided。PSK 路径只支持 DES-CBC 与 AES-GCM 两类Decrypt(cert, pkey)收件人用私钥先解开 EncryptedKeyRSA PKCS#1 v1.5再按内容加密算法解密内容DecryptUsingPSK(key)PSK 对应解密内部统一走 encryptedContentInfo.decrypt。解密层对 DES-CBC、3DES、AES-128/256-CBC、AES-GCM 均能识别并兼容“EncryptedContent 由多个 OCTET STRING 拼接”与“整体 tag 0 封包”两种 ASN.1 形态。关于算法细节AES-GCM 的 nonce 长 12 字节nonceSizeAES-GCM 参数结构体中除 nonce 外还记录了 ICVLen 认证标签长度encrypt.goCBC 路径采用PKCS#7 pad补齐明文pad。在 Moby 中的实际应用与阅读地图当前仓库并非只把该库当作孤立依赖PKCS#7 签名结构在 Moby 的镜像签名生态中承担着底层数据载体角色。可从以下位置继续追踪其用法api/types/image/signer_identity.go定义镜像签名者身份相关的类型是签名数据模型在公开 API 层的投影daemon/containerd/image_pull.gocontainerd 拉取路径中与 sigstore 组件协作间接依赖 PKCS#7 签名/时间戳验证能力依赖链参考go.mod 中的 digitorus/pkcs7 与 go.mod 中的 sigstore/timestamp-authority真正消费验证入口在 vendor/github.com/sigstore/timestamp-authority/v2/pkg/verification/verify.go 与 vendor/github.com/digitorus/timestamp/timestamp.go。对于希望做 PKCS#7 互操作如与 OpenSSLsmime -sign/openssl pkcs7结果互相验证的读者最直接的自测路径是用文首的SignAndDetach生成 DER 数据再以openssl pkcs7 -inform DER -print观察其 ASN.1 结构是否与 sign.go 中signedData的字段布局一致SignerInfos、Certificates、DigestAlgorithmIdentifiers、authenticatedAttributes[0] 等。常见问题速查分离式签名如何验签分离签名包内不含原文。参照 README 示例先p7.Content content手工挂回再调用Verify()。想启用信任链校验用 VerifyWithChain 传入x509.CertPool进一步控制 EKU、时间等则直接用 VerifyWithOpts。默认摘要算法太弱NewSignedData后、AddSigner前调用SetDigestAlgorithm(pkcs7.OIDDigestAlgorithmSHA256)即可升级为 SHA-256相关 OID 常量见 pkcs7.go。签名不含证书AddSigner传入的SignerInfoConfig{SkipCertificates: true}可在签名消息中不嵌入末端实体证书。为什么解析失败提示 unsupported content type当前实现仅覆盖 Data、SignedData、EnvelopedData 与 EncryptedData见 ErrUnsupportedContentType其它 PKCS#7 类型如 DigestData、AuthenticatedData暂不支持。想验证证书链部分路径签名侧 verifyPartialChain 只要求传入的父链在签名上自洽无需父链顶端是受信根可用于构造含父证书的链式签名。【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考