feat(数字签名): 优化签名验签状态与时间展示,统一前后端字段顺序
build.yaml / build (push) Successful in 37s

This commit is contained in:
xiaoqidun committed 2026-09-08 13:45:58 +08:00
1 parent b0d368e067
commit 7ee1052c8b
5 files changed
+132 -38

No files matched your search

+25 -16
View File
@@ -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) {
+37 -10
View File
@@ -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 获取签名错误信息
+20 -3
View File
@@ -29,8 +29,11 @@ const (
// digitalVerifyResult 数字签名验证结果
type digitalVerifyResult struct {
DataHashChecked bool
DataHashOK bool
SignedChecked bool
SignedOK bool
CertChecked bool
CertOK bool
SignerCerts [][]byte
Certs [][]byte
@@ -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
}
+8
View File
@@ -74,9 +74,13 @@ type sesCertDigest struct {
// sesVerifyResult SES签章验证结果
type sesVerifyResult struct {
DataHashChecked bool
DataHashOK bool
SignedChecked bool
SignedOK bool
SealChecked bool
SealOK bool
CertChecked bool
CertOK bool
SignCert SignatureCertInfo
SealCert SignatureCertInfo
@@ -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
}
+42 -9
View File
@@ -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
}