diff --git a/ofdgo_reader.go b/ofdgo_reader.go index faf6bc6..a42b895 100644 --- a/ofdgo_reader.go +++ b/ofdgo_reader.go @@ -38,7 +38,6 @@ type Reader struct { Stamps map[string][]Stamp Annots map[string][]Annotation fileIndex map[string]*zip.File - fileIndexFold map[string]*zip.File } // Close 关闭阅读器 @@ -54,14 +53,9 @@ func (r *Reader) Close() error { // 返回: error 错误信息 func (r *Reader) initRoot() error { r.fileIndex = make(map[string]*zip.File) - r.fileIndexFold = make(map[string]*zip.File) for _, f := range r.Zip.File { name := cleanPackagePath(f.Name) r.fileIndex[name] = f - fold := strings.ToLower(name) - if _, ok := r.fileIndexFold[fold]; !ok { - r.fileIndexFold[fold] = f - } } data, err := r.readFile("OFD.xml") if err != nil { @@ -117,9 +111,6 @@ func (r *Reader) packageFile(name string) (*zip.File, bool) { if f, ok := r.fileIndex[name]; ok { return f, true } - if f, ok := r.fileIndexFold[strings.ToLower(name)]; ok { - return f, true - } return nil, false } @@ -176,7 +167,7 @@ func (r *Reader) loadRes(resPath string) { if resPath == "" { return } - fullPath := path.Join(r.RootDir, resPath) + fullPath := r.ResPath(resPath) data, err := r.readFile(fullPath) if err != nil { return @@ -235,7 +226,7 @@ func resolveResourcePath(resPath, baseLoc, filePath string) string { // 入参: page 页面对象 // 返回: *PageContent 页面内容, error 错误信息 func (r *Reader) PageContent(page Page) (*PageContent, error) { - fullPath := path.Join(r.RootDir, page.BaseLoc) + fullPath := r.ResPath(page.BaseLoc) data, err := r.readFile(fullPath) if err != nil { return nil, err @@ -260,7 +251,9 @@ func (r *Reader) ResPath(resLink string) string { if resLink == "" { return "" } - resLink = strings.TrimPrefix(resLink, "/") + if strings.HasPrefix(resLink, "/") { + return cleanPackagePath(resLink) + } resLink = path.Clean(resLink) rootDir := strings.TrimPrefix(strings.ReplaceAll(r.RootDir, "\\", "/"), "/") if rootDir != "" && (resLink == rootDir || strings.HasPrefix(resLink, rootDir+"/")) { diff --git a/ofdgo_renderer_stamp.go b/ofdgo_renderer_stamp.go index 4280307..b58a304 100644 --- a/ofdgo_renderer_stamp.go +++ b/ofdgo_renderer_stamp.go @@ -18,16 +18,23 @@ import ( "bytes" "image" "image/color" + "image/draw" + "math" "github.com/tdewolff/canvas" + "github.com/tdewolff/canvas/renderers/rasterizer" ) // renderStamp 渲染印章 // 入参: ctx 画布上下文, s 印章对象, pageH 页面高度 func (r *Renderer) renderStamp(ctx *canvas.Context, s Stamp, pageH float64) { - x, y, w, h := s.Box.X, s.Box.Y, s.Box.W, s.Box.H - screenY := pageH - (y + h) if s.Type == "ofd" && len(s.Data) > 0 { + if s.Clip != nil { + if img := r.renderOFDStampImage(s.Data); img != nil { + r.renderStampImage(ctx, stampImageWithTransparentWhite(img), s, pageH) + } + return + } reader, err := NewReader(bytes.NewReader(s.Data), int64(len(s.Data))) if err == nil { defer reader.Close() @@ -44,8 +51,8 @@ func (r *Renderer) renderStamp(ctx *canvas.Context, s Stamp, pageH float64) { continue } ctx.Push() - ctx.Translate(x, screenY) - ctx.Scale(w/sealBox.W, h/sealBox.H) + ctx.Translate(s.Box.X, pageH-(s.Box.Y+s.Box.H)) + ctx.Scale(s.Box.W/sealBox.W, s.Box.H/sealBox.H) renderer.renderPageToContext(ctx, content, false) ctx.Pop() } @@ -56,17 +63,76 @@ func (r *Renderer) renderStamp(ctx *canvas.Context, s Stamp, pageH float64) { if len(s.Data) > 0 { img, _, err := decodeImageData(s.Data) if err == nil { - img = stampImageWithTransparentWhite(img) - ctx.Push() - ctx.Translate(x, screenY) - ctx.Scale(w/float64(img.Bounds().Dx()), h/float64(img.Bounds().Dy())) - ctx.DrawImage(0, 0, img, canvas.DPMM(1.0)) - ctx.Pop() + r.renderStampImage(ctx, stampImageWithTransparentWhite(img), s, pageH) return } } } +// renderOFDStampImage 渲染OFD印章图像 +// 入参: data OFD印章数据 +// 返回: image.Image 印章图像 +func (r *Renderer) renderOFDStampImage(data []byte) image.Image { + reader, err := NewReader(bytes.NewReader(data), int64(len(data))) + if err != nil { + return nil + } + defer reader.Close() + doc, err := reader.Doc() + if err != nil { + return nil + } + renderer := r.childRenderer(reader) + for _, pageRef := range doc.Pages.Page { + content, err := reader.PageContent(pageRef) + if err != nil { + continue + } + sealBox, err := renderer.GetPageBox(content) + if err != nil { + continue + } + c := canvas.New(sealBox.W, sealBox.H) + if err := renderer.renderPageToContext(canvas.NewContext(c), content, false); err != nil { + continue + } + return rasterizer.Draw(c, canvas.DPMM(r.DPI/25.4), canvas.DefaultColorSpace) + } + return nil +} + +// renderStampImage 渲染印章图像 +// 入参: ctx 画布上下文, img 印章图像, s 印章对象, pageH 页面高度 +func (r *Renderer) renderStampImage(ctx *canvas.Context, img image.Image, s Stamp, pageH float64) { + box := s.Box + if s.Clip != nil { + img = clipStampImage(img, box, *s.Clip) + box.X += s.Clip.X + box.Y += s.Clip.Y + box.W = s.Clip.W + box.H = s.Clip.H + } + ctx.Push() + ctx.Translate(box.X, pageH-(box.Y+box.H)) + ctx.Scale(box.W/float64(img.Bounds().Dx()), box.H/float64(img.Bounds().Dy())) + ctx.DrawImage(0, 0, img, canvas.DPMM(1.0)) + ctx.Pop() +} + +// clipStampImage 裁剪印章图像 +// 入参: img 印章图像, box 印章区域, clip 裁剪区域 +// 返回: image.Image 裁剪后的印章图像 +func clipStampImage(img image.Image, box, clip Box) image.Image { + bounds := img.Bounds() + x0 := int(math.Floor(clip.X / box.W * float64(bounds.Dx()))) + y0 := int(math.Floor(clip.Y / box.H * float64(bounds.Dy()))) + x1 := int(math.Ceil((clip.X + clip.W) / box.W * float64(bounds.Dx()))) + y1 := int(math.Ceil((clip.Y + clip.H) / box.H * float64(bounds.Dy()))) + out := image.NewNRGBA(image.Rect(0, 0, x1-x0, y1-y0)) + draw.Draw(out, out.Bounds(), img, image.Pt(bounds.Min.X+x0, bounds.Min.Y+y0), draw.Src) + return out +} + // stampImageWithTransparentWhite 处理印章图片白色底色 // 入参: img 印章图片对象 // 返回: image.Image 处理后的印章图片对象 diff --git a/ofdgo_sign.go b/ofdgo_sign.go index b2a0bf3..24e2d1a 100644 --- a/ofdgo_sign.go +++ b/ofdgo_sign.go @@ -80,6 +80,7 @@ type SignatureStamp struct { ID string `xml:"ID,attr"` PageRef string `xml:"PageRef,attr"` Boundary string `xml:"Boundary,attr"` + Clip string `xml:"Clip,attr"` } // SignatureStampPosition 签名外观位置信息 @@ -89,6 +90,8 @@ type SignatureStampPosition struct { PageID string Boundary string Box Box + Clip string + ClipBox *Box } // SignatureReferences 签名保护文件列表 @@ -122,12 +125,22 @@ func (r *Reader) SignatureStampPositions(stamps []SignatureStamp) ([]SignatureSt if err != nil { return nil, err } + var clipBox *Box + if stamp.Clip != "" { + clip, err := parseSignatureStampBox(stamp.Clip) + if err != nil { + return nil, err + } + clipBox = &clip + } positions = append(positions, SignatureStampPosition{ ID: stamp.ID, Page: page, PageID: stamp.PageRef, Boundary: stamp.Boundary, Box: box, + Clip: stamp.Clip, + ClipBox: clipBox, }) } return positions, nil @@ -139,23 +152,23 @@ func (r *Reader) SignatureStampPositions(stamps []SignatureStamp) ([]SignatureSt func parseSignatureStampBox(s string) (Box, error) { parts := strings.Fields(s) if len(parts) != 4 { - return Box{}, fmt.Errorf("invalid signature stamp boundary: %s", s) + return Box{}, fmt.Errorf("invalid signature stamp box: %s", s) } x, err := strconv.ParseFloat(parts[0], 64) if err != nil { - return Box{}, fmt.Errorf("invalid signature stamp boundary: %s", s) + return Box{}, fmt.Errorf("invalid signature stamp box: %s", s) } y, err := strconv.ParseFloat(parts[1], 64) if err != nil { - return Box{}, fmt.Errorf("invalid signature stamp boundary: %s", s) + return Box{}, fmt.Errorf("invalid signature stamp box: %s", s) } w, err := strconv.ParseFloat(parts[2], 64) if err != nil { - return Box{}, fmt.Errorf("invalid signature stamp boundary: %s", s) + return Box{}, fmt.Errorf("invalid signature stamp box: %s", s) } h, err := strconv.ParseFloat(parts[3], 64) if err != nil { - return Box{}, fmt.Errorf("invalid signature stamp boundary: %s", s) + return Box{}, fmt.Errorf("invalid signature stamp box: %s", s) } return Box{X: x, Y: y, W: w, H: h}, nil } @@ -212,8 +225,19 @@ func (r *Reader) parseSignatures(doc *Document) error { } for _, annot := range sigFile.SignedInfo.StampAnnot { pageID := annot.PageRef - bbox, _ := ParseBox(annot.Boundary) - r.addStamp(pageID, bbox, sealType, sealData) + bbox, err := parseSignatureStampBox(annot.Boundary) + if err != nil { + continue + } + var clipBox *Box + if annot.Clip != "" { + clip, err := parseSignatureStampBox(annot.Clip) + if err != nil { + continue + } + clipBox = &clip + } + r.addStamp(pageID, bbox, clipBox, sealType, sealData) } }(sigRef) } @@ -415,18 +439,20 @@ func probeImageMedia(data []byte) (string, []byte) { // Stamp 印章信息结构 type Stamp struct { Box Box + Clip *Box Type string Data []byte } // addStamp 添加印章到页面 -// 入参: pageID 页面ID, box 印章区域, sType 印章类型, data 印章数据 -func (r *Reader) addStamp(pageID string, box Box, sType string, data []byte) { +// 入参: pageID 页面ID, box 印章区域, clip 裁剪区域, sType 印章类型, data 印章数据 +func (r *Reader) addStamp(pageID string, box Box, clip *Box, sType string, data []byte) { if r.Stamps == nil { r.Stamps = make(map[string][]Stamp) } r.Stamps[pageID] = append(r.Stamps[pageID], Stamp{ Box: box, + Clip: clip, Type: sType, Data: data, }) diff --git a/ofdgo_sign_gbt.go b/ofdgo_sign_gbt.go index 1b5317d..c86ddec 100644 --- a/ofdgo_sign_gbt.go +++ b/ofdgo_sign_gbt.go @@ -66,8 +66,8 @@ type gbtSignerInfo struct { // 入参: method 签名算法, digestMethod 摘要算法, signedValue 签名值, signedData 被签名原文, options 验证选项 // 返回: *digitalVerifyResult 验证结果, error 错误信息 func verifyDigitalSignature(method, digestMethod string, signedValue, signedData []byte, options *signatureVerifyOptions) (*digitalVerifyResult, error) { - if isGBT35275SignedValue(signedValue) { - return verifyGBT35275SignedData(signedValue, signedData, options) + if value, ok := normalizeGBT35275SignedValue(signedValue); ok { + return verifyGBT35275SignedData(value, signedData, options) } if isSM2SignatureMethod(method) { return verifyRawDigitalSignature(signedValue, signedData, options) @@ -204,27 +204,150 @@ func verifyGBT35275SignedData(signedValue, signedData []byte, options *signature return result, nil } -// isGBT35275SignedValue 判断签名值是否为GB/T 35275 SignedData +// normalizeGBT35275SignedValue 规范化GB/T 35275 SignedData编码 // 入参: data 签名值数据 -// 返回: bool 是否为SignedData -func isGBT35275SignedValue(data []byte) bool { +// 返回: []byte 定长编码数据, bool 是否为SignedData +func normalizeGBT35275SignedValue(data []byte) ([]byte, bool) { + if contentType, ok := gbtContentType(data); ok { + return data, contentType == signContentSignedData + } + der, err := berToDefinite(data) + if err != nil { + return nil, false + } + contentType, ok := gbtContentType(der) + if !ok || contentType != signContentSignedData { + return nil, false + } + return der, true +} + +// gbtContentType 读取GB/T 35275内容类型 +// 入参: data DER编码数据 +// 返回: string 内容类型, bool 是否完成解析 +func gbtContentType(data []byte) (string, bool) { var root asn1.RawValue rest, err := asn1.Unmarshal(data, &root) if err != nil || len(rest) != 0 || root.Tag != signASN1Sequence { - return false + return "", false } items, ok := asn1Children(root.Bytes) - if !ok || len(items) < 2 { - return false + if !ok || len(items) == 0 { + return "", false + } + if items[0].Tag != asn1.TagOID { + return "", true } oid, err := asn1OIDString(items[0]) - return err == nil && oid == signContentSignedData + return oid, err == nil +} + +// berToDefinite 将BER不定长编码转换为定长编码 +// 入参: data BER编码数据 +// 返回: []byte 定长编码数据, error 错误信息 +func berToDefinite(data []byte) ([]byte, error) { + out, n, err := berValueToDefinite(data) + if err != nil { + return nil, err + } + if n != len(data) { + return nil, fmt.Errorf("invalid BER trailing data") + } + return out, nil +} + +// berValueToDefinite 转换单个BER编码值 +// 入参: data BER编码数据 +// 返回: []byte 定长编码数据, int 已读取长度, error 错误信息 +func berValueToDefinite(data []byte) ([]byte, int, error) { + if len(data) < 2 { + return nil, 0, fmt.Errorf("invalid BER value") + } + pos := 1 + if data[0]&0x1f == 0x1f { + for pos < len(data) && data[pos]&0x80 != 0 { + pos++ + } + pos++ + } + if pos >= len(data) { + return nil, 0, fmt.Errorf("invalid BER tag") + } + tag := data[:pos] + firstLength := data[pos] + pos++ + if firstLength == 0x80 { + if tag[0]&0x20 == 0 { + return nil, 0, fmt.Errorf("invalid BER indefinite primitive") + } + var content []byte + for { + if len(data)-pos < 2 { + return nil, 0, fmt.Errorf("invalid BER unterminated value") + } + if data[pos] == 0 && data[pos+1] == 0 { + pos += 2 + break + } + child, n, err := berValueToDefinite(data[pos:]) + if err != nil { + return nil, 0, err + } + content = append(content, child...) + pos += n + } + return wrapBERValue(tag, content), pos, nil + } + length := uint64(firstLength) + if firstLength&0x80 != 0 { + n := int(firstLength & 0x7f) + if n == 0 || n > 8 || len(data)-pos < n { + return nil, 0, fmt.Errorf("invalid BER length") + } + length = 0 + for _, b := range data[pos : pos+n] { + length = length<<8 | uint64(b) + } + pos += n + } + if length > uint64(len(data)-pos) { + return nil, 0, fmt.Errorf("invalid BER truncated value") + } + end := pos + int(length) + content := append([]byte(nil), data[pos:end]...) + if tag[0]&0x20 != 0 { + content = content[:0] + for pos < end { + child, n, err := berValueToDefinite(data[pos:end]) + if err != nil { + return nil, 0, err + } + content = append(content, child...) + pos += n + } + } + return wrapBERValue(tag, content), end, nil +} + +// wrapBERValue 包装定长BER编码值 +// 入参: tag 标签, content 内容 +// 返回: []byte 定长编码数据 +func wrapBERValue(tag, content []byte) []byte { + out := make([]byte, 0, len(tag)+len(content)+9) + out = append(out, tag...) + out = append(out, asn1LengthBytes(len(content))...) + return append(out, content...) } // parseGBT35275SignedData 解析GB/T 35275 SignedData // 入参: data 签名值数据 // 返回: *gbtSignedData SignedData结构, error 错误信息 func parseGBT35275SignedData(data []byte) (*gbtSignedData, error) { + var ok bool + data, ok = normalizeGBT35275SignedValue(data) + if !ok { + return nil, fmt.Errorf("invalid signed data content type") + } contentType, content, ok, err := parseGBTContentInfoBytes(data) if err != nil { return nil, err diff --git a/ofdgo_sign_ses.go b/ofdgo_sign_ses.go index ae7790f..5adf820 100644 --- a/ofdgo_sign_ses.go +++ b/ofdgo_sign_ses.go @@ -20,6 +20,7 @@ import ( "fmt" "math/big" "strings" + "time" ) const ( @@ -42,6 +43,7 @@ type sesSignature struct { SignAlg string Signature []byte DataHash []byte + Time time.Time Seal *sesSeal } @@ -55,6 +57,7 @@ type sesSeal struct { PicType string PicData []byte CertList sesCertList + Info SignatureSealInfo } // sesCertList SES印章证书列表 @@ -71,17 +74,19 @@ 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 + 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 } // parseSESSignature 解析SES签章值 @@ -119,13 +124,21 @@ func parseSESSignature(data []byte) (*sesSignature, error) { return nil, err } tbsItems, ok := asn1Children(items[0].Bytes) - if !ok || len(tbsItems) < 5 { + if !ok || len(tbsItems) < 5 || len(tbsItems) > 6 { return nil, fmt.Errorf("invalid ses toSign") } seal, err := parseSESSeal(tbsItems[1]) if err != nil { return nil, err } + version, err := asn1Integer(tbsItems[0]) + if err != nil || version != seal.Info.Version { + return nil, fmt.Errorf("invalid ses version") + } + signatureTime, err := asn1Time(tbsItems[2]) + if err != nil { + return nil, err + } dataHash, err := asn1BitOrOctetBytes(tbsItems[3]) if err != nil { return nil, err @@ -136,6 +149,7 @@ func parseSESSignature(data []byte) (*sesSignature, error) { SignAlg: alg, Signature: signature, DataHash: dataHash, + Time: signatureTime, Seal: seal, }, nil } @@ -156,6 +170,11 @@ func parseSESSignatureV1(items []asn1.RawValue) (*sesSignature, error) { if err != nil { return nil, err } + version, err := asn1Integer(tbsItems[0]) + if err != nil || version != seal.Info.Version { + return nil, fmt.Errorf("invalid ses version") + } + signatureTime := parseSESSignatureTimeV1(tbsItems[2]) dataHash, err := asn1BitOrOctetBytes(tbsItems[3]) if err != nil { return nil, err @@ -174,10 +193,31 @@ func parseSESSignatureV1(items []asn1.RawValue) (*sesSignature, error) { SignAlg: alg, Signature: signature, DataHash: dataHash, + Time: signatureTime, Seal: seal, }, nil } +// parseSESSignatureTimeV1 解析SES V1签名时间 +// 入参: raw 签名时间或时间戳数据 +// 返回: time.Time 签名时间 +func parseSESSignatureTimeV1(raw asn1.RawValue) time.Time { + data, err := asn1BitOrOctetBytes(raw) + if err != nil { + return time.Time{} + } + if t := parseSignatureDateTime(string(data)); !t.IsZero() { + return t + } + var value asn1.RawValue + rest, err := asn1.Unmarshal(data, &value) + if err != nil || len(rest) != 0 { + return time.Time{} + } + t, _ := asn1Time(value) + return t +} + // verifySESSignature 验证SES签章值 // 入参: data 签章值数据, signedData 被签名数据原文, options 验证选项 // 返回: *sesVerifyResult 验证结果, error 错误信息 @@ -198,6 +238,8 @@ func verifySESSignature(data, signedData []byte, options *signatureVerifyOptions result.Certs = append(result.Certs, sig.Seal.CertList.Certs...) result.Certs = append(result.Certs, options.SignCerts...) result.SealType = sig.Seal.PicType + result.SealInfo = sig.Seal.Info + result.SignatureTime = sig.Time result.DataHashOK = bytes.Equal(sig.DataHash, signSM3(signedData)) signPub, err := parseSM2PublicKeyFromCert(sig.Cert) if err != nil { @@ -243,11 +285,7 @@ func parseSESSeal(raw asn1.RawValue) (*sesSeal, error) { 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) + info, certList, err := parseSESSealInfo(infoItems) if err != nil { return nil, err } @@ -264,6 +302,7 @@ func parseSESSeal(raw asn1.RawValue) (*sesSeal, error) { PicType: picType, PicData: picData, CertList: certList, + Info: info, }, nil } @@ -291,7 +330,7 @@ func parseSESSealV1(raw asn1.RawValue, items []asn1.RawValue) (*sesSeal, error) if err != nil { return nil, err } - certList, err := parseSESCertListV1(infoItems[2]) + info, certList, err := parseSESSealInfo(infoItems) if err != nil { return nil, err } @@ -309,43 +348,92 @@ func parseSESSealV1(raw asn1.RawValue, items []asn1.RawValue) (*sesSeal, error) PicType: picType, PicData: picData, CertList: certList, + Info: info, }, 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") +// parseSESSealInfo 解析电子印章信息 +// 入参: items 印章信息ASN.1子元素 +// 返回: SignatureSealInfo 印章信息, sesCertList 证书列表, error 错误信息 +func parseSESSealInfo(items []asn1.RawValue) (SignatureSealInfo, sesCertList, error) { + if len(items) < 4 { + return SignatureSealInfo{}, sesCertList{}, fmt.Errorf("invalid ses seal info") } - return asn1Integer(items[1]) + header, ok := asn1Children(items[0].Bytes) + if !ok || len(header) != 3 { + return SignatureSealInfo{}, sesCertList{}, fmt.Errorf("invalid ses header") + } + if asn1String(header[0]) != "ES" { + return SignatureSealInfo{}, sesCertList{}, fmt.Errorf("invalid ses header") + } + version, err := asn1Integer(header[1]) + if err != nil { + return SignatureSealInfo{}, sesCertList{}, err + } + property, ok := asn1Children(items[2].Bytes) + if !ok { + return SignatureSealInfo{}, sesCertList{}, fmt.Errorf("invalid ses property") + } + certIndex := 2 + timeIndex := 3 + if version >= 4 { + certIndex = 3 + timeIndex = 4 + } + if len(property) != timeIndex+3 { + return SignatureSealInfo{}, sesCertList{}, fmt.Errorf("invalid ses property") + } + typeValue, err := asn1Integer(property[0]) + if err != nil { + return SignatureSealInfo{}, sesCertList{}, err + } + certList, err := parseSESCertList(property, version, certIndex) + if err != nil { + return SignatureSealInfo{}, sesCertList{}, err + } + createTime, err := asn1Time(property[timeIndex]) + if err != nil { + return SignatureSealInfo{}, sesCertList{}, err + } + validStart, err := asn1Time(property[timeIndex+1]) + if err != nil { + return SignatureSealInfo{}, sesCertList{}, err + } + validEnd, err := asn1Time(property[timeIndex+2]) + if err != nil { + return SignatureSealInfo{}, sesCertList{}, err + } + return SignatureSealInfo{ + Version: version, + ID: strings.TrimSpace(asn1String(items[1])), + VendorID: strings.TrimSpace(asn1String(header[2])), + Type: typeValue, + Name: strings.TrimSpace(asn1String(property[1])), + CreateTime: createTime, + ValidStart: validStart, + ValidEnd: validEnd, + }, certList, nil } // parseSESCertList 解析印章证书列表 -// 入参: raw 印章属性信息, version 印章版本 +// 入参: property 印章属性, version 印章版本, certIndex 证书列表位置 // 返回: 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") +func parseSESCertList(property []asn1.RawValue, version, certIndex int) (sesCertList, error) { + if version == 1 { + return parseSESCertListV1(property[certIndex]) } if version < 4 { - return parseSESCertInfoList(items[2]) + return parseSESCertInfoList(property[certIndex]) } - if len(items) < 4 { - return sesCertList{}, fmt.Errorf("invalid ses cert list") - } - listType, err := asn1Integer(items[2]) + listType, err := asn1Integer(property[certIndex-1]) if err != nil { return sesCertList{}, err } switch listType { case 1: - return parseSESCertInfoList(items[3]) + return parseSESCertInfoList(property[certIndex]) case 2: - return parseSESCertDigestList(items[3]) + return parseSESCertDigestList(property[certIndex]) default: return sesCertList{}, fmt.Errorf("unsupported ses cert list type") } @@ -396,12 +484,12 @@ func parseSESCertDigestList(raw asn1.RawValue) (sesCertList, error) { list := sesCertList{Digests: make([]sesCertDigest, 0, len(items))} for _, item := range items { fields, ok := asn1Children(item.Bytes) - if !ok || len(fields) < 2 { + if !ok || len(fields) != 2 { return sesCertList{}, fmt.Errorf("invalid ses cert digest") } - method, err := parseGBTAlgorithm(fields[0]) - if err != nil { - return sesCertList{}, err + method := strings.TrimSpace(asn1String(fields[0])) + if method == "" { + return sesCertList{}, fmt.Errorf("invalid ses cert digest") } digest, err := asn1OctetString(fields[1]) if err != nil { diff --git a/ofdgo_sign_verify.go b/ofdgo_sign_verify.go index b2bdb55..bf3e8bc 100644 --- a/ofdgo_sign_verify.go +++ b/ofdgo_sign_verify.go @@ -18,6 +18,7 @@ import ( "bytes" "crypto" "crypto/ecdsa" + "crypto/md5" "crypto/rsa" "crypto/sha1" "crypto/sha256" @@ -37,35 +38,44 @@ import ( ) // SignatureVerifyReport 签名验证报告 -// Valid表示签名完整性及调用方指定的证书策略均通过 +// Valid表示签名完整性、签名时间语义及调用方指定的证书策略均通过 +// SealCertTimeOK仅提供制章证书在签名时间的状态信息, 不参与Valid判断 type SignatureVerifyReport struct { - ID string - BaseLoc string - Type SignType - Provider SignatureProvider - Signer string - SignCert SignatureCertInfo - SealCert SignatureCertInfo - SealType string - SignatureMethod string - SignatureDateTime string - DigestMethod string - References []SignatureReferenceVerify - Stamps []SignatureStamp - StampPositions []SignatureStampPosition - StampPositionError string - DigestOK bool - DataHashOK bool - SignedValueOK bool - SealOK bool - SealMatchOK bool - CertOK bool - CertTimeChecked bool - CertTimeOK bool - CertTrustChecked bool - CertTrustOK bool - Valid bool - Error string + ID string + BaseLoc string + Type SignType + Provider SignatureProvider + Signer string + SignCert SignatureCertInfo + SealCert SignatureCertInfo + SealInfo SignatureSealInfo + SealType string + SignatureMethod string + SignatureDateTime string + SignatureTime time.Time + DigestMethod string + References []SignatureReferenceVerify + Stamps []SignatureStamp + StampPositions []SignatureStampPosition + StampPositionError string + DigestOK bool + DataHashOK bool + SignedValueOK bool + SealOK bool + SealMatchOK bool + CertOK bool + SignatureTimeChecked bool + SignatureTimeOK bool + SealCertTimeChecked bool + SealCertTimeOK bool + SealTimeChecked bool + SealTimeOK bool + CertTimeChecked bool + CertTimeOK bool + CertTrustChecked bool + CertTrustOK bool + Valid bool + Error string } // IntegrityValid 判断签名完整性是否有效 @@ -75,13 +85,14 @@ func (report SignatureVerifyReport) IntegrityValid() bool { } // TrustedValid 判断签名是否可信有效 -// 返回: bool 签名完整性、证书信任及证书有效期是否均验证通过 +// 返回: bool 签名完整性、时间语义、证书信任及证书有效期是否均验证通过 func (report SignatureVerifyReport) TrustedValid() bool { - return report.IntegrityValid() && report.CertTrustChecked && report.CertTrustOK && report.CertTimeChecked && report.CertTimeOK + return report.IntegrityValid() && report.certificatePolicyOK() && report.CertTrustChecked && report.CertTimeChecked } // SignatureCertInfo 签名证书信息 type SignatureCertInfo struct { + Raw []byte Subject string CommonName string Organization string @@ -91,6 +102,18 @@ type SignatureCertInfo struct { NotAfter time.Time } +// SignatureSealInfo 电子印章信息 +type SignatureSealInfo struct { + Version int + ID string + VendorID string + Type int + Name string + CreateTime time.Time + ValidStart time.Time + ValidEnd time.Time +} + // SignatureReferenceVerify 签名保护文件验证结果 type SignatureReferenceVerify struct { FileRef string @@ -239,6 +262,9 @@ func (r *Reader) verifySignature(sigListPath string, sigRef Signature, options * Type: sigRef.Type, SealMatchOK: true, } + if report.Type == "" { + report.Type = SignTypeSeal + } sigData, err := r.readFileExact(sigPath) if err != nil { report.Error = err.Error() @@ -253,6 +279,9 @@ func (r *Reader) verifySignature(sigListPath string, sigRef Signature, options * report.SignatureMethod = sigFile.SignedInfo.SignatureMethod report.SignatureDateTime = sigFile.SignedInfo.SignatureDateTime report.DigestMethod = sigFile.SignedInfo.References.CheckMethod + if report.DigestMethod == "" { + report.DigestMethod = "MD5" + } report.References = r.verifySignatureReferences(sigPath, sigFile.SignedInfo.References) report.Stamps = append(report.Stamps, sigFile.SignedInfo.StampAnnot...) report.StampPositions, err = r.SignatureStampPositions(report.Stamps) @@ -266,7 +295,7 @@ func (r *Reader) verifySignature(sigListPath string, sigRef Signature, options * report.Error = err.Error() return report } - switch sigRef.Type { + switch report.Type { case SignTypeSign: result, err := verifyDigitalSignature(report.SignatureMethod, report.DigestMethod, signedValue, sigData, options) if err != nil { @@ -279,19 +308,23 @@ func (r *Reader) verifySignature(sigListPath string, sigRef Signature, options * 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) report.Valid = report.IntegrityValid() && report.certificatePolicyOK() return report - case "", SignTypeSeal: + case SignTypeSeal: default: - report.Error = fmt.Sprintf("unsupported signature type: %s", sigRef.Type) + report.Error = fmt.Sprintf("unsupported signature type: %s", report.Type) return report } sesResult, err := verifySESSignature(signedValue, sigData, options) if sesResult != nil { report.SignCert = sesResult.SignCert report.SealCert = sesResult.SealCert + report.SealInfo = sesResult.SealInfo report.SealType = sesResult.SealType + report.SignatureTime = sesResult.SignatureTime report.Signer = sesResult.SignCert.CommonName } if err != nil { @@ -302,6 +335,7 @@ func (r *Reader) verifySignature(sigListPath string, sigRef Signature, options * 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 != "" { sealPath := signatureRefPath(sigPath, sigFile.SignedInfo.Seal.BaseLoc) @@ -383,6 +417,10 @@ func (r *Reader) readFileExact(name string) ([]byte, error) { // 入参: method 摘要算法, data 原文数据 // 返回: []byte 摘要值, error 错误信息 func signatureDigest(method string, data []byte) ([]byte, error) { + if strings.TrimSpace(method) == "" { + sum := md5.Sum(data) + return sum[:], nil + } if isSM3DigestMethod(method) { return signSM3(data), nil } @@ -397,6 +435,8 @@ func signatureDigest(method string, data []byte) ([]byte, error) { // 返回: crypto.Hash 摘要算法, bool 是否支持 func signatureDigestHash(method string) (crypto.Hash, bool) { switch signatureMethodText(method) { + case "1.2.840.113549.2.5", "MD5": + return crypto.MD5, true case "1.3.14.3.2.26", "SHA1": return crypto.SHA1, true case "2.16.840.1.101.3.4.2.4", "SHA224": @@ -453,6 +493,9 @@ func signatureMethodHash(method, digestMethod string) (crypto.Hash, error) { // 返回: []byte 摘要值 func signatureHashBytes(h crypto.Hash, data []byte) []byte { switch h { + case crypto.MD5: + sum := md5.Sum(data) + return sum[:] case crypto.SHA1: sum := sha1.Sum(data) return sum[:] @@ -623,9 +666,38 @@ func (report *SignatureVerifyReport) applySignatureCertificatePolicy(options *si } } +// applySignatureTimePolicy 应用签名时间策略 +func (report *SignatureVerifyReport) applySignatureTimePolicy() { + if !report.SignatureTime.IsZero() && !report.SignCert.NotBefore.IsZero() && !report.SignCert.NotAfter.IsZero() { + report.SignatureTimeChecked = true + report.SignatureTimeOK = timeInRange(report.SignatureTime, report.SignCert.NotBefore, report.SignCert.NotAfter) + } + if !report.SignatureTime.IsZero() && !report.SealCert.NotBefore.IsZero() && !report.SealCert.NotAfter.IsZero() { + report.SealCertTimeChecked = true + report.SealCertTimeOK = timeInRange(report.SignatureTime, report.SealCert.NotBefore, report.SealCert.NotAfter) + } + if !report.SignatureTime.IsZero() && !report.SealInfo.ValidStart.IsZero() && !report.SealInfo.ValidEnd.IsZero() { + report.SealTimeChecked = true + report.SealTimeOK = timeInRange(report.SignatureTime, report.SealInfo.ValidStart, report.SealInfo.ValidEnd) + } +} + +// timeInRange 判断时间是否位于闭区间 +// 入参: t 待判断时间, start 起始时间, end 结束时间 +// 返回: bool 是否位于区间 +func timeInRange(t, start, end time.Time) bool { + return !start.After(end) && !t.Before(start) && !t.After(end) +} + // certificatePolicyOK 判断证书策略是否通过 // 返回: bool 是否通过 func (report SignatureVerifyReport) certificatePolicyOK() bool { + if report.SignatureTimeChecked && !report.SignatureTimeOK { + return false + } + if report.SealTimeChecked && !report.SealTimeOK { + return false + } if report.CertTimeChecked && !report.CertTimeOK { return false } @@ -790,6 +862,7 @@ func signatureCertInfo(data []byte) SignatureCertInfo { subject := certificateNameValues(cert.SubjectValue) issuer := certificateNameValues(cert.IssuerValue) info := SignatureCertInfo{ + Raw: append([]byte(nil), data...), Subject: certificateNameString(subject), CommonName: certificateNameFirst(subject, "2.5.4.3"), Organization: certificateNameFirst(subject, "2.5.4.10"), @@ -803,6 +876,23 @@ func signatureCertInfo(data []byte) SignatureCertInfo { return info } +// parseSignatureDateTime 解析带时区的签名时间 +// 入参: value 签名时间文本 +// 返回: time.Time 签名时间 +func parseSignatureDateTime(value string) time.Time { + value = strings.TrimSpace(value) + for _, layout := range []string{ + time.RFC3339Nano, + "20060102150405.999999999Z07:00", + "20060102150405Z07:00", + } { + if t, err := time.Parse(layout, value); err == nil { + return t + } + } + return time.Time{} +} + const ( signatureExtensionKeyUsage = "2.5.29.15" signatureExtensionBasicConstraints = "2.5.29.19"