Help us shape the future of Go! Take the 2026 Go Developer Survey

sign

package
v0.1.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 21, 2026 License: Apache-2.0 Imports: 27 Imported by: 0

README

OFD 签署与验证

Sign 为 OFD 文档计算引用摘要、生成 Signature.xml,把原始 XML 字节交给外部命令, 并将命令输出的 SignedValue.dat 写回包中。外部命令协议见 ofd-creator。

CreateSeal、CreateSignedValue 提供 GB/T 38540-2020 SES V4 的 SM2/SM3 编码与签署。 证书与私钥由调用方提供;CreateSeal 的 Width、Height 是整数毫米,不是图片像素。 SignerCertificates 设置获授权签章者的完整 DER 证书;也可用 SignerDigests 设置 certListType=2 的 {Type, Value} 列表,支持 SM3、SHA256 及其 OID。 两种列表互斥,缺省授权制章者证书。印章名称按 UTF8String 编码。

CreateSignedValue 接收受保护的 Signature.xml 原始字节和该文件的包内路径。 签署前检查印章签名、授权、有效期、证书用途及证书与私钥匹配。SigningOptions.Validate 用于接入签署前的信任链、证书吊销及印章状态检查;调用方要求完整签署流程时必须提供。 SigningOptions.Timestamp 接收新生成的签名值字节,返回 DER TimeStampToken;可在回调中 调用带超时的 TSA 客户端。返回整个时间戳响应包或与签名值不匹配的令牌会被拒绝。 Extensions 支持非关键厂商扩展,critical=false 在 DER 中省略;未知关键扩展不能得到 完整验证通过结论。

验证器保留第三方显式编码 critical=FALSE 的解析信息和原始签名字节,但按 GB/T 38540 §6.1.2.6、DER(X.690 §11.5)将完整格式检查 ses.format 判为失败。 例如仓库的 999.ofd 样例含此非规范编码,其两层数学签名仍可通过验证。

seal, err := sign.CreateSeal(sign.SealParams{
    Provider: "example", ESID: "seal@example", Name: "示例印章",
    PictureType: "png", PictureData: picturePNG, Width: 40, Height: 40,
    SignerCertificates: [][]byte{signerCertDER},
}, makerCertDER, makerKey, signingTime)
// 检查 err 后,以 signerCertDER/signerKey 签章;制章者与签章者可以不同。
value, err := sign.CreateSignedValue(signatureXML, seal, signerCertDER, signerKey,
    "/Doc_0/Signatures/Signature_sign-1.xml", signingTime, signingOptions)

上述代码中的证书、密钥、图片与 signingOptions 由应用提供。生成签名值时 SM2 对原文使用 SM3,独立于 OFD References.CheckMethod;引用摘要支持 MD5、SHA1、SM3。 引用摘要可为 SHA1,而 SES DataHash 仍使用 SM3。

完整验证

results, err := sign.Verify(ctx, "document.ofd", sign.VerificationOptions{
    Trust: &sign.CertificateTrustOptions{Roots: roots, Intermediates: intermediates},
    Revocation: &sign.CertificateRevocationOptions{CRLs: crls, Issuers: issuers},
    SealStatus: sealStatusAtTime,
})

roots 为 smx509.CertPool,intermediates、crls、issuers 为 DER 字节列表。 SealStatus(id, at) 由应用根据可信的印章状态记录返回指定时刻的 good、revoked 或 unknown。验证器不会隐式加载系统根证书、下载 CRL 或查询印章服务。 CurrentTime 指定完整验签的验证时钟,缺省时在入口读取一次当前时间,组件与完整 策略共用此时刻。入口先检查签章时间非空且不晚于验证时刻;未来签章的完整结论为 invalid,FullyValid() 返回 false,即使其证书在声明的签章时间有效且两层签名正确。 标准结论中的证书信任和吊销判定以签章时刻为参考,同时要求 CRL 在验证时钟有效, 能使用签章之后发布的最新 CRL 判定历史吊销时间。当前支持完整、直接签发且带 有效 NextUpdate 的 CRL;增量 CRL、分区/间接 CRL 及未知关键扩展会报告证据未完成。 时间戳可通过 TimestampTrust 使用独立的信任根。

Trust.CurrentTime 可覆盖组件级证书链诊断的时间;Revocation.CurrentTime 可覆盖 组件级 CRL 诊断的证据时钟及吊销判定时间。这些覆盖影响 Trusted、RevocationStatus 等诊断字段,完整策略仍按签章时间判定历史证书状态,并按顶层 CurrentTime 检查 证据与时间顺序。VerificationTime 保留证书有效期所使用的签章时间语义。

每项结果包含文档体索引、签名 ID、引用摘要结果、两层签名结果,以及 Result.Validation.Status 和 Checks:

状态 含义
valid 必需的格式、签名、授权、印章有效期与状态、证书用途/信任/吊销、时间、原文绑定检查全部通过;存在时间戳时也已通过其验证。
invalid 至少一个检查已经确认失败。
incomplete 没有已确认失败的检查,但缺少必需证据、未支持关键扩展或尚不能解释原文保护范围。

接受签章时使用 item.Result.FullyValid():只有完整状态为 valid 才返回 true; 结果为空、未完成或失败均返回 false。应先处理 Verify 返回的错误,并按业务要求 检查签名数量(空结果表示没有签名)。

DigestValid 表示引用摘要与 SES DataHash 是否匹配,Result.Valid 表示两层签名 检查是否通过;两者均不能代替完整结论。算法声明不一致会使完整结论为 invalid, 保护范围无法确认会使其为 incomplete。已确认的引用或原文摘要错误优先报告 invalid,不会被尚未确认的范围或未支持算法遮蔽。

两层签名的错误保存在 Result.Seal.Error、Result.Outer.Error 及对应的 Validation.Checks 中。SM2 摘要计算失败会保留具体原因(如 UID 过长), 摘要计算成功但签名不匹配时报告「SM2 签名验证失败」。

PropertyInfo 目前支持指向受保护 Signature.xml 的包内路径;其他厂商元数据 编码可解析保留,但保护范围会报告未完成。时间戳支持 CMS/TSTInfo 的签名值绑定、 TSA 专用关键 EKU、ESS 签名者证书摘要绑定、信任及整条证书链吊销检查; 不带时间戳的签章是合法情况。

时间戳回归目前使用测试 CA 和独立编码的 CMS/TSTInfo 令牌;真实第三方 TSA 服务的互操作尚未验证,后续需提供服务地址、认证配置(如需)及可信证书/吊销证据进行实测。

存在时间戳时,timestamp.signing_time 还按 §7.3 h)4)引用 f)的要求,检查 签章者证书在时间戳时刻的有效期和吊销状态。签章后、时间戳前的证书吊销会使 完整结论失败;缺少该时刻可验证的吊销证据则报告未完成。

该实现不包含 SM4 文档加密或 RSA/ECDSA OFD 文档验签。标准支持范围及验证配置 应随使用场景声明;实现与回归测试不等同于外部符合性认证。

Documentation

Overview

Package sign 提供 OFD 外部命令签署、SM2/SM3 SES 生成和完整签章验证。

签名在包内容最终确定后追加:本包为每个文档体计算被引用文件的摘要并生成 Signature.xml(SignedInfo),把该文件字节通过标准输入交给外部命令,命令在 标准输出返回 SignedValue.dat 字节,本包再写回 Signatures.xml 与 OFD.xml。 签名算法与私钥由外部命令负责,本包不接触密钥。

Index

Examples

Constants

View Source
const (
	// DefaultSignatureMethod 是 SM2 + SM3 的签名算法 OID。
	DefaultSignatureMethod = "1.2.156.10197.1.501"
	// DefaultCheckMethod 是默认摘要算法。
	DefaultCheckMethod = "SM3"
)
View Source
const (
	SignatureFormatAuto = parser.SM2SignatureFormatAuto
	SignatureFormatDER  = parser.SM2SignatureFormatDER
	SignatureFormatRaw  = parser.SM2SignatureFormatRaw
)

Variables

This section is empty.

Functions

func CreateSignedValue added in v0.1.5

func CreateSignedValue(signatureXML []byte, seal *Seal, certificate []byte, key *sm2.PrivateKey, propertyInfo string, at time.Time, options SigningOptions) ([]byte, error)

CreateSignedValue 对 Signature.xml 字节生成 SM2/SM3 SES V4 签章值。 propertyInfo 指定受保护的 Signature.xml 包内路径。签署前检查证书与密钥匹配、 印章签名及授权、有效期和用途;证书信任和状态校验由 options.Validate 接入。 返回值可交给 creator.Signature.SignedValue,或由外部签名命令输出。

func Sign

func Sign(input any, w io.Writer, options Options) error

Sign 读取 input(文件路径、字节数据或 io.Reader),为每个文档体追加签名后写入 w。

Types

type CertificateDigest added in v0.1.5

type CertificateDigest = ses.CertificateDigest

type CertificateRevocationOptions added in v0.1.5

type CertificateRevocationOptions = parser.CertificateRevocationOptions

type CertificateTrustOptions added in v0.1.5

type CertificateTrustOptions = parser.CertificateTrustOptions

type Extension added in v0.1.5

type Extension = ses.Extension

type Options

type Options struct {
	// Command 是外部签名命令,按空白拆分参数,不支持引号或 shell 语法。
	// 命令从标准输入读取 Signature.xml 字节,向标准输出写 SignedValue.dat 字节。
	Command string
	// ID 是签名标识,空值时使用 "sign-1"。
	ID string
	// ProviderName、ProviderVersion、Company 写入 SignedInfo 的 Provider。
	ProviderName    string
	ProviderVersion string
	Company         string
	// SignatureMethod 是签名算法 OID,空值时使用 DefaultSignatureMethod。
	SignatureMethod string
	// CheckMethod 是摘要算法,空值时使用 DefaultCheckMethod。
	CheckMethod string
	// Date 是签名时间,零值时使用当前时间。
	Date time.Time
	// Deterministic 使用固定 ZIP 时间,生成可复现的签名结果。
	// 需要 Date 固定时配合设置,否则 Signature.xml 中的签名时间仍会变化。
	Deterministic bool
	// Stamp 控制是否在 Signature.xml 写入 StampAnnot,让阅读器把印章图片绘制到页面上。
	// 为 nil 时不写入 StampAnnot(只验签,不显示印章)。
	Stamp *StampOptions
	// StampSeam 在同一文档体的每个页面右边缘写入一条同一印章的裁片。
	// 所有标注引用同一份 Seal,按页面顺序拼回完整骑缝章。
	StampSeam *StampSeamOptions
	// Seal 指定独立的电子印章文件(DER 编码的 SES_Seal,如 .esl)。提供时
	// Signature.xml 会写入 <Seal BaseLoc="Seal.esl"/> 并把该文件一起写入包,
	// 阅读器优先从独立印章文件提取印章图。SignedValue.dat 仍内嵌印章,
	// 作为阅读器未识别 Seal 元素时的回退路径。
	Seal string
	// References 控制 SignedInfo/References 覆盖的文件集合;为 nil 时签名文档体目录下的全部文件(不含 OFD.xml)。
	References *ReferenceOptions
	// Environment 是传递给外部命令的额外环境变量,格式为 KEY=VALUE。
	Environment []string
}

Options 控制外部命令签名。

type ReferenceOptions

type ReferenceOptions struct {
	Include      []string
	Exclude      []string
	RootDocument bool
}

ReferenceOptions 选择 SignedInfo/References 覆盖的文件。 Include/Exclude 是不区分大小写的 glob(相对文档体目录,如 "Pages/**"、"*.xml"), 为空表示包含全部文件。RootDocument 为真时把 OFD.xml 纳入引用。

type SM2SignatureFormat added in v0.1.5

type SM2SignatureFormat = parser.SM2SignatureFormat

type Seal added in v0.1.5

type Seal = ses.Seal

func CreateSeal added in v0.1.5

func CreateSeal(params SealParams, certificate []byte, key *sm2.PrivateKey, at time.Time) (*Seal, error)

CreateSeal 使用调用方提供的制章者证书和私钥生成 SES V4 印章。 SignerCertificates/SignerDigests 可授权与制章者不同的签章者。

type SealParams added in v0.1.5

type SealParams = ses.SealParams

SealParams 描述显示尺寸以毫米计的 SES 电子印章,以及获授权签章者列表。

type SignatureValidation added in v0.1.5

type SignatureValidation = parser.SignatureValidation

type SignatureVerificationResult added in v0.1.5

type SignatureVerificationResult = parser.SignatureVerificationResult

type SigningOptions added in v0.1.5

type SigningOptions = ses.SigningOptions

type StampOptions

type StampOptions struct {
	// PageRef 是 StampAnnot@PageRef 引用的页面 ID;为空时使用首个文档体的首页。
	PageRef string
	// Boundary 是 StampAnnot@Boundary,格式为 "x y width height";为空时在首页右下角放置默认大小印章。
	Boundary string
}

StampOptions 描述签章在页面上的位置。

type StampSeamOptions added in v0.1.5

type StampSeamOptions struct {
	// Edge 是骑缝章贴靠的页面边缘,支持 "left"、"right"、"top" 与 "bottom";
	// 空值默认为 "right"。
	Edge string
	// Pages 选择参与分片的页码,支持 "all"、"odd"、"even";空值默认为 "all"。
	Pages string
	// Pieces 是印章拆分份数;零值按参与分片的页数计算,非零时必须与参与页数相等。
	Pieces int
	// GroupPages 是每组骑缝章覆盖的页数;零值表示所有参与页面共用一枚章。
	GroupPages int
	// Size 是印章边长,单位为毫米;零值使用默认值。
	Size float64
	// MinStrip 是每页裁片的最小宽度,单位为毫米;零值使用 2mm。
	MinStrip float64
	// X 是上下边缘时印章左边坐标,单位为毫米;负值按页面水平居中。
	X float64
	// Y 是印章底边坐标,单位为毫米;负值按页面垂直居中,零值可贴页面底部。
	Y float64
}

StampSeamOptions 描述跨整个文档体页面序列的骑缝章。 每页按自身 Area/PhysicalBox 定位,缺失时回退文档公共 PageArea。

type Verification added in v0.1.5

type Verification struct {
	DocumentIndex int    `json:"document_index"`
	ID            string `json:"id"`
	// DigestValid 仅表示引用摘要与 SES DataHash 匹配;完整结论使用 Result.FullyValid()。
	DigestValid bool                         `json:"digest_valid"`
	Result      *SignatureVerificationResult `json:"result,omitempty"`
	Error       string                       `json:"error,omitempty"`
}

Verification 是一个文档体中的一个签名的完整验证结果。

func Verify added in v0.1.5

func Verify(ctx context.Context, input any, options VerificationOptions) ([]Verification, error)

Verify 校验 OFD 的文件摘要及 GB/T 38540—2020 SES 签章。 input 接受文件路径、[]byte 或 io.Reader。缺少信任、吊销或印章状态证据时, Result.Validation.Status 为 incomplete;两层数学签名结果仍独立保留。 业务接受签章前应检查 Result.FullyValid()。

Example
package main

import (
	"context"
	"fmt"

	"github.com/zc310/ofd/pkg/sign"
)

func main() {
	results, err := sign.Verify(context.Background(), "../../testdata/ofdrw/999.ofd", sign.VerificationOptions{})
	if err != nil {
		panic(err)
	}
	fmt.Println(len(results))
	// 省略证书与状态证据时不能据两层签名结果认定完整验证通过。
	fmt.Println(results[0].Result.FullyValid())
}
Output:
1
false

type VerificationCheck added in v0.1.5

type VerificationCheck = parser.VerificationCheck

type VerificationOptions added in v0.1.5

type VerificationOptions = parser.SignatureVerificationOptions

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL