From 98b37d865a8d5638d9131d7a27e4baf56e88aeda Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Wed, 8 Jul 2026 09:42:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=AD=BE=E5=90=8D=E9=AA=8C=E7=AD=BE):=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=AD=BE=E5=90=8D=E9=AA=8C=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ofdgo.go | 2 +- ofdgo_sign.go | 64 ++++- ofdgo_sign_gbt.go | 474 ++++++++++++++++++++++++++++++++ ofdgo_sign_ses.go | 637 +++++++++++++++++++++++++++++++++++++++++++ ofdgo_sign_sm2.go | 163 +++++++++++ ofdgo_sign_sm3.go | 211 ++++++++++++++ ofdgo_sign_verify.go | 399 +++++++++++++++++++++++++++ 7 files changed, 1936 insertions(+), 14 deletions(-) create mode 100644 ofdgo_sign_gbt.go create mode 100644 ofdgo_sign_ses.go create mode 100644 ofdgo_sign_sm2.go create mode 100644 ofdgo_sign_sm3.go create mode 100644 ofdgo_sign_verify.go diff --git a/ofdgo.go b/ofdgo.go index 1c556cb..fe84cb0 100644 --- a/ofdgo.go +++ b/ofdgo.go @@ -43,7 +43,7 @@ func Open(path string) (*Reader, error) { return reader, nil } -// NewReader 从流创建一个 OFD 阅读器 +// NewReader 从IO读取器创建OFD阅读器 // 入参: r IO读取器, size 数据大小 // 返回: *Reader 阅读器实例, error 错误信息 func NewReader(r io.ReaderAt, size int64) (*Reader, error) { diff --git a/ofdgo_sign.go b/ofdgo_sign.go index f69b3fa..08b6cb7 100644 --- a/ofdgo_sign.go +++ b/ofdgo_sign.go @@ -22,11 +22,12 @@ import ( "strings" ) +// SignType 签名类型 type SignType string const ( - SignTypeSeal = "Seal" - SignTypeSign = "Sign" + SignTypeSeal SignType = "Seal" + SignTypeSign SignType = "Sign" ) // Signatures 签名列表 @@ -47,16 +48,49 @@ type Signature struct { type SignatureFile struct { XMLName xml.Name `xml:"Signature"` SignedValue string `xml:"SignedValue"` - SignedInfo struct { - Seal struct { - BaseLoc string `xml:"BaseLoc"` - } `xml:"Seal"` - StampAnnot []struct { - ID string `xml:"ID,attr"` - PageRef string `xml:"PageRef,attr"` - Boundary string `xml:"Boundary,attr"` - } `xml:"StampAnnot"` - } `xml:"SignedInfo"` + SignedInfo SignedInfo +} + +// SignedInfo 签名信息 +type SignedInfo struct { + Provider SignatureProvider `xml:"Provider"` + SignatureMethod string `xml:"SignatureMethod"` + SignatureDateTime string `xml:"SignatureDateTime"` + Seal SignatureSeal `xml:"Seal"` + StampAnnot []SignatureStamp `xml:"StampAnnot"` + References SignatureReferences `xml:"References"` + Raw []byte `xml:"-"` +} + +// SignatureProvider 签名提供者信息 +type SignatureProvider struct { + ProviderName string `xml:"ProviderName,attr"` + Company string `xml:"Company,attr"` + Version string `xml:"Version,attr"` +} + +// SignatureSeal 签名印章引用 +type SignatureSeal struct { + BaseLoc string `xml:"BaseLoc"` +} + +// SignatureStamp 签名印章注释 +type SignatureStamp struct { + ID string `xml:"ID,attr"` + PageRef string `xml:"PageRef,attr"` + Boundary string `xml:"Boundary,attr"` +} + +// SignatureReferences 签名保护文件列表 +type SignatureReferences struct { + CheckMethod string `xml:"CheckMethod,attr"` + Reference []SignatureReference `xml:"Reference"` +} + +// SignatureReference 签名保护文件引用 +type SignatureReference struct { + FileRef string `xml:"FileRef,attr"` + CheckValue string `xml:"CheckValue"` } // parseSignatures 解析签名文件 @@ -99,7 +133,11 @@ func (r *Reader) parseSignatures(doc *Document) error { if len(sealData) == 0 && sigFile.SignedValue != "" { signedValuePath := resolveResourcePath(sigPath, "", sigFile.SignedValue) if data, err := r.ResData(signedValuePath); err == nil { - sealType, sealData = extractSeal(data) + if sig, err := parseSESSignature(data); err == nil { + sealType, sealData = sig.Seal.PicType, sig.Seal.PicData + } else { + sealType, sealData = extractSeal(data) + } } } if len(sealData) == 0 { diff --git a/ofdgo_sign_gbt.go b/ofdgo_sign_gbt.go new file mode 100644 index 0000000..074a5d9 --- /dev/null +++ b/ofdgo_sign_gbt.go @@ -0,0 +1,474 @@ +// 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" +) + +const ( + signContentData = "1.2.156.10197.6.1.4.2.1" + signContentSignedData = "1.2.156.10197.6.1.4.2.2" + signAttrMessageDigest = "1.2.840.113549.1.9.4" +) + +// digitalVerifyResult 数字签名验证结果 +type digitalVerifyResult struct { + DataHashOK bool + SignedOK bool + CertOK bool +} + +// gbtSignedData GB/T 35275 SignedData结构 +type gbtSignedData struct { + ContentDigest []byte + Certs []gbtCertificate + Signers []gbtSignerInfo +} + +// gbtCertificate SignedData证书索引信息 +type gbtCertificate struct { + Raw []byte + Issuer []byte + Serial *big.Int +} + +// gbtSignerInfo SignedData签名者信息 +type gbtSignerInfo struct { + Issuer []byte + Serial *big.Int + DigestAlg string + SignatureAlg string + Signature []byte + AuthAttrs []byte + AttrDigest []byte +} + +// verifyDigitalSignature 验证OFD数字签名 +// 入参: method 签名算法, signedValue 签名值, signedData 被签名原文, options 验证选项 +// 返回: *digitalVerifyResult 验证结果, error 错误信息 +func verifyDigitalSignature(method string, signedValue, signedData []byte, options *signatureVerifyOptions) (*digitalVerifyResult, error) { + if !isSM2SignatureMethod(method) { + return nil, fmt.Errorf("unsupported signature method") + } + if isGBT35275SignedValue(signedValue) { + return verifyGBT35275SignedData(signedValue, signedData, options) + } + return verifyRawDigitalSignature(signedValue, signedData, options) +} + +// verifyRawDigitalSignature 验证裸SM2数字签名 +// 入参: signedValue 签名值, signedData 被签名原文, options 验证选项 +// 返回: *digitalVerifyResult 验证结果, error 错误信息 +func verifyRawDigitalSignature(signedValue, signedData []byte, options *signatureVerifyOptions) (*digitalVerifyResult, error) { + if len(options.SignCerts) == 0 { + return nil, fmt.Errorf("signature certificate not found") + } + result := &digitalVerifyResult{DataHashOK: true} + for _, cert := range options.SignCerts { + pub, err := parseSM2PublicKeyFromCert(cert) + if err != nil { + continue + } + result.CertOK = true + if sm2VerifySignature(pub, nil, signedData, signedValue) { + result.SignedOK = true + return result, nil + } + } + return result, nil +} + +// verifyGBT35275SignedData 验证GB/T 35275 SignedData签名值 +// 入参: signedValue 签名值, signedData 被签名原文, options 验证选项 +// 返回: *digitalVerifyResult 验证结果, error 错误信息 +func verifyGBT35275SignedData(signedValue, signedData []byte, options *signatureVerifyOptions) (*digitalVerifyResult, error) { + sd, err := parseGBT35275SignedData(signedValue) + if err != nil { + return nil, err + } + for _, cert := range options.SignCerts { + c, err := parseGBTCertificate(cert) + if err == nil { + sd.Certs = append(sd.Certs, c) + } + } + if len(sd.Signers) == 0 { + return nil, fmt.Errorf("invalid signed data signer info") + } + result := &digitalVerifyResult{} + digest := signSM3(signedData) + if len(sd.ContentDigest) != 0 && !bytes.Equal(sd.ContentDigest, digest) { + return result, nil + } + result.DataHashOK = true + for _, signer := range sd.Signers { + if !isSM3DigestMethod(signer.DigestAlg) { + return nil, fmt.Errorf("unsupported digest method") + } + plain := sd.ContentDigest + if len(signer.AuthAttrs) != 0 { + if !bytes.Equal(signer.AttrDigest, digest) { + result.DataHashOK = false + return result, nil + } + plain = signer.AuthAttrs + } + if len(plain) == 0 { + return nil, fmt.Errorf("invalid signed data content") + } + cert := sd.findCert(signer.Issuer, signer.Serial) + if cert == nil { + return result, nil + } + if !isSM2SignatureMethod(signer.SignatureAlg) { + return nil, fmt.Errorf("unsupported signature method") + } + pub, err := parseSM2PublicKeyFromCert(cert.Raw) + if err != nil { + return result, err + } + result.CertOK = true + if !sm2VerifySignature(pub, nil, plain, signer.Signature) { + return result, nil + } + } + result.SignedOK = true + return result, nil +} + +// isGBT35275SignedValue 判断签名值是否为GB/T 35275 SignedData +// 入参: data 签名值数据 +// 返回: bool 是否为SignedData +func isGBT35275SignedValue(data []byte) bool { + var root asn1.RawValue + rest, err := asn1.Unmarshal(data, &root) + if err != nil || len(rest) != 0 || root.Tag != signASN1Sequence { + return false + } + items, ok := asn1Children(root.Bytes) + if !ok || len(items) < 2 { + return false + } + oid, err := asn1OIDString(items[0]) + return err == nil && oid == signContentSignedData +} + +// parseGBT35275SignedData 解析GB/T 35275 SignedData +// 入参: data 签名值数据 +// 返回: *gbtSignedData SignedData结构, error 错误信息 +func parseGBT35275SignedData(data []byte) (*gbtSignedData, error) { + contentType, content, ok, err := parseGBTContentInfoBytes(data) + if err != nil { + return nil, err + } + if !ok || contentType != signContentSignedData { + return nil, fmt.Errorf("invalid signed data content type") + } + items, ok := asn1Children(content.Bytes) + if !ok || len(items) < 4 { + return nil, fmt.Errorf("invalid signed data") + } + sd := &gbtSignedData{} + contentOID, content, hasContent, err := parseGBTContentInfo(items[2]) + if err != nil { + return nil, err + } + if hasContent { + if contentOID != signContentData { + return nil, fmt.Errorf("invalid signed data inner content type") + } + if content.Tag == asn1.TagOctetString { + sd.ContentDigest, err = asn1OctetString(content) + if err != nil { + return nil, err + } + } + } + for i := 3; i < len(items); i++ { + item := items[i] + if item.Class == asn1.ClassContextSpecific && item.Tag == 0 { + certs, err := parseGBTCertificates(item) + if err != nil { + return nil, err + } + sd.Certs = certs + continue + } + if item.Class == asn1.ClassContextSpecific && item.Tag == 1 { + continue + } + signers, err := parseGBTSignerInfos(item) + if err != nil { + return nil, err + } + sd.Signers = signers + } + return sd, nil +} + +// parseGBTContentInfoBytes 解析ContentInfo字节 +// 入参: data DER编码数据 +// 返回: string 内容类型, asn1.RawValue 内容, bool 是否存在内容, error 错误信息 +func parseGBTContentInfoBytes(data []byte) (string, asn1.RawValue, bool, error) { + var raw asn1.RawValue + rest, err := asn1.Unmarshal(data, &raw) + if err != nil || len(rest) != 0 { + return "", asn1.RawValue{}, false, fmt.Errorf("invalid content info") + } + return parseGBTContentInfo(raw) +} + +// parseGBTContentInfo 解析ContentInfo结构 +// 入参: raw ASN.1原始值 +// 返回: string 内容类型, asn1.RawValue 内容, bool 是否存在内容, error 错误信息 +func parseGBTContentInfo(raw asn1.RawValue) (string, asn1.RawValue, bool, error) { + items, ok := asn1Children(raw.Bytes) + if !ok || len(items) == 0 { + return "", asn1.RawValue{}, false, fmt.Errorf("invalid content info") + } + oid, err := asn1OIDString(items[0]) + if err != nil { + return "", asn1.RawValue{}, false, err + } + if len(items) == 1 { + return oid, asn1.RawValue{}, false, nil + } + content, err := asn1Explicit(items[1]) + if err != nil { + return "", asn1.RawValue{}, false, err + } + return oid, content, true, nil +} + +// parseGBTCertificates 解析SignedData证书集合 +// 入参: raw ASN.1原始值 +// 返回: []gbtCertificate 证书列表, error 错误信息 +func parseGBTCertificates(raw asn1.RawValue) ([]gbtCertificate, error) { + items, ok := asn1Children(raw.Bytes) + if !ok { + return nil, fmt.Errorf("invalid signed data certificates") + } + certs := make([]gbtCertificate, 0, len(items)) + for _, item := range items { + cert, err := parseGBTCertificate(item.FullBytes) + if err != nil { + return nil, err + } + certs = append(certs, cert) + } + return certs, nil +} + +// parseGBTCertificate 解析X.509证书索引字段 +// 入参: data DER编码证书 +// 返回: gbtCertificate 证书索引信息, error 错误信息 +func parseGBTCertificate(data []byte) (gbtCertificate, error) { + var cert struct { + TBSCertificate asn1.RawValue + SignatureAlgorithm asn1.RawValue + SignatureValue asn1.BitString + } + rest, err := asn1.Unmarshal(data, &cert) + if err != nil || len(rest) != 0 { + return gbtCertificate{}, fmt.Errorf("invalid certificate") + } + items, ok := asn1Children(cert.TBSCertificate.Bytes) + if !ok { + return gbtCertificate{}, fmt.Errorf("invalid tbs certificate") + } + idx := 0 + if len(items) > 0 && items[0].Class == asn1.ClassContextSpecific && items[0].Tag == 0 { + idx++ + } + if len(items) <= idx+2 { + return gbtCertificate{}, fmt.Errorf("invalid certificate issuer") + } + serial, err := asn1IntegerBig(items[idx]) + if err != nil { + return gbtCertificate{}, err + } + return gbtCertificate{ + Raw: append([]byte(nil), data...), + Issuer: append([]byte(nil), items[idx+2].FullBytes...), + Serial: serial, + }, nil +} + +// parseGBTSignerInfos 解析签名者信息集合 +// 入参: raw ASN.1原始值 +// 返回: []gbtSignerInfo 签名者列表, error 错误信息 +func parseGBTSignerInfos(raw asn1.RawValue) ([]gbtSignerInfo, error) { + items, ok := asn1Children(raw.Bytes) + if !ok { + return nil, fmt.Errorf("invalid signer infos") + } + signers := make([]gbtSignerInfo, 0, len(items)) + for _, item := range items { + signer, err := parseGBTSignerInfo(item) + if err != nil { + return nil, err + } + signers = append(signers, signer) + } + return signers, nil +} + +// parseGBTSignerInfo 解析签名者信息 +// 入参: raw ASN.1原始值 +// 返回: gbtSignerInfo 签名者信息, error 错误信息 +func parseGBTSignerInfo(raw asn1.RawValue) (gbtSignerInfo, error) { + items, ok := asn1Children(raw.Bytes) + if !ok || len(items) < 5 { + return gbtSignerInfo{}, fmt.Errorf("invalid signer info") + } + issuer, serial, err := parseGBTIssuerAndSerial(items[1]) + if err != nil { + return gbtSignerInfo{}, err + } + digestAlg, err := parseGBTAlgorithm(items[2]) + if err != nil { + return gbtSignerInfo{}, err + } + idx := 3 + var authAttrs []byte + var attrDigest []byte + if items[idx].Class == asn1.ClassContextSpecific && items[idx].Tag == 0 { + authAttrs = asn1SetBytes(items[idx].Bytes) + attrDigest, err = parseGBTMessageDigestAttr(items[idx]) + if err != nil { + return gbtSignerInfo{}, err + } + idx++ + } + if len(items) <= idx+1 { + return gbtSignerInfo{}, fmt.Errorf("invalid signer info") + } + signatureAlg, err := parseGBTAlgorithm(items[idx]) + if err != nil { + return gbtSignerInfo{}, err + } + signature, err := asn1OctetString(items[idx+1]) + if err != nil { + return gbtSignerInfo{}, err + } + return gbtSignerInfo{ + Issuer: issuer, + Serial: serial, + DigestAlg: digestAlg, + SignatureAlg: signatureAlg, + Signature: signature, + AuthAttrs: authAttrs, + AttrDigest: attrDigest, + }, nil +} + +// parseGBTIssuerAndSerial 解析证书颁发者和序列号 +// 入参: raw ASN.1原始值 +// 返回: []byte 颁发者DN, *big.Int 序列号, error 错误信息 +func parseGBTIssuerAndSerial(raw asn1.RawValue) ([]byte, *big.Int, error) { + items, ok := asn1Children(raw.Bytes) + if !ok || len(items) < 2 { + return nil, nil, fmt.Errorf("invalid issuer and serial") + } + serial, err := asn1IntegerBig(items[1]) + if err != nil { + return nil, nil, err + } + return append([]byte(nil), items[0].FullBytes...), serial, nil +} + +// parseGBTAlgorithm 解析算法标识 +// 入参: raw ASN.1原始值 +// 返回: string 算法OID, error 错误信息 +func parseGBTAlgorithm(raw asn1.RawValue) (string, error) { + if raw.Tag == asn1.TagOID { + return asn1OIDString(raw) + } + items, ok := asn1Children(raw.Bytes) + if !ok || len(items) == 0 { + return "", fmt.Errorf("invalid algorithm identifier") + } + return asn1OIDString(items[0]) +} + +// parseGBTMessageDigestAttr 解析认证属性中的message-digest +// 入参: raw ASN.1原始值 +// 返回: []byte 摘要值, error 错误信息 +func parseGBTMessageDigestAttr(raw asn1.RawValue) ([]byte, error) { + attrs, ok := asn1Children(raw.Bytes) + if !ok { + return nil, fmt.Errorf("invalid authenticated attributes") + } + for _, attr := range attrs { + items, ok := asn1Children(attr.Bytes) + if !ok || len(items) < 2 { + continue + } + oid, err := asn1OIDString(items[0]) + if err != nil || oid != signAttrMessageDigest { + continue + } + values, ok := asn1Children(items[1].Bytes) + if !ok || len(values) == 0 { + return nil, fmt.Errorf("invalid message digest attribute") + } + return asn1OctetString(values[0]) + } + return nil, fmt.Errorf("message digest attribute not found") +} + +// findCert 查找签名者证书 +// 入参: issuer 颁发者DN, serial 证书序列号 +// 返回: *gbtCertificate 证书信息 +func (sd *gbtSignedData) findCert(issuer []byte, serial *big.Int) *gbtCertificate { + for i := range sd.Certs { + cert := &sd.Certs[i] + if bytes.Equal(cert.Issuer, issuer) && cert.Serial.Cmp(serial) == 0 { + return cert + } + } + return nil +} + +// asn1Explicit 解析显式标签内容 +// 入参: raw ASN.1原始值 +// 返回: asn1.RawValue 标签内容, error 错误信息 +func asn1Explicit(raw asn1.RawValue) (asn1.RawValue, error) { + if raw.Class != asn1.ClassContextSpecific || raw.Tag != 0 || !raw.IsCompound { + return asn1.RawValue{}, fmt.Errorf("invalid explicit content") + } + var out asn1.RawValue + rest, err := asn1.Unmarshal(raw.Bytes, &out) + if err != nil || len(rest) != 0 { + return asn1.RawValue{}, fmt.Errorf("invalid explicit content") + } + return out, nil +} + +// asn1IntegerBig 解析ASN.1整数 +// 入参: raw ASN.1原始值 +// 返回: *big.Int 整数值, error 错误信息 +func asn1IntegerBig(raw asn1.RawValue) (*big.Int, error) { + var out *big.Int + rest, err := asn1.Unmarshal(raw.FullBytes, &out) + if err != nil || len(rest) != 0 || out == nil { + return nil, fmt.Errorf("invalid integer") + } + return out, nil +} diff --git a/ofdgo_sign_ses.go b/ofdgo_sign_ses.go new file mode 100644 index 0000000..3a39244 --- /dev/null +++ b/ofdgo_sign_ses.go @@ -0,0 +1,637 @@ +// 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" + "strings" +) + +const ( + signDigestSM3 = "1.2.156.10197.1.401" + signDigestSM3NoKey = "1.2.156.10197.1.401.1" + signDigestSM3Key = "1.2.156.10197.1.401.2" + signMethodSM2SM3 = "1.2.156.10197.1.501" + signMethodSM2SM3B = "1.2.156.10197.501" + signMethodSM2Sign = "1.2.156.10197.1.301.1" + signCurveSM2P256 = "1.2.156.10197.1.301" + signECPublicKey = "1.2.840.10045.2.1" + signASN1Sequence = 16 + signPublicKeySize = 65 +) + +// sesSignature SES签章值 +type sesSignature struct { + ToSign []byte + Cert []byte + SignAlg string + Signature []byte + DataHash []byte + Seal *sesSeal +} + +// sesSeal SES电子印章 +type sesSeal struct { + Raw []byte + SignData []byte + Cert []byte + SignAlg string + Signature []byte + PicType string + PicData []byte + CertList sesCertList +} + +// sesCertList SES印章证书列表 +type sesCertList struct { + Certs [][]byte + Digests [][]byte +} + +// sesVerifyResult SES签章验证结果 +type sesVerifyResult struct { + DataHashOK bool + SignedOK bool + SealOK bool + CertOK bool +} + +// parseSESSignature 解析SES签章值 +// 入参: data 签章值数据 +// 返回: *sesSignature SES签章值, error 错误信息 +func parseSESSignature(data []byte) (*sesSignature, error) { + var root asn1.RawValue + rest, err := asn1.Unmarshal(data, &root) + if err != nil { + return nil, err + } + if len(rest) != 0 || root.Tag != signASN1Sequence || !root.IsCompound { + return nil, fmt.Errorf("invalid ses signature") + } + items, ok := asn1Children(root.Bytes) + if !ok { + return nil, fmt.Errorf("invalid ses signature items") + } + if len(items) == 2 { + return parseSESSignatureV1(items) + } + if len(items) < 4 || len(items) > 5 { + return nil, fmt.Errorf("invalid ses signature items") + } + cert, err := asn1OctetString(items[1]) + if err != nil { + return nil, err + } + alg, err := asn1OIDString(items[2]) + if err != nil { + return nil, err + } + signature, err := asn1BitStringBytes(items[3]) + if err != nil { + return nil, err + } + tbsItems, ok := asn1Children(items[0].Bytes) + if !ok || len(tbsItems) < 5 { + return nil, fmt.Errorf("invalid ses toSign") + } + seal, err := parseSESSeal(tbsItems[1]) + if err != nil { + return nil, err + } + dataHash, err := asn1BitOrOctetBytes(tbsItems[3]) + if err != nil { + return nil, err + } + return &sesSignature{ + ToSign: append([]byte(nil), items[0].FullBytes...), + Cert: cert, + SignAlg: alg, + Signature: signature, + DataHash: dataHash, + Seal: seal, + }, nil +} + +// parseSESSignatureV1 解析SES V1签章值 +// 入参: items ASN.1子元素 +// 返回: *sesSignature SES签章值, error 错误信息 +func parseSESSignatureV1(items []asn1.RawValue) (*sesSignature, error) { + signature, err := asn1BitStringBytes(items[1]) + if err != nil { + return nil, err + } + tbsItems, ok := asn1Children(items[0].Bytes) + if !ok || len(tbsItems) != 7 { + return nil, fmt.Errorf("invalid ses v1 toSign") + } + seal, err := parseSESSeal(tbsItems[1]) + if err != nil { + return nil, err + } + dataHash, err := asn1BitOrOctetBytes(tbsItems[3]) + if err != nil { + return nil, err + } + cert, err := asn1OctetString(tbsItems[5]) + if err != nil { + return nil, err + } + alg, err := asn1OIDString(tbsItems[6]) + if err != nil { + return nil, err + } + return &sesSignature{ + ToSign: append([]byte(nil), items[0].FullBytes...), + Cert: cert, + SignAlg: alg, + Signature: signature, + DataHash: dataHash, + Seal: seal, + }, nil +} + +// verifySESSignature 验证SES签章值 +// 入参: data 签章值数据, signedData 被签名数据原文 +// 返回: *sesVerifyResult 验证结果, error 错误信息 +func verifySESSignature(data, signedData []byte) (*sesVerifyResult, error) { + sig, err := parseSESSignature(data) + if err != nil { + return nil, err + } + if !isSM2SignatureMethod(sig.SignAlg) || !isSM2SignatureMethod(sig.Seal.SignAlg) { + return nil, fmt.Errorf("unsupported signature method") + } + result := &sesVerifyResult{} + result.DataHashOK = bytes.Equal(sig.DataHash, signSM3(signedData)) + signPub, err := parseSM2PublicKeyFromCert(sig.Cert) + if err != nil { + return result, err + } + sealPub, err := parseSM2PublicKeyFromCert(sig.Seal.Cert) + if err != nil { + return result, err + } + result.SignedOK = sm2VerifySignature(signPub, nil, sig.ToSign, sig.Signature) + result.SealOK = sm2VerifySignature(sealPub, nil, sig.Seal.SignData, sig.Seal.Signature) + result.CertOK = sesCertInList(sig.Cert, sig.Seal.CertList) + return result, nil +} + +// parseSESSeal 解析SES电子印章 +// 入参: raw ASN.1原始值 +// 返回: *sesSeal SES电子印章, error 错误信息 +func parseSESSeal(raw asn1.RawValue) (*sesSeal, error) { + items, ok := asn1Children(raw.Bytes) + if !ok { + return nil, fmt.Errorf("invalid ses seal") + } + if len(items) == 2 { + return parseSESSealV1(raw, items) + } + if len(items) < 4 || len(items) > 5 { + return nil, fmt.Errorf("invalid ses seal") + } + cert, err := asn1OctetString(items[1]) + if err != nil { + return nil, err + } + alg, err := asn1OIDString(items[2]) + if err != nil { + return nil, err + } + signature, err := asn1BitStringBytes(items[3]) + if err != nil { + return nil, err + } + infoItems, ok := asn1Children(items[0].Bytes) + if !ok || len(infoItems) < 4 || len(infoItems) > 5 { + return nil, fmt.Errorf("invalid ses seal info") + } + version, err := parseSESHeaderVersion(infoItems[0]) + if err != nil { + return nil, err + } + certList, err := parseSESCertList(infoItems[2], version) + if err != nil { + return nil, err + } + picType, picData, err := parseSESPicture(infoItems[3]) + if err != nil { + return nil, err + } + return &sesSeal{ + Raw: append([]byte(nil), raw.FullBytes...), + SignData: append([]byte(nil), items[0].FullBytes...), + Cert: cert, + SignAlg: alg, + Signature: signature, + PicType: picType, + PicData: picData, + CertList: certList, + }, nil +} + +// parseSESSealV1 解析SES V1电子印章 +// 入参: raw ASN.1原始值, items ASN.1子元素 +// 返回: *sesSeal SES电子印章, error 错误信息 +func parseSESSealV1(raw asn1.RawValue, items []asn1.RawValue) (*sesSeal, error) { + infoItems, ok := asn1Children(items[0].Bytes) + if !ok || len(infoItems) < 4 || len(infoItems) > 5 { + return nil, fmt.Errorf("invalid ses v1 seal info") + } + signItems, ok := asn1Children(items[1].Bytes) + if !ok || len(signItems) != 3 { + return nil, fmt.Errorf("invalid ses v1 sign info") + } + cert, err := asn1OctetString(signItems[0]) + if err != nil { + return nil, err + } + alg, err := asn1OIDString(signItems[1]) + if err != nil { + return nil, err + } + signature, err := asn1BitStringBytes(signItems[2]) + if err != nil { + return nil, err + } + certList, err := parseSESCertListV1(infoItems[2]) + if err != nil { + return nil, err + } + picType, picData, err := parseSESPicture(infoItems[3]) + if err != nil { + return nil, err + } + signData := asn1SequenceBytes(items[0].FullBytes, signItems[0].FullBytes, signItems[1].FullBytes) + return &sesSeal{ + Raw: append([]byte(nil), raw.FullBytes...), + SignData: signData, + Cert: cert, + SignAlg: alg, + Signature: signature, + PicType: picType, + PicData: picData, + CertList: certList, + }, nil +} + +// parseSESHeaderVersion 解析印章头版本 +// 入参: raw 印章头信息 +// 返回: int 版本号, error 错误信息 +func parseSESHeaderVersion(raw asn1.RawValue) (int, error) { + items, ok := asn1Children(raw.Bytes) + if !ok || len(items) < 2 { + return 0, fmt.Errorf("invalid ses header") + } + return asn1Integer(items[1]) +} + +// parseSESCertList 解析印章证书列表 +// 入参: raw 印章属性信息 +// 返回: sesCertList 证书列表, error 错误信息 +func parseSESCertList(raw asn1.RawValue, version int) (sesCertList, error) { + items, ok := asn1Children(raw.Bytes) + if !ok || len(items) < 3 { + return sesCertList{}, fmt.Errorf("invalid ses property") + } + if version < 4 { + return parseSESCertInfoList(items[2]) + } + if len(items) < 4 { + return sesCertList{}, fmt.Errorf("invalid ses cert list") + } + listType, err := asn1Integer(items[2]) + if err != nil { + return sesCertList{}, err + } + switch listType { + case 1: + return parseSESCertInfoList(items[3]) + case 2: + return parseSESCertDigestList(items[3]) + default: + return sesCertList{}, fmt.Errorf("unsupported ses cert list type") + } +} + +// parseSESCertListV1 解析SES V1证书列表 +// 入参: raw 证书列表ASN.1值 +// 返回: sesCertList 证书列表, error 错误信息 +func parseSESCertListV1(raw asn1.RawValue) (sesCertList, error) { + certs, ok := asn1Children(raw.Bytes) + if !ok || len(certs) == 0 { + return sesCertList{}, fmt.Errorf("invalid ses cert list") + } + list := sesCertList{Certs: make([][]byte, 0, len(certs))} + for _, item := range certs { + list.Certs = append(list.Certs, append([]byte(nil), item.FullBytes...)) + } + return list, nil +} + +// parseSESCertInfoList 解析SES证书信息列表 +// 入参: raw 证书信息列表ASN.1值 +// 返回: sesCertList 证书列表, error 错误信息 +func parseSESCertInfoList(raw asn1.RawValue) (sesCertList, error) { + certs, ok := asn1Children(raw.Bytes) + if !ok || len(certs) == 0 { + return sesCertList{}, fmt.Errorf("invalid ses cert list") + } + list := sesCertList{Certs: make([][]byte, 0, len(certs))} + for _, item := range certs { + cert, err := asn1OctetString(item) + if err != nil { + return sesCertList{}, err + } + list.Certs = append(list.Certs, cert) + } + return list, nil +} + +// parseSESCertDigestList 解析SES证书摘要列表 +// 入参: raw 证书摘要列表ASN.1值 +// 返回: sesCertList 证书列表, error 错误信息 +func parseSESCertDigestList(raw asn1.RawValue) (sesCertList, error) { + items, ok := asn1Children(raw.Bytes) + if !ok || len(items) == 0 { + return sesCertList{}, fmt.Errorf("invalid ses cert digest list") + } + list := sesCertList{Digests: make([][]byte, 0, len(items))} + for _, item := range items { + fields, ok := asn1Children(item.Bytes) + if !ok || len(fields) < 2 { + return sesCertList{}, fmt.Errorf("invalid ses cert digest") + } + digest, err := asn1OctetString(fields[1]) + if err != nil { + return sesCertList{}, err + } + list.Digests = append(list.Digests, digest) + } + return list, nil +} + +// parseSESPicture 解析印章图片 +// 入参: raw 印章图片信息 +// 返回: string 图片类型, []byte 图片数据, error 错误信息 +func parseSESPicture(raw asn1.RawValue) (string, []byte, error) { + items, ok := asn1Children(raw.Bytes) + if !ok || len(items) < 4 { + return "", nil, fmt.Errorf("invalid ses picture") + } + picType := normalizeSealType(strings.ToLower(strings.TrimSpace(asn1String(items[0])))) + picData, err := asn1OctetString(items[1]) + if err != nil { + return "", nil, err + } + if picType == "ofd" { + if data := trimOFDPackage(picData); len(data) > 0 { + picData = data + } + } + return picType, picData, nil +} + +// parseSM2PublicKeyFromCert 从证书解析SM2公钥 +// 入参: data DER编码证书 +// 返回: sm2PublicKey SM2公钥, error 错误信息 +func parseSM2PublicKeyFromCert(data []byte) (sm2PublicKey, error) { + var cert struct { + TBSCertificate asn1.RawValue + SignatureAlgorithm asn1.RawValue + SignatureValue asn1.BitString + } + rest, err := asn1.Unmarshal(data, &cert) + if err != nil { + return sm2PublicKey{}, err + } + if len(rest) != 0 { + return sm2PublicKey{}, fmt.Errorf("invalid certificate") + } + items, ok := asn1Children(cert.TBSCertificate.Bytes) + if !ok { + return sm2PublicKey{}, fmt.Errorf("invalid tbs certificate") + } + idx := 0 + if len(items) > 0 && items[0].Class == asn1.ClassContextSpecific && items[0].Tag == 0 { + idx++ + } + if len(items) <= idx+5 { + return sm2PublicKey{}, fmt.Errorf("invalid public key info") + } + return parseSM2PublicKeyInfo(items[idx+5]) +} + +// parseSM2PublicKeyInfo 解析SM2公钥信息 +// 入参: raw SubjectPublicKeyInfo原始值 +// 返回: sm2PublicKey SM2公钥, error 错误信息 +func parseSM2PublicKeyInfo(raw asn1.RawValue) (sm2PublicKey, error) { + var spki struct { + Algorithm asn1.RawValue + SubjectPublicKey asn1.BitString + } + rest, err := asn1.Unmarshal(raw.FullBytes, &spki) + if err != nil { + return sm2PublicKey{}, err + } + if len(rest) != 0 { + return sm2PublicKey{}, fmt.Errorf("invalid subject public key info") + } + algItems, ok := asn1Children(spki.Algorithm.Bytes) + if !ok || len(algItems) < 2 { + return sm2PublicKey{}, fmt.Errorf("invalid public key algorithm") + } + alg, err := asn1OIDString(algItems[0]) + if err != nil { + return sm2PublicKey{}, err + } + curve, err := asn1OIDString(algItems[1]) + if err != nil { + return sm2PublicKey{}, err + } + if alg != signECPublicKey || curve != signCurveSM2P256 { + return sm2PublicKey{}, fmt.Errorf("unsupported public key algorithm") + } + key := spki.SubjectPublicKey.Bytes + if len(key) != signPublicKeySize || key[0] != 4 { + return sm2PublicKey{}, fmt.Errorf("invalid sm2 public key") + } + return sm2PublicKey{ + X: new(big.Int).SetBytes(key[1:33]), + Y: new(big.Int).SetBytes(key[33:65]), + }, nil +} + +// signSM3 计算SM3摘要 +// 入参: data 原文数据 +// 返回: []byte 摘要值 +func signSM3(data []byte) []byte { + h := newSM3() + h.Write(data) + return h.Sum(nil) +} + +// isSM2SignatureMethod 判断是否为SM2签名算法 +// 入参: method 算法标识 +// 返回: bool 是否为SM2签名算法 +func isSM2SignatureMethod(method string) bool { + method = strings.TrimSpace(method) + return method == signMethodSM2SM3 || method == signMethodSM2SM3B || method == signMethodSM2Sign +} + +// isSM3DigestMethod 判断是否为SM3摘要算法 +// 入参: method 算法标识 +// 返回: bool 是否为SM3摘要算法 +func isSM3DigestMethod(method string) bool { + method = strings.TrimSpace(method) + return method == signDigestSM3 || method == signDigestSM3NoKey || method == signDigestSM3Key || method == "SM3" +} + +// sesCertInList 判断证书是否在印章证书列表中 +// 入参: cert DER编码证书, list 证书列表 +// 返回: bool 是否存在 +func sesCertInList(cert []byte, list sesCertList) bool { + for _, item := range list.Certs { + if bytes.Equal(cert, item) { + return true + } + } + digest := signSM3(cert) + for _, item := range list.Digests { + if bytes.Equal(digest, item) { + return true + } + } + return false +} + +// asn1OctetString 解析ASN.1八位字符串 +// 入参: raw ASN.1原始值 +// 返回: []byte 字节数据, error 错误信息 +func asn1OctetString(raw asn1.RawValue) ([]byte, error) { + var out []byte + rest, err := asn1.Unmarshal(raw.FullBytes, &out) + if err != nil || len(rest) != 0 { + return nil, fmt.Errorf("invalid octet string") + } + return append([]byte(nil), out...), nil +} + +// asn1BitStringBytes 解析ASN.1位字符串 +// 入参: raw ASN.1原始值 +// 返回: []byte 字节数据, error 错误信息 +func asn1BitStringBytes(raw asn1.RawValue) ([]byte, error) { + var bits asn1.BitString + rest, err := asn1.Unmarshal(raw.FullBytes, &bits) + if err != nil || len(rest) != 0 || bits.BitLength%8 != 0 { + return nil, fmt.Errorf("invalid bit string") + } + return append([]byte(nil), bits.Bytes...), nil +} + +// asn1BitOrOctetBytes 解析ASN.1位字符串或八位字符串 +// 入参: raw ASN.1原始值 +// 返回: []byte 字节数据, error 错误信息 +func asn1BitOrOctetBytes(raw asn1.RawValue) ([]byte, error) { + if raw.Tag == asn1.TagOctetString { + return asn1OctetString(raw) + } + return asn1BitStringBytes(raw) +} + +// asn1OIDString 解析ASN.1对象标识符 +// 入参: raw ASN.1原始值 +// 返回: string OID字符串, error 错误信息 +func asn1OIDString(raw asn1.RawValue) (string, error) { + var oid asn1.ObjectIdentifier + rest, err := asn1.Unmarshal(raw.FullBytes, &oid) + if err != nil || len(rest) != 0 { + return "", fmt.Errorf("invalid oid") + } + return oid.String(), nil +} + +// asn1Integer 解析ASN.1整数 +// 入参: raw ASN.1原始值 +// 返回: int 整数值, error 错误信息 +func asn1Integer(raw asn1.RawValue) (int, error) { + var out int + rest, err := asn1.Unmarshal(raw.FullBytes, &out) + if err != nil || len(rest) != 0 { + return 0, fmt.Errorf("invalid integer") + } + return out, nil +} + +// asn1String 解析ASN.1字符串 +// 入参: raw ASN.1原始值 +// 返回: string 字符串 +func asn1String(raw asn1.RawValue) string { + var s string + if _, err := asn1.Unmarshal(raw.FullBytes, &s); err != nil { + return strings.TrimSpace(string(raw.Bytes)) + } + return s +} + +// asn1SequenceBytes 编码ASN.1序列 +// 入参: parts 序列元素DER数据 +// 返回: []byte ASN.1序列DER数据 +func asn1SequenceBytes(parts ...[]byte) []byte { + var content []byte + for _, part := range parts { + content = append(content, part...) + } + return asn1Wrap(0x30, content) +} + +// asn1SetBytes 编码ASN.1集合 +// 入参: content 集合内容DER数据 +// 返回: []byte ASN.1集合DER数据 +func asn1SetBytes(content []byte) []byte { + return asn1Wrap(0x31, content) +} + +// asn1Wrap 包装ASN.1标签 +// 入参: tag ASN.1标签, content 内容DER数据 +// 返回: []byte ASN.1 DER数据 +func asn1Wrap(tag byte, content []byte) []byte { + out := []byte{tag} + out = append(out, asn1LengthBytes(len(content))...) + out = append(out, content...) + return out +} + +// asn1LengthBytes 编码ASN.1长度 +// 入参: n 内容长度 +// 返回: []byte ASN.1长度编码 +func asn1LengthBytes(n int) []byte { + if n < 128 { + return []byte{byte(n)} + } + var buf [8]byte + i := len(buf) + for n > 0 { + i-- + buf[i] = byte(n) + n >>= 8 + } + out := []byte{0x80 | byte(len(buf)-i)} + return append(out, buf[i:]...) +} diff --git a/ofdgo_sign_sm2.go b/ofdgo_sign_sm2.go new file mode 100644 index 0000000..3b2df15 --- /dev/null +++ b/ofdgo_sign_sm2.go @@ -0,0 +1,163 @@ +// 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 ( + "crypto/elliptic" + "encoding/asn1" + "encoding/binary" + "math/big" +) + +const sm2DefaultUserID = "1234567812345678" + +var sm2P256 = newSM2P256() + +// sm2PublicKey SM2公钥 +type sm2PublicKey struct { + X *big.Int + Y *big.Int +} + +// newSM2P256 创建SM2椭圆曲线 +// 返回: elliptic.Curve SM2椭圆曲线 +func newSM2P256() elliptic.Curve { + c := &elliptic.CurveParams{Name: "SM2-P-256"} + c.P = sm2Big("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF") + c.N = sm2Big("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123") + c.B = sm2Big("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93") + c.Gx = sm2Big("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7") + c.Gy = sm2Big("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0") + c.BitSize = 256 + return c +} + +// sm2Big 解析SM2大整数常量 +// 入参: s 十六进制字符串 +// 返回: *big.Int 大整数 +func sm2Big(s string) *big.Int { + n, _ := new(big.Int).SetString(s, 16) + return n +} + +// sm2VerifySignature 验证SM2签名值 +// 入参: pub 公钥, userID 用户标识, msg 原文, sig 签名值 +// 返回: bool 是否验证通过 +func sm2VerifySignature(pub sm2PublicKey, userID, msg, sig []byte) bool { + r, s, ok := parseSM2Signature(sig) + if !ok { + return false + } + return sm2Verify(pub, userID, msg, r, s) +} + +// parseSM2Signature 解析SM2签名值 +// 入参: sig 签名值 +// 返回: *big.Int R值, *big.Int S值, bool 是否解析成功 +func parseSM2Signature(sig []byte) (*big.Int, *big.Int, bool) { + if len(sig) == 64 { + return new(big.Int).SetBytes(sig[:32]), new(big.Int).SetBytes(sig[32:]), true + } + var rs struct { + R *big.Int + S *big.Int + } + rest, err := asn1.Unmarshal(sig, &rs) + if err != nil || len(rest) != 0 || rs.R == nil || rs.S == nil { + return nil, nil, false + } + return rs.R, rs.S, true +} + +// sm2Verify 验证SM2签名 +// 入参: pub 公钥, userID 用户标识, msg 原文, r R值, s S值 +// 返回: bool 是否验证通过 +func sm2Verify(pub sm2PublicKey, userID, msg []byte, r, s *big.Int) bool { + n := sm2P256.Params().N + if r.Sign() <= 0 || s.Sign() <= 0 || r.Cmp(n) >= 0 || s.Cmp(n) >= 0 { + return false + } + if pub.X == nil || pub.Y == nil || !sm2P256.IsOnCurve(pub.X, pub.Y) { + return false + } + e := new(big.Int).SetBytes(sm2MessageDigest(pub, userID, msg)) + t := new(big.Int).Add(r, s) + t.Mod(t, n) + if t.Sign() == 0 { + return false + } + x1, y1 := sm2P256.ScalarBaseMult(s.Bytes()) + x2, y2 := sm2P256.ScalarMult(pub.X, pub.Y, t.Bytes()) + x, _ := sm2P256.Add(x1, y1, x2, y2) + if x == nil { + return false + } + v := new(big.Int).Add(e, x) + v.Mod(v, n) + return v.Cmp(r) == 0 +} + +// sm2MessageDigest 计算SM2签名摘要 +// 入参: pub 公钥, userID 用户标识, msg 原文 +// 返回: []byte 摘要值 +func sm2MessageDigest(pub sm2PublicKey, userID, msg []byte) []byte { + h := newSM3() + h.Write(sm2ZA(pub, userID)) + h.Write(msg) + return h.Sum(nil) +} + +// sm2ZA 计算SM2用户标识杂凑值 +// 入参: pub 公钥, userID 用户标识 +// 返回: []byte ZA值 +func sm2ZA(pub sm2PublicKey, userID []byte) []byte { + if len(userID) == 0 { + userID = []byte(sm2DefaultUserID) + } + h := newSM3() + var entl [2]byte + binary.BigEndian.PutUint16(entl[:], uint16(len(userID)*8)) + h.Write(entl[:]) + h.Write(userID) + h.Write(sm2Fixed(sm2A())) + h.Write(sm2Fixed(sm2P256.Params().B)) + h.Write(sm2Fixed(sm2P256.Params().Gx)) + h.Write(sm2Fixed(sm2P256.Params().Gy)) + h.Write(sm2Fixed(pub.X)) + h.Write(sm2Fixed(pub.Y)) + return h.Sum(nil) +} + +// sm2A 获取SM2曲线A参数 +// 返回: *big.Int 曲线A参数 +func sm2A() *big.Int { + return new(big.Int).Sub(sm2P256.Params().P, big.NewInt(3)) +} + +// sm2Fixed 转换为SM2固定长度字节 +// 入参: n 大整数 +// 返回: []byte 固定长度字节 +func sm2Fixed(n *big.Int) []byte { + out := make([]byte, 32) + if n == nil { + return out + } + b := n.Bytes() + if len(b) > len(out) { + b = b[len(b)-len(out):] + } + copy(out[len(out)-len(b):], b) + return out +} diff --git a/ofdgo_sign_sm3.go b/ofdgo_sign_sm3.go new file mode 100644 index 0000000..c319fcc --- /dev/null +++ b/ofdgo_sign_sm3.go @@ -0,0 +1,211 @@ +// 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 ( + "encoding/binary" + "hash" + "math/bits" +) + +const ( + sm3Size = 32 + sm3BlockSize = 64 +) + +var sm3Init = [8]uint32{ + 0x7380166f, + 0x4914b2b9, + 0x172442d7, + 0xda8a0600, + 0xa96f30bc, + 0x163138aa, + 0xe38dee4d, + 0xb0fb0e4e, +} + +// sm3Digest SM3杂凑值计算器 +type sm3Digest struct { + h [8]uint32 + x [sm3BlockSize]byte + nx int + len uint64 +} + +// newSM3 创建SM3杂凑值计算器 +// 返回: hash.Hash SM3杂凑值计算器 +func newSM3() hash.Hash { + d := new(sm3Digest) + d.Reset() + return d +} + +// Reset 重置SM3状态 +func (d *sm3Digest) Reset() { + d.h = sm3Init + d.nx = 0 + d.len = 0 +} + +// Size 获取SM3杂凑值长度 +// 返回: int 杂凑值长度 +func (d *sm3Digest) Size() int { + return sm3Size +} + +// BlockSize 获取SM3分组长度 +// 返回: int 分组长度 +func (d *sm3Digest) BlockSize() int { + return sm3BlockSize +} + +// Write 写入待计算数据 +// 入参: p 待计算数据 +// 返回: int 写入长度, error 错误信息 +func (d *sm3Digest) Write(p []byte) (int, error) { + nn := len(p) + d.len += uint64(nn) + if d.nx > 0 { + n := copy(d.x[d.nx:], p) + d.nx += n + if d.nx == sm3BlockSize { + sm3Block(d, d.x[:]) + d.nx = 0 + } + p = p[n:] + } + if len(p) >= sm3BlockSize { + n := len(p) &^ (sm3BlockSize - 1) + sm3Block(d, p[:n]) + p = p[n:] + } + if len(p) > 0 { + d.nx = copy(d.x[:], p) + } + return nn, nil +} + +// Sum 返回SM3杂凑值 +// 入参: in 前缀数据 +// 返回: []byte 杂凑值 +func (d *sm3Digest) Sum(in []byte) []byte { + dd := *d + hash := dd.checkSum() + return append(in, hash[:]...) +} + +// checkSum 计算SM3最终杂凑值 +// 返回: [sm3Size]byte 杂凑值 +func (d *sm3Digest) checkSum() [sm3Size]byte { + lenBits := d.len << 3 + var tmp [64]byte + tmp[0] = 0x80 + if d.nx < 56 { + d.Write(tmp[:56-d.nx]) + } else { + d.Write(tmp[:64+56-d.nx]) + } + binary.BigEndian.PutUint64(tmp[:8], lenBits) + d.Write(tmp[:8]) + var digest [sm3Size]byte + for i, v := range d.h { + binary.BigEndian.PutUint32(digest[i*4:], v) + } + return digest +} + +// sm3Block 处理SM3消息分组 +// 入参: d SM3杂凑值计算器, p 消息分组数据 +func sm3Block(d *sm3Digest, p []byte) { + var w [68]uint32 + var w1 [64]uint32 + for len(p) >= sm3BlockSize { + for i := 0; i < 16; i++ { + w[i] = binary.BigEndian.Uint32(p[i*4:]) + } + for i := 16; i < 68; i++ { + x := w[i-16] ^ w[i-9] ^ bits.RotateLeft32(w[i-3], 15) + w[i] = sm3P1(x) ^ bits.RotateLeft32(w[i-13], 7) ^ w[i-6] + } + for i := 0; i < 64; i++ { + w1[i] = w[i] ^ w[i+4] + } + a, b, c, e := d.h[0], d.h[1], d.h[2], d.h[4] + dd, f, g, hh := d.h[3], d.h[5], d.h[6], d.h[7] + for i := 0; i < 64; i++ { + t := uint32(0x7a879d8a) + if i < 16 { + t = 0x79cc4519 + } + ss1 := bits.RotateLeft32(bits.RotateLeft32(a, 12)+e+bits.RotateLeft32(t, i), 7) + ss2 := ss1 ^ bits.RotateLeft32(a, 12) + tt1 := sm3FF(i, a, b, c) + dd + ss2 + w1[i] + tt2 := sm3GG(i, e, f, g) + hh + ss1 + w[i] + dd = c + c = bits.RotateLeft32(b, 9) + b = a + a = tt1 + hh = g + g = bits.RotateLeft32(f, 19) + f = e + e = sm3P0(tt2) + } + d.h[0] ^= a + d.h[1] ^= b + d.h[2] ^= c + d.h[3] ^= dd + d.h[4] ^= e + d.h[5] ^= f + d.h[6] ^= g + d.h[7] ^= hh + p = p[sm3BlockSize:] + } +} + +// sm3P0 计算SM3置换函数P0 +// 入参: x 输入值 +// 返回: uint32 置换结果 +func sm3P0(x uint32) uint32 { + return x ^ bits.RotateLeft32(x, 9) ^ bits.RotateLeft32(x, 17) +} + +// sm3P1 计算SM3置换函数P1 +// 入参: x 输入值 +// 返回: uint32 置换结果 +func sm3P1(x uint32) uint32 { + return x ^ bits.RotateLeft32(x, 15) ^ bits.RotateLeft32(x, 23) +} + +// sm3FF 计算SM3布尔函数FF +// 入参: i 轮数, x 输入值, y 输入值, z 输入值 +// 返回: uint32 计算结果 +func sm3FF(i int, x, y, z uint32) uint32 { + if i < 16 { + return x ^ y ^ z + } + return (x & y) | (x & z) | (y & z) +} + +// sm3GG 计算SM3布尔函数GG +// 入参: i 轮数, x 输入值, y 输入值, z 输入值 +// 返回: uint32 计算结果 +func sm3GG(i int, x, y, z uint32) uint32 { + if i < 16 { + return x ^ y ^ z + } + return (x & y) | (^x & z) +} + +var _ hash.Hash = (*sm3Digest)(nil) diff --git a/ofdgo_sign_verify.go b/ofdgo_sign_verify.go new file mode 100644 index 0000000..63ec79e --- /dev/null +++ b/ofdgo_sign_verify.go @@ -0,0 +1,399 @@ +// 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" + "crypto/subtle" + "encoding/base64" + "encoding/pem" + "encoding/xml" + "fmt" + "io" + "path" + "strings" +) + +// SignatureVerifyReport 签名验证报告 +type SignatureVerifyReport struct { + ID string + BaseLoc string + Type SignType + Provider SignatureProvider + SignatureMethod string + SignatureDateTime string + DigestMethod string + References []SignatureReferenceVerify + DigestOK bool + DataHashOK bool + SignedValueOK bool + SealOK bool + SealMatchOK bool + CertOK bool + Valid bool + Error string +} + +// SignatureReferenceVerify 签名保护文件验证结果 +type SignatureReferenceVerify struct { + FileRef string + Path string + CheckValue []byte + Actual []byte + OK bool + Error string +} + +// signatureVerifyOptions 签名验证选项 +type signatureVerifyOptions struct { + SignCerts [][]byte +} + +// SignatureVerifyOption 签名验证选项函数 +type SignatureVerifyOption func(*signatureVerifyOptions) + +// WithSignatureCert 添加数字签名验证证书 +// 入参: cert DER或PEM编码证书 +// 返回: SignatureVerifyOption 签名验证选项 +func WithSignatureCert(cert []byte) SignatureVerifyOption { + return func(o *signatureVerifyOptions) { + o.SignCerts = append(o.SignCerts, parseSignatureCerts(cert)...) + } +} + +// WithSignatureCerts 添加多张数字签名验证证书 +// 入参: certs DER或PEM编码证书列表 +// 返回: SignatureVerifyOption 签名验证选项 +func WithSignatureCerts(certs ...[]byte) SignatureVerifyOption { + return func(o *signatureVerifyOptions) { + for _, cert := range certs { + o.SignCerts = append(o.SignCerts, parseSignatureCerts(cert)...) + } + } +} + +// VerifySignaturesBytes 验证OFD字节数据签名 +// 入参: data OFD字节数据, opts 签名验证选项 +// 返回: []SignatureVerifyReport 签名验证报告, error 错误信息 +func VerifySignaturesBytes(data []byte, opts ...SignatureVerifyOption) ([]SignatureVerifyReport, error) { + return VerifySignaturesReader(bytes.NewReader(data), int64(len(data)), opts...) +} + +// VerifySignaturesStream 验证OFD顺序流签名 +// 入参: r IO顺序读取器, opts 签名验证选项 +// 返回: []SignatureVerifyReport 签名验证报告, error 错误信息 +func VerifySignaturesStream(r io.Reader, opts ...SignatureVerifyOption) ([]SignatureVerifyReport, error) { + data, err := io.ReadAll(r) + if err != nil { + return nil, err + } + return VerifySignaturesBytes(data, opts...) +} + +// VerifySignaturesReader 验证OFD读取器签名 +// 入参: r IO读取器, size 数据大小, opts 签名验证选项 +// 返回: []SignatureVerifyReport 签名验证报告, error 错误信息 +func VerifySignaturesReader(r io.ReaderAt, size int64, opts ...SignatureVerifyOption) ([]SignatureVerifyReport, error) { + reader, err := NewReader(r, size) + if err != nil { + return nil, err + } + return reader.VerifySignatures(opts...) +} + +// VerifySignatures 验证文档签名 +// 入参: opts 签名验证选项 +// 返回: []SignatureVerifyReport 签名验证报告, error 错误信息 +func (r *Reader) VerifySignatures(opts ...SignatureVerifyOption) ([]SignatureVerifyReport, error) { + options := signatureVerifyOptions{} + for _, opt := range opts { + opt(&options) + } + doc, err := r.Doc() + if err != nil { + return nil, err + } + if doc.Signatures == "" { + return nil, nil + } + sigListPath := r.ResPath(doc.Signatures) + data, err := r.readFileExact(sigListPath) + if err != nil { + return nil, err + } + var signatures Signatures + if err := xml.Unmarshal(data, &signatures); err != nil { + return nil, err + } + reports := make([]SignatureVerifyReport, 0, len(signatures.List)) + for _, sigRef := range signatures.List { + reports = append(reports, r.verifySignature(sigListPath, sigRef, &options)) + } + return reports, nil +} + +// verifySignature 验证单个签名 +// 入参: sigListPath 签名列表路径, sigRef 签名引用, options 验证选项 +// 返回: SignatureVerifyReport 签名验证报告 +func (r *Reader) verifySignature(sigListPath string, sigRef Signature, options *signatureVerifyOptions) SignatureVerifyReport { + sigPath := signatureRefPath(sigListPath, sigRef.BaseLoc) + report := SignatureVerifyReport{ + ID: sigRef.ID, + BaseLoc: sigRef.BaseLoc, + Type: sigRef.Type, + SealMatchOK: true, + } + sigData, err := r.readFileExact(sigPath) + if err != nil { + report.Error = err.Error() + return report + } + sigFile, err := parseSignatureFile(sigData) + if err != nil { + report.Error = err.Error() + return report + } + report.Provider = sigFile.SignedInfo.Provider + report.SignatureMethod = sigFile.SignedInfo.SignatureMethod + report.SignatureDateTime = sigFile.SignedInfo.SignatureDateTime + report.DigestMethod = sigFile.SignedInfo.References.CheckMethod + report.References = r.verifySignatureReferences(sigPath, sigFile.SignedInfo.References) + report.DigestOK = referencesOK(report.References) + signedValuePath := signatureRefPath(sigPath, sigFile.SignedValue) + signedValue, err := r.readFileExact(signedValuePath) + if err != nil { + report.Error = err.Error() + return report + } + switch sigRef.Type { + case SignTypeSign: + result, err := verifyDigitalSignature(report.SignatureMethod, signedValue, sigData, options) + if err != nil { + report.Error = err.Error() + return report + } + report.DataHashOK = result.DataHashOK + report.SignedValueOK = result.SignedOK + report.SealOK = true + report.CertOK = result.CertOK + report.Valid = report.DigestOK && report.DataHashOK && report.SignedValueOK && report.CertOK + return report + case "", SignTypeSeal: + default: + report.Error = fmt.Sprintf("unsupported signature type: %s", sigRef.Type) + return report + } + sesResult, err := verifySESSignature(signedValue, sigData) + if err != nil { + report.Error = err.Error() + return report + } + report.DataHashOK = sesResult.DataHashOK + report.SignedValueOK = sesResult.SignedOK + report.SealOK = sesResult.SealOK + report.CertOK = sesResult.CertOK + if sigFile.SignedInfo.Seal.BaseLoc != "" { + sealPath := signatureRefPath(sigPath, sigFile.SignedInfo.Seal.BaseLoc) + sealData, err := r.readFileExact(sealPath) + if err != nil { + report.Error = err.Error() + return report + } + sig, err := parseSESSignature(signedValue) + if err != nil { + report.Error = err.Error() + return report + } + report.SealMatchOK = bytes.Equal(sealData, sig.Seal.Raw) + } + report.Valid = report.DigestOK && report.DataHashOK && report.SignedValueOK && report.SealOK && report.SealMatchOK && report.CertOK + return report +} + +// verifySignatureReferences 验证签名保护文件列表 +// 入参: sigPath 签名文件路径, refs 签名保护文件列表 +// 返回: []SignatureReferenceVerify 保护文件验证结果 +func (r *Reader) verifySignatureReferences(sigPath string, refs SignatureReferences) []SignatureReferenceVerify { + results := make([]SignatureReferenceVerify, 0, len(refs.Reference)) + for _, ref := range refs.Reference { + results = append(results, r.verifySignatureReference(sigPath, refs.CheckMethod, ref)) + } + return results +} + +// verifySignatureReference 验证签名保护文件 +// 入参: sigPath 签名文件路径, method 摘要算法, ref 保护文件引用 +// 返回: SignatureReferenceVerify 保护文件验证结果 +func (r *Reader) verifySignatureReference(sigPath, method string, ref SignatureReference) SignatureReferenceVerify { + refPath := signatureRefPath(sigPath, ref.FileRef) + result := SignatureReferenceVerify{ + FileRef: ref.FileRef, + Path: refPath, + } + checkValue, err := base64.StdEncoding.DecodeString(strings.TrimSpace(ref.CheckValue)) + if err != nil { + result.Error = err.Error() + return result + } + data, err := r.readFileExact(refPath) + if err != nil { + result.Error = err.Error() + return result + } + actual, err := signatureDigest(method, data) + if err != nil { + result.Error = err.Error() + return result + } + result.CheckValue = checkValue + result.Actual = actual + result.OK = subtle.ConstantTimeCompare(checkValue, actual) == 1 + return result +} + +// parseSignatureFile 解析签名文件 +// 入参: data 签名文件XML数据 +// 返回: *SignatureFile 签名文件结构, error 错误信息 +func parseSignatureFile(data []byte) (*SignatureFile, error) { + var sigFile SignatureFile + if err := xml.Unmarshal(data, &sigFile); err != nil { + return nil, err + } + raw, err := xmlElementRaw(data, "SignedInfo") + if err != nil { + return nil, err + } + sigFile.SignedInfo.Raw = raw + return &sigFile, nil +} + +// readFileExact 读取OFD包内文件 +// 入参: name 包内文件路径 +// 返回: []byte 文件数据, error 错误信息 +func (r *Reader) readFileExact(name string) ([]byte, error) { + name = cleanPackagePath(name) + if f, ok := r.fileIndex[name]; ok { + return readZipFile(f) + } + return nil, fmt.Errorf("file not found: %s", name) +} + +// signatureDigest 计算签名摘要 +// 入参: method 摘要算法, data 原文数据 +// 返回: []byte 摘要值, error 错误信息 +func signatureDigest(method string, data []byte) ([]byte, error) { + if isSM3DigestMethod(method) { + return signSM3(data), nil + } + return nil, fmt.Errorf("unsupported digest method: %s", method) +} + +// signatureRefPath 解析签名文件引用路径 +// 入参: basePath 基准路径, refPath 引用路径 +// 返回: string 包内文件路径 +func signatureRefPath(basePath, refPath string) string { + p := strings.TrimSpace(refPath) + p = strings.ReplaceAll(p, "\\", "/") + if strings.HasPrefix(p, "/") { + return cleanPackagePath(p) + } + return path.Clean(resolveResourcePath(basePath, "", p)) +} + +// referencesOK 判断保护文件摘要是否全部通过 +// 入参: refs 保护文件验证结果 +// 返回: bool 是否全部通过 +func referencesOK(refs []SignatureReferenceVerify) bool { + if len(refs) == 0 { + return false + } + for _, ref := range refs { + if !ref.OK { + return false + } + } + return true +} + +// xmlElementRaw 提取XML元素原始字节 +// 入参: data XML数据, localName 元素名称 +// 返回: []byte 元素原始字节, error 错误信息 +func xmlElementRaw(data []byte, localName string) ([]byte, error) { + dec := xml.NewDecoder(bytes.NewReader(data)) + for { + tok, err := dec.Token() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + start, ok := tok.(xml.StartElement) + if !ok || start.Name.Local != localName { + continue + } + end := int(dec.InputOffset()) + begin := bytes.LastIndex(data[:end], []byte("<")) + if begin < 0 { + return nil, fmt.Errorf("xml element not found: %s", localName) + } + depth := 1 + for depth > 0 { + tok, err = dec.Token() + if err != nil { + return nil, err + } + switch tok.(type) { + case xml.StartElement: + depth++ + case xml.EndElement: + depth-- + } + } + return append([]byte(nil), data[begin:int(dec.InputOffset())]...), nil + } + return nil, fmt.Errorf("xml element not found: %s", localName) +} + +// parseSignatureCerts 解析签名验证证书 +// 入参: data DER或PEM编码证书 +// 返回: [][]byte DER编码证书列表 +func parseSignatureCerts(data []byte) [][]byte { + data = bytes.TrimSpace(data) + if len(data) == 0 { + return nil + } + var certs [][]byte + rest := data + hasPEM := false + for { + block, next := pem.Decode(rest) + if block == nil { + break + } + hasPEM = true + if block.Type == "CERTIFICATE" { + certs = append(certs, append([]byte(nil), block.Bytes...)) + } + rest = next + } + if len(certs) != 0 { + return certs + } + if hasPEM { + return nil + } + return [][]byte{append([]byte(nil), data...)} +}