You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang如何提取PDF数字签名数据并通过根证书完成验签

Golang 提取PDF数字签名并完成根证书验签实现

你提供的C#实现基于iText的签名处理能力,Go生态可以用unipdf库实现完全对等的功能,不需要从零解析PDF签名结构,证书校验、PKCS7验签直接用Go标准库就能完成,不需要额外找第三方加密依赖。

依赖说明

  • PDF签名解析:使用unipdf库,是目前Go生态对PDF数字签名规范支持最完整的实现,覆盖签名枚举、签名范围校验、PKCS7数据提取所有你C#代码用到的iText核心能力
  • 证书与签名校验:直接使用Go标准库crypto/x509、crypto/pkcs7即可,无需引入额外第三方加密组件

实现逻辑(完全对齐提供的C#代码流程)

  1. 读取根证书文件,解析为x509证书对象
  2. 加载目标PDF文件,初始化签名处理工具实例
  3. 枚举PDF内所有嵌入的数字签名字段
  4. 对单个签名依次做三项校验:
    • 校验签名覆盖范围,确认签名是否作用于整个文档,排除签名后追加内容的情况
    • 校验签名本身的完整性,确认文档签名后未被篡改
    • 提取签名携带的证书链,用预先加载的根证书校验签名者证书的信任链,确认签名者身份受信任

参考实现代码

package main

import (
	"crypto/x509"
	"errors"
	"fmt"
	"os"

	"github.com/unidoc/unipdf/v3/model"
	"github.com/unidoc/unipdf/v3/model/sighandler"
)

func Verify(pdfPath string, rootCertPath string) error {
	// 加载根证书 对齐C#中X509Certificate初始化逻辑
	rootCertBytes, err := os.ReadFile(rootCertPath)
	if err != nil {
		return fmt.Errorf("读取根证书失败: %w", err)
	}
	rootCert, err := x509.ParseCertificate(rootCertBytes)
	if err != nil {
		return fmt.Errorf("解析根证书失败: %w", err)
	}

	// 打开PDF 初始化签名工具 对齐C#中PdfDocument、SignatureUtil初始化逻辑
	f, err := os.Open(pdfPath)
	if err != nil {
		return fmt.Errorf("打开PDF文件失败: %w", err)
	}
	defer f.Close()
	pdfReader, err := model.NewPdfReader(f)
	if err != nil {
		return fmt.Errorf("解析PDF结构失败: %w", err)
	}
	signUtil, err := sighandler.NewPdfSignatureHandler(pdfReader)
	if err != nil {
		return fmt.Errorf("初始化签名处理工具失败: %w", err)
	}

	// 获取所有签名名称 对齐C#中GetSignatureNames逻辑
	signNames, err := signUtil.GetSignatureNames()
	if err != nil {
		return fmt.Errorf("获取PDF内签名列表失败: %w", err)
	}

	for _, signName := range signNames {
		// 校验签名覆盖范围 对齐C#中SignatureCoversWholeDocument逻辑
		coverWholeDoc, err := signUtil.SignatureCoversWholeDocument(signName)
		if err != nil {
			return fmt.Errorf("校验签名[%s]覆盖范围失败: %w", signName, err)
		}
		if !coverWholeDoc {
			fmt.Printf("警告:签名[%s]未覆盖整个文档,存在签名后追加内容的风险\n", signName)
		}

		// 读取PKCS7签名数据 校验签名完整性 对齐C#中ReadSignatureData、VerifySignatureIntegrityAndAuthenticity逻辑
		pkcs7Sign, err := signUtil.ReadSignatureData(signName)
		if err != nil {
			return fmt.Errorf("读取签名[%s]数据失败: %w", signName, err)
		}
		integrityValid, err := pkcs7Sign.VerifyIntegrity()
		if err != nil {
			return fmt.Errorf("校验签名[%s]完整性失败: %w", signName, err)
		}
		if !integrityValid {
			fmt.Printf("警告:签名[%s]完整性校验失败,文档签名后已被篡改\n", signName)
		}

		// 提取签名证书链 用根证书校验信任链 对齐C#中GetCertificates、certs[0].Verify逻辑
		certChain := pkcs7Sign.GetCertificates()
		if len(certChain) == 0 {
			return fmt.Errorf("签名[%s]未携带证书信息", signName)
		}
		signerCert := certChain[0]
		// 构造信任根池,加载传入的根证书
		trustPool := x509.NewCertPool()
		trustPool.AddCert(rootCert)
		_, err = signerCert.Verify(x509.VerifyOptions{
			Roots: trustPool,
		})
		certTrusted := true
		if err != nil {
			certTrusted = false
			if errors.Is(err, x509.CertificateInvalidError{}) || errors.Is(err, x509.UnknownAuthorityError{}) {
				fmt.Printf("警告:签名[%s]证书信任链校验失败,签名证书并非指定根证书签发\n", signName)
			} else {
				return fmt.Errorf("校验签名[%s]证书链失败: %w", signName, err)
			}
		}

		fmt.Printf("签名[%s]校验结果:覆盖全文档=%v, 内容完整性有效=%v, 证书链受信任=%v\n",
			signName, coverWholeDoc, integrityValid, certTrusted)
	}
	return nil
}

func main() {
	// 调用示例:传入PDF路径、根证书路径
	if err := Verify("target.pdf", "root.cer"); err != nil {
		panic(err)
	}
}

补充说明

  • 若需要校验带时间戳的签名,只需在读取PKCS7数据后增加时间戳有效性校验逻辑,unipdf已封装对应方法,不需要额外解析
  • 若需要校验证书吊销状态,可在x509.VerifyOptions中配置CRL或OCSP校验参数,Go标准库原生支持该能力
  • 注意unipdf用于商业场景需要符合其对应授权协议,个人非商用场景可免费使用

内容的提问来源于stack exchange,提问作者Andrey Osipov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 08:39:15