From 7ee1052c8b6fa8f23d8238e3d22a4dc1651661ad Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Tue, 8 Sep 2026 13:45:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=95=B0=E5=AD=97=E7=AD=BE=E5=90=8D):=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=AD=BE=E5=90=8D=E9=AA=8C=E7=AD=BE=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E4=B8=8E=E6=97=B6=E9=97=B4=E5=B1=95=E7=A4=BA=EF=BC=8C?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=89=8D=E5=90=8E=E7=AB=AF=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/webui/ofdgo.js | 41 ++++++++++++++++++++------------- internal/webui/webui.go | 47 +++++++++++++++++++++++++++++-------- ofdgo_sign_gbt.go | 35 ++++++++++++++++++++-------- ofdgo_sign_ses.go | 34 ++++++++++++++++----------- ofdgo_sign_verify.go | 51 +++++++++++++++++++++++++++++++++-------- 5 files changed, 151 insertions(+), 57 deletions(-) diff --git a/assets/webui/ofdgo.js b/assets/webui/ofdgo.js index b9780ca..4bf15bb 100644 --- a/assets/webui/ofdgo.js +++ b/assets/webui/ofdgo.js @@ -1773,7 +1773,7 @@ function renderSignatures() { const fragment = document.createDocumentFragment(); for (const signature of signatures) { const row = document.createElement("div"); - row.className = `signature-row ${signature.valid ? "valid" : "invalid"}`; + row.className = `signature-row ${signature.status}`; const head = document.createElement("div"); head.className = "signature-head"; @@ -1782,7 +1782,7 @@ function renderSignatures() { const badges = document.createElement("div"); badges.className = "signature-badges"; - badges.append(fontBadge(signature.valid ? "通过" : "异常", signature.valid ? "valid" : "invalid")); + badges.append(fontBadge(signature.status === "valid" ? "通过" : signature.status === "invalid" ? "异常" : "未验", signature.status)); head.append(name, badges); row.append(head); @@ -1795,20 +1795,20 @@ function renderSignatures() { appendSignatureLine(row, "签者", signature.signer); appendSignatureLine(row, "时间", formatSignatureTime(signature.signatureDateTime)); appendSignatureLine(row, "机构", signatureAgency(signature)); - appendSignatureCheck(row, "原文", signature.dataHashOK); - appendSignatureCheck(row, "签名", signature.signedValueOK); + appendSignatureCheck(row, "原文", signature.dataHashChecked, signature.dataHashOK); + appendSignatureCheck(row, "签名", signature.signedValueChecked, signature.signedValueOK); if (signature.type !== "Sign") { - appendSignatureCheck(row, "章验", signature.sealOK); - appendSignatureCheck(row, "一致", signature.sealMatchOK); + appendSignatureCheck(row, "章验", signature.sealChecked, signature.sealOK); + appendSignaturePolicy(row, "一致", signature.sealMatchChecked, signature.sealMatchOK); } - appendSignatureCheck(row, "证书", signature.certOK); + appendSignatureCheck(row, "证书", signature.certChecked, signature.certOK); appendSignaturePolicy(row, "签期", signature.signatureTimeChecked, signature.signatureTimeOK); appendSignaturePolicy(row, "章期", signature.sealTimeChecked, signature.sealTimeOK); if (signature.sealCertTimeChecked) { appendSignatureLine(row, "制期", signature.sealCertTimeOK ? "有效" : "失效", signature.sealCertTimeOK ? "ok" : ""); } appendSignaturePolicy(row, "时效", signature.certTimeChecked, signature.certTimeOK); - appendSignaturePolicy(row, "信任", signature.certTrustChecked, signature.certTrustOK); + appendSignatureCheck(row, "信任", signature.certTrustChecked, signature.certTrustOK); appendSignatureLine(row, "保护", signatureReferenceText(signature), signatureReferenceStatus(signature)); appendSignatureLine(row, "算法", signature.signatureMethod); appendSignatureLine(row, "散列", signature.digestMethod); @@ -1826,11 +1826,16 @@ function signatureSummary(signatures) { if (!signatures.length) { return "0"; } - const invalid = signatures.filter((signature) => !signature.valid).length; + const invalid = signatures.filter((signature) => signature.status === "invalid").length; + const unchecked = signatures.filter((signature) => signature.status === "unchecked").length; + const parts = [String(signatures.length)]; if (invalid) { - return `${signatures.length} · 异常 ${invalid}`; + parts.push(`异常 ${invalid}`); } - return `通过 ${signatures.length}`; + if (unchecked) { + parts.push(`未验 ${unchecked}`); + } + return parts.length > 1 ? parts.join(" · ") : `通过 ${signatures.length}`; } function signatureNameNode(signature) { @@ -1885,7 +1890,8 @@ function signatureReferenceText(signature) { if (!count) { return ""; } - return `${passed === count ? "通过" : "失败"} ${passed}/${count}`; + const status = signatureReferenceStatus(signature); + return `${status === "ok" ? "通过" : status === "fail" ? "失败" : "未验"} ${passed}/${count}`; } function signatureReferenceStatus(signature) { @@ -1893,7 +1899,10 @@ function signatureReferenceStatus(signature) { if (!count) { return ""; } - return signature.referencePassed === count ? "ok" : "fail"; + if (signature.referencePassed < signature.referenceChecked) { + return "fail"; + } + return signature.referenceChecked === count ? "ok" : ""; } function formatSignatureTime(value) { @@ -1920,15 +1929,15 @@ function appendSignatureLine(row, label, value, status = "") { row.append(line); } -function appendSignatureCheck(row, label, ok) { - appendSignatureLine(row, label, ok ? "通过" : "失败", ok ? "ok" : "fail"); +function appendSignatureCheck(row, label, checked, ok) { + appendSignatureLine(row, label, checked ? ok ? "通过" : "失败" : "未验", checked ? ok ? "ok" : "fail" : ""); } function appendSignaturePolicy(row, label, checked, ok) { if (!checked) { return; } - appendSignatureCheck(row, label, ok); + appendSignatureCheck(row, label, checked, ok); } async function focusSignatureStamp(stamp) { diff --git a/internal/webui/webui.go b/internal/webui/webui.go index addab44..048690c 100644 --- a/internal/webui/webui.go +++ b/internal/webui/webui.go @@ -23,6 +23,7 @@ import ( "image/jpeg" "image/png" "strings" + "time" "github.com/xiaoqidun/ofdgo" ) @@ -43,7 +44,7 @@ type FontInfo = ofdgo.FontInfo type SignatureInfo struct { ID string `json:"id"` Type string `json:"type"` - Valid bool `json:"valid"` + Status string `json:"status"` Version string `json:"version,omitempty"` SealType string `json:"sealType,omitempty"` SealID string `json:"sealId,omitempty"` @@ -53,10 +54,15 @@ type SignatureInfo struct { SignatureDateTime string `json:"signatureDateTime,omitempty"` Provider string `json:"provider,omitempty"` Company string `json:"company,omitempty"` + DataHashChecked bool `json:"dataHashChecked"` DataHashOK bool `json:"dataHashOK"` + SignedValueChecked bool `json:"signedValueChecked"` SignedValueOK bool `json:"signedValueOK"` + SealChecked bool `json:"sealChecked"` SealOK bool `json:"sealOK"` + SealMatchChecked bool `json:"sealMatchChecked"` SealMatchOK bool `json:"sealMatchOK"` + CertChecked bool `json:"certChecked"` CertOK bool `json:"certOK"` SignatureTimeChecked bool `json:"signatureTimeChecked,omitempty"` SignatureTimeOK bool `json:"signatureTimeOK,omitempty"` @@ -69,6 +75,7 @@ type SignatureInfo struct { CertTrustChecked bool `json:"certTrustChecked,omitempty"` CertTrustOK bool `json:"certTrustOK,omitempty"` ReferenceCount int `json:"referenceCount"` + ReferenceChecked int `json:"referenceChecked"` ReferencePassed int `json:"referencePassed"` SignatureMethod string `json:"signatureMethod,omitempty"` DigestMethod string `json:"digestMethod,omitempty"` @@ -426,23 +433,39 @@ func (s *Session) signatureInfos() ([]SignatureInfo, error) { // 入参: report 签名验证报告 // 返回: SignatureInfo 签名验证信息 func signatureInfo(report ofdgo.SignatureVerifyReport) SignatureInfo { + status := "unchecked" + if report.Valid { + status = "valid" + } else if report.HasFailure() { + status = "invalid" + } + dateTime := report.SignatureDateTime + if !report.SignatureTime.IsZero() { + dateTime = report.SignatureTime.Format(time.RFC3339Nano) + } + checked, passed := signatureReferenceCounts(report.References) return SignatureInfo{ ID: report.ID, Type: string(report.Type), - Valid: report.Valid, + Status: status, Version: report.Provider.Version, SealType: report.SealType, SealID: report.SealInfo.ID, SealName: report.SealInfo.Name, SealVendor: report.SealInfo.VendorID, Signer: signatureSigner(report), - SignatureDateTime: report.SignatureDateTime, + SignatureDateTime: dateTime, Provider: report.Provider.ProviderName, Company: report.Provider.Company, + DataHashChecked: report.DataHashChecked, DataHashOK: report.DataHashOK, + SignedValueChecked: report.SignedValueChecked, SignedValueOK: report.SignedValueOK, + SealChecked: report.SealChecked, SealOK: report.SealOK, + SealMatchChecked: report.SealMatchChecked, SealMatchOK: report.SealMatchOK, + CertChecked: report.CertChecked, CertOK: report.CertOK, SignatureTimeChecked: report.SignatureTimeChecked, SignatureTimeOK: report.SignatureTimeOK, @@ -455,7 +478,8 @@ func signatureInfo(report ofdgo.SignatureVerifyReport) SignatureInfo { CertTrustChecked: report.CertTrustChecked, CertTrustOK: report.CertTrustOK, ReferenceCount: len(report.References), - ReferencePassed: signatureReferencePassed(report.References), + ReferenceChecked: checked, + ReferencePassed: passed, SignatureMethod: report.SignatureMethod, DigestMethod: report.DigestMethod, SignSerial: report.SignCert.SerialNumber, @@ -483,17 +507,20 @@ func signatureSigner(report ofdgo.SignatureVerifyReport) string { return report.SignCert.Subject } -// signatureReferencePassed 获取已通过保护文件数量 +// signatureReferenceCounts 获取保护文件检查数量 // 入参: refs 保护文件验证结果 -// 返回: int 已通过保护文件数量 -func signatureReferencePassed(refs []ofdgo.SignatureReferenceVerify) int { - count := 0 +// 返回: int 已检查数量, int 已通过数量 +func signatureReferenceCounts(refs []ofdgo.SignatureReferenceVerify) (int, int) { + checked, passed := 0, 0 for _, ref := range refs { + if ref.Checked { + checked++ + } if ref.OK { - count++ + passed++ } } - return count + return checked, passed } // signatureReportError 获取签名错误信息 diff --git a/ofdgo_sign_gbt.go b/ofdgo_sign_gbt.go index c86ddec..53fcf0c 100644 --- a/ofdgo_sign_gbt.go +++ b/ofdgo_sign_gbt.go @@ -29,12 +29,15 @@ const ( // digitalVerifyResult 数字签名验证结果 type digitalVerifyResult struct { - DataHashOK bool - SignedOK bool - CertOK bool - SignerCerts [][]byte - Certs [][]byte - CertInfo SignatureCertInfo + DataHashChecked bool + DataHashOK bool + SignedChecked bool + SignedOK bool + CertChecked bool + CertOK bool + SignerCerts [][]byte + Certs [][]byte + CertInfo SignatureCertInfo } // gbtSignedData GB/T 35275 SignedData结构 @@ -85,15 +88,17 @@ func verifyRawDigitalSignature(signedValue, signedData []byte, options *signatur if len(options.SignCerts) == 0 { return nil, fmt.Errorf("signature certificate not found") } - result := &digitalVerifyResult{DataHashOK: true} + result := &digitalVerifyResult{DataHashChecked: true, DataHashOK: true} for _, cert := range options.SignCerts { pub, err := parseSM2PublicKeyFromCert(cert) if err != nil { continue } + result.CertChecked = true result.CertOK = true result.SignerCerts = [][]byte{cert} result.CertInfo = signatureCertInfo(cert) + result.SignedChecked = true if sm2VerifySignature(pub, nil, signedData, signedValue) { result.SignedOK = true return result, nil @@ -112,15 +117,17 @@ func verifyRawPublicKeySignature(method, digestMethod string, signedValue, signe if _, err := signatureMethodHash(method, digestMethod); err != nil { return nil, err } - result := &digitalVerifyResult{DataHashOK: true} + result := &digitalVerifyResult{DataHashChecked: true, DataHashOK: true} for _, cert := range options.SignCerts { ok, err := verifyPublicKeySignature(method, digestMethod, cert, signedData, signedValue) if err != nil { continue } + result.CertChecked = true result.CertOK = true result.SignerCerts = [][]byte{cert} result.CertInfo = signatureCertInfo(cert) + result.SignedChecked = true if ok { result.SignedOK = true return result, nil @@ -147,18 +154,21 @@ func verifyGBT35275SignedData(signedValue, signedData []byte, options *signature return nil, fmt.Errorf("invalid signed data signer info") } result := &digitalVerifyResult{Certs: sd.rawCerts()} - for _, signer := range sd.Signers { + for index, signer := range sd.Signers { digest, err := signatureDigest(signer.DigestAlg, signedData) if err != nil { return nil, err } if len(sd.ContentDigest) != 0 && !bytes.Equal(sd.ContentDigest, digest) { + result.DataHashChecked = true + result.DataHashOK = false return result, nil } result.DataHashOK = true plain := sd.ContentDigest if len(signer.AuthAttrs) != 0 { if !bytes.Equal(signer.AttrDigest, digest) { + result.DataHashChecked = true result.DataHashOK = false return result, nil } @@ -167,6 +177,7 @@ func verifyGBT35275SignedData(signedValue, signedData []byte, options *signature if len(plain) == 0 { return nil, fmt.Errorf("invalid signed data content") } + result.DataHashChecked = index == len(sd.Signers)-1 cert := sd.findCert(signer.Issuer, signer.Serial) if cert == nil { result.CertOK = false @@ -181,6 +192,8 @@ func verifyGBT35275SignedData(signedValue, signedData []byte, options *signature return result, err } if !sm2VerifySignature(pub, nil, plain, signer.Signature) { + result.SignedChecked = true + result.CertChecked = index == len(sd.Signers)-1 result.CertOK = true return result, nil } @@ -195,11 +208,15 @@ func verifyGBT35275SignedData(signedValue, signedData []byte, options *signature return result, err } if !ok { + result.SignedChecked = true + result.CertChecked = index == len(sd.Signers)-1 result.CertOK = true return result, nil } } + result.CertChecked = true result.CertOK = true + result.SignedChecked = true result.SignedOK = true return result, nil } diff --git a/ofdgo_sign_ses.go b/ofdgo_sign_ses.go index 5adf820..619961b 100644 --- a/ofdgo_sign_ses.go +++ b/ofdgo_sign_ses.go @@ -74,19 +74,23 @@ type sesCertDigest struct { // sesVerifyResult SES签章验证结果 type sesVerifyResult struct { - DataHashOK bool - SignedOK bool - SealOK bool - CertOK bool - SignCert SignatureCertInfo - SealCert SignatureCertInfo - SignCertRaw []byte - SealCertRaw []byte - SealRaw []byte - Certs [][]byte - SealType string - SealInfo SignatureSealInfo - SignatureTime time.Time + DataHashChecked bool + DataHashOK bool + SignedChecked bool + SignedOK bool + SealChecked bool + SealOK bool + CertChecked bool + CertOK bool + SignCert SignatureCertInfo + SealCert SignatureCertInfo + SignCertRaw []byte + SealCertRaw []byte + SealRaw []byte + Certs [][]byte + SealType string + SealInfo SignatureSealInfo + SignatureTime time.Time } // parseSESSignature 解析SES签章值 @@ -240,6 +244,7 @@ func verifySESSignature(data, signedData []byte, options *signatureVerifyOptions result.SealType = sig.Seal.PicType result.SealInfo = sig.Seal.Info result.SignatureTime = sig.Time + result.DataHashChecked = true result.DataHashOK = bytes.Equal(sig.DataHash, signSM3(signedData)) signPub, err := parseSM2PublicKeyFromCert(sig.Cert) if err != nil { @@ -249,8 +254,11 @@ func verifySESSignature(data, signedData []byte, options *signatureVerifyOptions if err != nil { return result, err } + result.SignedChecked = true result.SignedOK = sm2VerifySignature(signPub, nil, sig.ToSign, sig.Signature) + result.SealChecked = true result.SealOK = sm2VerifySignature(sealPub, nil, sig.Seal.SignData, sig.Seal.Signature) + result.CertChecked = true result.CertOK = sesCertInList(sig.Cert, sig.Seal.CertList) return result, nil } diff --git a/ofdgo_sign_verify.go b/ofdgo_sign_verify.go index bf3e8bc..9f0f0d4 100644 --- a/ofdgo_sign_verify.go +++ b/ofdgo_sign_verify.go @@ -39,6 +39,7 @@ import ( // SignatureVerifyReport 签名验证报告 // Valid表示签名完整性、签名时间语义及调用方指定的证书策略均通过 +// Checked表示对应检查已得出结论, 未检查时不能将OK的零值视为失败 // SealCertTimeOK仅提供制章证书在签名时间的状态信息, 不参与Valid判断 type SignatureVerifyReport struct { ID string @@ -59,10 +60,15 @@ type SignatureVerifyReport struct { StampPositions []SignatureStampPosition StampPositionError string DigestOK bool + DataHashChecked bool DataHashOK bool + SignedValueChecked bool SignedValueOK bool + SealChecked bool SealOK bool + SealMatchChecked bool SealMatchOK bool + CertChecked bool CertOK bool SignatureTimeChecked bool SignatureTimeOK bool @@ -90,6 +96,21 @@ func (report SignatureVerifyReport) TrustedValid() bool { return report.IntegrityValid() && report.certificatePolicyOK() && report.CertTrustChecked && report.CertTimeChecked } +// HasFailure 判断已完成的签名检查是否存在失败 +// 返回: bool 是否存在失败, 未完成检查时仍需判断Valid和Error +func (report SignatureVerifyReport) HasFailure() bool { + for _, ref := range report.References { + if ref.Checked && !ref.OK { + return true + } + } + return report.DataHashChecked && !report.DataHashOK || + report.SignedValueChecked && !report.SignedValueOK || + report.SealChecked && !report.SealOK || + report.SealMatchChecked && !report.SealMatchOK || + report.CertChecked && !report.CertOK || !report.certificatePolicyOK() +} + // SignatureCertInfo 签名证书信息 type SignatureCertInfo struct { Raw []byte @@ -120,6 +141,7 @@ type SignatureReferenceVerify struct { Path string CheckValue []byte Actual []byte + Checked bool OK bool Error string } @@ -298,16 +320,21 @@ func (r *Reader) verifySignature(sigListPath string, sigRef Signature, options * switch report.Type { case SignTypeSign: result, err := verifyDigitalSignature(report.SignatureMethod, report.DigestMethod, signedValue, sigData, options) + if result != nil { + report.DataHashChecked = result.DataHashChecked + report.DataHashOK = result.DataHashOK + report.SignedValueChecked = result.SignedChecked + report.SignedValueOK = result.SignedOK + report.CertChecked = result.CertChecked + report.CertOK = result.CertOK + report.SignCert = result.CertInfo + report.Signer = result.CertInfo.CommonName + } 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.SignCert = result.CertInfo - report.Signer = result.CertInfo.CommonName report.SignatureTime = parseSignatureDateTime(report.SignatureDateTime) report.applySignatureTimePolicy() report.applySignatureCertificatePolicy(options, result.SignerCerts, result.Certs) @@ -320,6 +347,14 @@ func (r *Reader) verifySignature(sigListPath string, sigRef Signature, options * } sesResult, err := verifySESSignature(signedValue, sigData, options) if sesResult != nil { + report.DataHashChecked = sesResult.DataHashChecked + report.DataHashOK = sesResult.DataHashOK + report.SignedValueChecked = sesResult.SignedChecked + report.SignedValueOK = sesResult.SignedOK + report.SealChecked = sesResult.SealChecked + report.SealOK = sesResult.SealOK + report.CertChecked = sesResult.CertChecked + report.CertOK = sesResult.CertOK report.SignCert = sesResult.SignCert report.SealCert = sesResult.SealCert report.SealInfo = sesResult.SealInfo @@ -331,10 +366,6 @@ func (r *Reader) verifySignature(sigListPath string, sigRef Signature, options * report.Error = err.Error() return report } - report.DataHashOK = sesResult.DataHashOK - report.SignedValueOK = sesResult.SignedOK - report.SealOK = sesResult.SealOK - report.CertOK = sesResult.CertOK report.applySignatureTimePolicy() report.applySignatureCertificatePolicy(options, [][]byte{sesResult.SignCertRaw, sesResult.SealCertRaw}, sesResult.Certs) if sigFile.SignedInfo.Seal.BaseLoc != "" { @@ -344,6 +375,7 @@ func (r *Reader) verifySignature(sigListPath string, sigRef Signature, options * report.Error = err.Error() return report } + report.SealMatchChecked = true report.SealMatchOK = bytes.Equal(sealData, sesResult.SealRaw) } report.Valid = report.IntegrityValid() && report.certificatePolicyOK() @@ -387,6 +419,7 @@ func (r *Reader) verifySignatureReference(sigPath, method string, ref SignatureR } result.CheckValue = checkValue result.Actual = actual + result.Checked = true result.OK = subtle.ConstantTimeCompare(checkValue, actual) == 1 return result }