Files
ofdgo/ofdgo_sign_timestamp.go
T
2026-10-09 10:39:51 +08:00

575 lines
19 KiB
Go

// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ofdgo
import (
"bytes"
"encoding/asn1"
"fmt"
"math/big"
"time"
"github.com/emmansun/gmsm/smx509"
)
// CMS时间戳内容、签名属性及签署证书属性的对象标识
const (
signTimestampContent = "1.2.840.113549.1.9.16.1.4"
signTimestampAttr = "1.2.840.113549.1.9.16.2.14"
signContentCMS = "1.2.840.113549.1.7.2"
signAttrContentType = "1.2.840.113549.1.9.3"
signAttrESS = "1.2.840.113549.1.9.16.2.12"
signAttrESSV2 = "1.2.840.113549.1.9.16.2.47"
)
// SignatureTimestampRequest 外部时间戳证据请求
// Data为完整SignedValue文件字节, 回调返回的RFC3161令牌须绑定Data
type SignatureTimestampRequest struct {
DocIndex int
SignatureID string
Data []byte
}
// SignatureTimestampOptions 离线RFC3161时间戳验证选项
// Tokens及Fetch返回的令牌绑定完整SignedValue, 内嵌令牌绑定所属签名值
// Fetch仅提供证据, 本库不主动联网, TrustCerts须由调用方显式提供
type SignatureTimestampOptions struct {
Tokens [][]byte
TrustCerts [][]byte
Certificates [][]byte
Fetch func(SignatureTimestampRequest) ([][]byte, error)
Required bool
PolicyOID string
Nonce *big.Int
VerifyTime *time.Time
}
// SignatureTimestampReport 时间戳验证报告
// Time只表示令牌声明时间, 仅Valid为true时可作为可信时间使用
type SignatureTimestampReport struct {
tsaName asn1.RawValue
Time time.Time
PolicyOID string
SerialNumber string
Nonce *big.Int
Signer SignatureCertInfo
BindingChecked bool
BindingOK bool
SignedValueChecked bool
SignedValueOK bool
CertTrustChecked bool
CertTrustOK bool
CertTimeChecked bool
CertTimeOK bool
Valid bool
Error string
}
// signatureTimestampEvidence 保存时间戳令牌及其绑定数据
type signatureTimestampEvidence struct {
Token []byte
Data []byte
}
// WithSignatureTimestamp 设置离线时间戳验证
// 入参: options 时间戳选项
// 返回: SignatureVerifyOption 验签选项
func WithSignatureTimestamp(options SignatureTimestampOptions) SignatureVerifyOption {
options.Tokens = cloneSignatureEvidence(options.Tokens)
options.TrustCerts = appendSignatureCerts(nil, options.TrustCerts...)
options.Certificates = appendSignatureCerts(nil, options.Certificates...)
if options.Nonce != nil {
options.Nonce = new(big.Int).Set(options.Nonce)
}
if options.VerifyTime != nil {
t := *options.VerifyTime
options.VerifyTime = &t
}
return func(o *signatureVerifyOptions) { o.Timestamp = &options }
}
// VerifySignatureTimestamp 验证RFC3161令牌与原始字节的绑定
// 入参: token DER编码TimeStampToken, data 被时间戳保护的原始字节, options 验证选项
// 返回: SignatureTimestampReport 验证报告
func VerifySignatureTimestamp(token, data []byte, options SignatureTimestampOptions) (report SignatureTimestampReport) {
err := verifySignatureTimestamp(token, data, options, &report)
if err != nil {
report.Error = err.Error()
}
report.Valid = err == nil && report.BindingChecked && report.BindingOK && report.SignedValueChecked && report.SignedValueOK && report.CertTrustChecked && report.CertTrustOK && report.CertTimeChecked && report.CertTimeOK
return report
}
// cloneSignatureEvidence 深拷贝离线证据
// 入参: values 原证据列表
// 返回: [][]byte 独立证据列表
func cloneSignatureEvidence(values [][]byte) [][]byte {
out := make([][]byte, len(values))
for i := range values {
out[i] = bytes.Clone(values[i])
}
return out
}
// verifySignatureTimestamp 检查令牌绑定、签名与TSA信任
// 入参: token 令牌DER, data 原始字节, o 验证选项, report 结果
// 返回: error 验证错误
func verifySignatureTimestamp(token, data []byte, o SignatureTimestampOptions, report *SignatureTimestampReport) error {
oid, content, present, err := parseGBTContentInfoBytes(token)
if err != nil || !present || oid != signContentCMS {
return fmt.Errorf("invalid RFC3161 CMS token")
}
items, ok := asn1Children(content.Bytes)
if !ok || len(items) < 4 {
return fmt.Errorf("invalid timestamp signed data")
}
version, err := asn1Integer(items[0])
if err != nil || version != 3 {
return fmt.Errorf("unsupported timestamp CMS version")
}
oid, value, present, err := parseGBTContentInfo(items[2])
if err != nil || !present || oid != signTimestampContent {
return fmt.Errorf("invalid TSTInfo content type")
}
tst, err := asn1OctetString(value)
if err != nil {
return err
}
if err := verifyTimestampInfo(tst, data, o, report); err != nil {
return err
}
certs := appendSignatureCerts(nil, o.Certificates...)
idx := 3
if items[idx].Class == asn1.ClassContextSpecific && items[idx].Tag == 0 {
parsed, err := parseGBTCertificates(items[idx])
if err != nil {
return err
}
for _, cert := range parsed {
certs = append(certs, cert.Raw)
}
idx++
}
if idx < len(items) && items[idx].Class == asn1.ClassContextSpecific && items[idx].Tag == 1 {
idx++
}
if idx != len(items)-1 || items[idx].Tag != asn1.TagSet || items[idx].Class != asn1.ClassUniversal {
return fmt.Errorf("invalid timestamp signer set")
}
signers, err := parseGBTSignerInfos(items[idx])
if err != nil || len(signers) != 1 {
return fmt.Errorf("timestamp requires one issuer-and-serial signer")
}
signer := signers[0]
if err := signatureSignerDigest(signer.SignatureAlg, signer.DigestAlg); err != nil {
return err
}
algorithms, ok := asn1Children(items[1].Bytes)
if !ok || items[1].Tag != asn1.TagSet || len(algorithms) != 1 {
return fmt.Errorf("invalid timestamp digest set")
}
digestAlg, err := parseGBTAlgorithm(algorithms[0])
if err != nil || digestAlg != signer.DigestAlg {
return fmt.Errorf("timestamp digest set mismatch")
}
if err := signatureAlgorithmPolicy(&SignaturePolicy{RejectWeakAlgorithms: true}, signer.SignatureAlg, signer.DigestAlg); err != nil {
return err
}
attrs, err := signatureAuthenticatedAttributes(signer.AuthAttrs)
if err != nil {
return err
}
ct, err := asn1OIDString(attrs[signAttrContentType])
if err != nil || ct != signTimestampContent {
return fmt.Errorf("timestamp content-type attribute mismatch")
}
digest, err := signatureDigest(signer.DigestAlg, tst)
if err != nil || !bytes.Equal(digest, signer.AttrDigest) {
return fmt.Errorf("timestamp content digest mismatch")
}
var cert []byte
for _, raw := range certs {
c, err := parseSignatureCertificate(raw)
if err == nil && bytes.Equal(c.RawIssuer, signer.Issuer) && c.SerialNumber.Cmp(signer.Serial) == 0 {
if cert != nil && !bytes.Equal(cert, raw) {
return fmt.Errorf("ambiguous TSA certificate")
}
cert = raw
}
}
if cert == nil {
return fmt.Errorf("TSA certificate not found")
}
if err := verifyTimestampESS(attrs, cert); err != nil {
return err
}
c, err := parseSignatureCertificate(cert)
if err != nil {
return err
}
if len(report.tsaName.FullBytes) != 0 && (report.tsaName.Class != asn1.ClassContextSpecific || report.tsaName.Tag != 4 || !bytes.Equal(report.tsaName.Bytes, c.RawSubject)) {
return fmt.Errorf("unsupported or mismatched TSTInfo TSA name")
}
critical := false
for _, extension := range c.Extensions {
if extension.Id.String() == "2.5.29.37" {
critical = extension.Critical
}
}
if !critical || len(c.UnknownExtKeyUsage) != 0 || len(c.ExtKeyUsage) != 1 || c.ExtKeyUsage[0] != smx509.ExtKeyUsageTimeStamping {
return fmt.Errorf("TSA certificate requires exclusive critical timestamping usage")
}
report.Signer = signatureCertInfo(cert)
report.SignedValueChecked = true
if isSM2SignatureMethod(signer.SignatureAlg) {
pub, err := parseSM2PublicKeyFromCert(cert)
if err != nil {
return err
}
report.SignedValueOK = verifySM2Signature(pub, nil, signer.AuthAttrs, signer.Signature)
} else {
report.SignedValueOK, err = verifyPublicKeySignature(signer.SignatureAlg, signer.DigestAlg, cert, signer.AuthAttrs, signer.Signature)
if err != nil {
return err
}
}
if !report.SignedValueOK {
return fmt.Errorf("invalid timestamp signature")
}
report.CertTimeChecked = true
report.CertTimeOK = timeInRange(report.Time, c.NotBefore, c.NotAfter)
if !report.CertTimeOK {
return fmt.Errorf("TSA certificate not valid at timestamp time")
}
trusts := appendSignatureCerts(nil, o.TrustCerts...)
if len(trusts) == 0 {
return fmt.Errorf("timestamp trust certificates not provided")
}
report.CertTrustChecked = true
report.CertTrustOK = verifySignatureCertificateChain(c, certs, trusts, report.Time, smx509.ExtKeyUsageTimeStamping) == nil
if !report.CertTrustOK {
return fmt.Errorf("untrusted TSA certificate")
}
return nil
}
// verifyTimestampInfo 验证TSTInfo及可选nonce和策略
// 入参: data TSTInfo字节, bound 被保护字节, o 验证选项, report 结果
// 返回: error 验证错误
func verifyTimestampInfo(data, bound []byte, o SignatureTimestampOptions, report *SignatureTimestampReport) error {
var raw asn1.RawValue
rest, err := asn1.Unmarshal(data, &raw)
if err != nil || len(rest) != 0 || raw.Tag != asn1.TagSequence {
return fmt.Errorf("invalid TSTInfo")
}
items, ok := asn1Children(raw.Bytes)
if !ok || len(items) < 5 {
return fmt.Errorf("invalid TSTInfo fields")
}
v, err := asn1Integer(items[0])
if err != nil || v != 1 {
return fmt.Errorf("unsupported TSTInfo version")
}
report.PolicyOID, err = asn1OIDString(items[1])
if err != nil {
return err
}
if o.PolicyOID != "" && o.PolicyOID != report.PolicyOID {
return fmt.Errorf("timestamp policy mismatch")
}
imprint, ok := asn1Children(items[2].Bytes)
if !ok || len(imprint) != 2 {
return fmt.Errorf("invalid timestamp imprint")
}
method, err := parseGBTAlgorithm(imprint[0])
if err != nil {
return err
}
if err := signatureAlgorithmPolicy(&SignaturePolicy{RejectWeakAlgorithms: true}, "", method); err != nil {
return err
}
want, err := asn1OctetString(imprint[1])
if err != nil {
return err
}
got, err := signatureDigest(method, bound)
if err != nil {
return err
}
report.BindingChecked, report.BindingOK = true, bytes.Equal(want, got)
if !report.BindingOK {
return fmt.Errorf("timestamp message imprint mismatch")
}
serial, err := asn1IntegerBig(items[3])
if err != nil || serial.Sign() < 0 {
return fmt.Errorf("invalid timestamp serial")
}
report.SerialNumber = serial.String()
if items[4].Tag != asn1.TagGeneralizedTime {
return fmt.Errorf("invalid timestamp generalized time")
}
report.Time, err = asn1Time(items[4])
if err != nil {
return err
}
now := time.Now()
if o.VerifyTime != nil {
now = *o.VerifyTime
}
if report.Time.After(now) {
return fmt.Errorf("timestamp is in the future")
}
previous := -1
for _, item := range items[5:] {
order := -1
switch {
case item.Class == asn1.ClassUniversal && item.Tag == asn1.TagSequence:
order = 0
var accuracy struct {
Seconds int `asn1:"optional"`
Millis int `asn1:"optional,tag:0"`
Micros int `asn1:"optional,tag:1"`
}
rest, err := asn1.Unmarshal(item.FullBytes, &accuracy)
if err != nil || len(rest) != 0 || accuracy.Seconds < 0 || accuracy.Millis < 0 || accuracy.Millis > 999 || accuracy.Micros < 0 || accuracy.Micros > 999 {
return fmt.Errorf("invalid timestamp accuracy")
}
case item.Class == asn1.ClassUniversal && item.Tag == asn1.TagBoolean:
order = 1
var ordering bool
if _, err := asn1.Unmarshal(item.FullBytes, &ordering); err != nil {
return err
}
case item.Class == asn1.ClassUniversal && item.Tag == asn1.TagInteger:
order = 2
report.Nonce, err = asn1IntegerBig(item)
if err != nil || report.Nonce.Sign() < 0 {
return fmt.Errorf("invalid timestamp nonce")
}
case item.Class == asn1.ClassContextSpecific && item.Tag == 0:
order = 3
report.tsaName, err = asn1Explicit(item)
if err != nil {
return err
}
case item.Class == asn1.ClassContextSpecific && item.Tag == 1:
return fmt.Errorf("timestamp extensions not supported")
default:
return fmt.Errorf("invalid timestamp optional field")
}
if order <= previous {
return fmt.Errorf("duplicate or unordered timestamp field")
}
previous = order
}
if o.Nonce != nil && (report.Nonce == nil || report.Nonce.Cmp(o.Nonce) != 0) {
return fmt.Errorf("timestamp nonce mismatch")
}
return nil
}
// signatureAuthenticatedAttributes 拒绝重复属性和非DER排序
// 入参: data 签名属性DER集合
// 返回: map[string]asn1.RawValue 属性映射, error 解析错误
func signatureAuthenticatedAttributes(data []byte) (map[string]asn1.RawValue, error) {
var raw asn1.RawValue
rest, err := asn1.Unmarshal(data, &raw)
if err != nil || len(rest) != 0 || raw.Class != asn1.ClassUniversal || raw.Tag != asn1.TagSet {
return nil, fmt.Errorf("invalid signed attributes")
}
attrs, ok := asn1Children(raw.Bytes)
if !ok {
return nil, fmt.Errorf("invalid signed attributes")
}
out := make(map[string]asn1.RawValue)
var previous []byte
for _, attr := range attrs {
if bytes.Compare(previous, attr.FullBytes) > 0 {
return nil, fmt.Errorf("non-DER signed attributes")
}
previous = attr.FullBytes
items, ok := asn1Children(attr.Bytes)
if !ok || len(items) != 2 || items[1].Tag != asn1.TagSet {
return nil, fmt.Errorf("invalid signed attribute")
}
oid, err := asn1OIDString(items[0])
if err != nil {
return nil, err
}
if _, exists := out[oid]; exists {
return nil, fmt.Errorf("duplicate signed attribute: %s", oid)
}
values, ok := asn1Children(items[1].Bytes)
if !ok || len(values) != 1 {
return nil, fmt.Errorf("signed attribute must have one value")
}
out[oid] = values[0]
}
return out, nil
}
// verifyTimestampESS 验证ESS证书摘要及颁发者序列号绑定
// 入参: attrs 签名属性, cert TSA证书DER
// 返回: error 绑定错误
func verifyTimestampESS(attrs map[string]asn1.RawValue, cert []byte) error {
found := false
for _, oid := range []string{signAttrESS, signAttrESSV2} {
value, exists := attrs[oid]
if !exists {
continue
}
found = true
outer, ok := asn1Children(value.Bytes)
if !ok || len(outer) < 1 || len(outer) > 2 {
return fmt.Errorf("invalid ESS signing certificate")
}
certs, ok := asn1Children(outer[0].Bytes)
if !ok || len(certs) == 0 {
return fmt.Errorf("empty ESS certificate list")
}
fields, ok := asn1Children(certs[0].Bytes)
if !ok || len(fields) == 0 {
return fmt.Errorf("invalid ESS certificate identifier")
}
method := "SHA1"
idx := 0
if oid == signAttrESSV2 {
method = "SHA256"
if fields[0].Tag == asn1.TagSequence {
var err error
method, err = parseGBTAlgorithm(fields[0])
if err != nil {
return err
}
idx++
}
}
if len(fields) < idx+1 || len(fields) > idx+2 {
return fmt.Errorf("invalid ESS certificate fields")
}
want, err := asn1OctetString(fields[idx])
if err != nil {
return err
}
got, err := signatureDigest(method, cert)
if err != nil || !bytes.Equal(want, got) {
return fmt.Errorf("ESS certificate hash mismatch")
}
if len(fields) == idx+2 {
issuer, ok := asn1Children(fields[idx+1].Bytes)
if !ok || len(issuer) != 2 {
return fmt.Errorf("invalid ESS issuer serial")
}
serial, err := asn1IntegerBig(issuer[1])
if err != nil {
return err
}
c, err := parseSignatureCertificate(cert)
if err != nil {
return err
}
names, ok := asn1Children(issuer[0].Bytes)
matched := false
for _, name := range names {
matched = matched || name.Class == asn1.ClassContextSpecific && name.Tag == 4 && bytes.Equal(name.Bytes, c.RawIssuer)
}
if !ok || !matched || serial.Cmp(c.SerialNumber) != 0 {
return fmt.Errorf("ESS issuer serial mismatch")
}
}
}
if !found {
return fmt.Errorf("timestamp ESS certificate binding missing")
}
return nil
}
// signatureTimestampAttributes 提取签名值中的RFC3161未认证属性
// 入参: raw 未认证属性ASN.1值
// 返回: [][]byte 时间戳令牌, error 解析错误
func signatureTimestampAttributes(raw asn1.RawValue) ([][]byte, error) {
if len(raw.FullBytes) == 0 {
return nil, nil
}
attrs, ok := asn1Children(raw.Bytes)
if !ok {
return nil, fmt.Errorf("invalid timestamp attributes")
}
var tokens [][]byte
for _, attr := range attrs {
items, ok := asn1Children(attr.Bytes)
if !ok || len(items) != 2 {
return nil, fmt.Errorf("invalid unsigned attribute")
}
oid, err := asn1OIDString(items[0])
if err != nil {
return nil, err
}
if oid != signTimestampAttr {
continue
}
if len(tokens) != 0 {
return nil, fmt.Errorf("duplicate timestamp attribute")
}
values, ok := asn1Children(items[1].Bytes)
if !ok || items[1].Tag != asn1.TagSet || len(values) == 0 {
return nil, fmt.Errorf("empty timestamp attribute")
}
for _, value := range values {
tokens = append(tokens, bytes.Clone(value.FullBytes))
}
}
return tokens, nil
}
// applySignatureEvidence 验证内嵌及外部时间戳并应用吊销策略
// 入参: o 验签选项, signedValue 签名文件字节, embedded 内嵌令牌, certs 签署证书, extra 辅助证书
func (report *SignatureVerifyReport) applySignatureEvidence(o *signatureVerifyOptions, signedValue []byte, embedded []signatureTimestampEvidence, certs, extra [][]byte) {
var options SignatureTimestampOptions
if o.Timestamp != nil {
options = *o.Timestamp
}
if o.Policy != nil {
options.Required = options.Required || o.Policy.RequireTimestamp
}
evidence := append([]signatureTimestampEvidence(nil), embedded...)
for _, token := range options.Tokens {
evidence = append(evidence, signatureTimestampEvidence{Token: token, Data: signedValue})
}
if options.Fetch != nil {
tokens, err := options.Fetch(SignatureTimestampRequest{DocIndex: report.DocIndex, SignatureID: report.ID, Data: bytes.Clone(signedValue)})
if err != nil {
report.Timestamps = append(report.Timestamps, SignatureTimestampReport{Error: err.Error()})
}
for _, token := range tokens {
evidence = append(evidence, signatureTimestampEvidence{Token: token, Data: signedValue})
}
}
for _, item := range evidence {
report.Timestamps = append(report.Timestamps, VerifySignatureTimestamp(item.Token, item.Data, options))
}
if len(report.Timestamps) != 0 {
report.TimestampChecked, report.TimestampOK = true, true
for _, result := range report.Timestamps {
report.TimestampOK = report.TimestampOK && result.Valid
}
}
if options.Required && !report.TimestampChecked {
report.PolicyChecked, report.PolicyOK = true, false
report.PolicyError = "required timestamp evidence missing"
}
report.applySignatureRevocation(o, certs, extra)
}