forked from gokalkan/gokalkan
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpdf_verify.go
More file actions
128 lines (104 loc) · 3.92 KB
/
Copy pathpdf_verify.go
File metadata and controls
128 lines (104 loc) · 3.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package gokalkan
import (
"bytes"
"errors"
"fmt"
"time"
"github.com/digitorus/pdf"
)
// ErrNoPDFSignatures возвращается VerifyPDF, если в документе нет ни одной
// подписи (аналог NoSignaturesFoundException в NCANode).
var ErrNoPDFSignatures = errors.New("gokalkan: pdf document contains no digital signatures")
// PDFSignerInfo - информация об одной подписи в PDF документе.
type PDFSignerInfo struct {
Valid bool
// Reason/Location/ContactInfo - nil, если соответствующий ключ подписи
// отсутствует в PDF (не то же самое, что пустая строка - см. IsNull() у
// pdf.Value), указатель на "" - если ключ присутствует, но пуст.
Reason *string
Location *string
ContactInfo *string
SignDate time.Time
// VerifyInfo - человекочитаемый отчёт нативной проверки (см. Verify/
// VerifyDetached), пусто если Valid == false из-за ошибки более
// раннего этапа (например, битые /ByteRange).
VerifyInfo string
// SubFilter - значение /SubFilter словаря подписи (например
// "ETSI.CAdES.detached") - формат самой подписи на уровне PDF.
SubFilter string
// CMS - сырые байты подписи (значение /Contents, detached CMS) - для
// извлечения сертификата подписанта см. Client.GetCertFromCMS.
CMS []byte
}
// VerifyPDF проверяет все подписи в PDF документе (по /ByteRange и /Contents
// каждого поля подписи). Возвращает ErrNoPDFSignatures, если подписей нет.
func (cli *Client) VerifyPDF(pdfData []byte) (allValid bool, signers []PDFSignerInfo, err error) {
rdr, err := pdf.NewReader(bytes.NewReader(pdfData), int64(len(pdfData)))
if err != nil {
return false, nil, fmt.Errorf("gokalkan: read pdf: %w", err)
}
sigs := findAllSignatureValues(rdr)
if len(sigs) == 0 {
return false, nil, ErrNoPDFSignatures
}
allValid = true
for _, sig := range sigs {
info, verifyErr := cli.verifyOnePDFSignature(pdfData, sig.val)
if verifyErr != nil {
info.VerifyInfo = verifyErr.Error()
info.Valid = false
}
if !info.Valid {
allValid = false
}
signers = append(signers, info)
}
return allValid, signers, nil
}
func (cli *Client) verifyOnePDFSignature(pdfData []byte, v pdf.Value) (PDFSignerInfo, error) {
info := PDFSignerInfo{
Reason: textOrNil(v.Key("Reason")),
Location: textOrNil(v.Key("Location")),
ContactInfo: textOrNil(v.Key("ContactInfo")),
SubFilter: v.Key("SubFilter").Name(),
}
if m := v.Key("M"); !m.IsNull() {
if t, err := parsePDFDate(m.Text()); err == nil {
info.SignDate = t
}
}
byteRange, err := byteRangeOf(v)
if err != nil {
return info, err
}
b0, l0, b1, l1 := byteRange[0], byteRange[1], byteRange[2], byteRange[3]
if b0 < 0 || l0 < 0 || b1 < 0 || l1 < 0 || b0+l0 > int64(len(pdfData)) || b1+l1 > int64(len(pdfData)) {
return info, fmt.Errorf("gokalkan: byte range out of bounds")
}
content := make([]byte, 0, l0+l1)
content = append(content, pdfData[b0:b0+l0]...)
content = append(content, pdfData[b1:b1+l1]...)
cms := []byte(v.Key("Contents").RawString())
if len(cms) == 0 {
return info, fmt.Errorf("gokalkan: empty signature contents")
}
info.CMS = cms
verifyInfo, err := cli.VerifyDetached(cms, content)
if err != nil {
return info, err
}
info.Valid = true
info.VerifyInfo = verifyInfo
return info, nil
}
func textOrNil(v pdf.Value) *string {
if v.IsNull() {
return nil
}
text := v.Text()
return &text
}
// parsePDFDate парсит формат даты подписи PDF: (D:YYYYMMDDHHmmSSOHH'mm').
func parsePDFDate(v string) (time.Time, error) {
return time.Parse("D:20060102150405Z07'00'", v)
}