mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-08-30 04:02:39 +08:00
+5
-12
@@ -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+"/")) {
|
||||
|
||||
+76
-10
@@ -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 处理后的印章图片对象
|
||||
|
||||
+35
-9
@@ -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,
|
||||
})
|
||||
|
||||
+132
-9
@@ -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
|
||||
|
||||
+130
-42
@@ -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 {
|
||||
|
||||
+123
-33
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user