mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-30 13:48:51 +08:00
Merge pull request #334 from unidoc/v3-a5i-pdf-sign
WIP: Initial digital signature support (v3)
This commit is contained in:
commit
c8b70efef3
@ -293,6 +293,9 @@ func (str *PdfObjectString) Str() string {
|
||||
// UTF-16BE is applied when the first two bytes are 0xFE, 0XFF, otherwise decoding of
|
||||
// PDFDocEncoding is performed.
|
||||
func (str *PdfObjectString) Decoded() string {
|
||||
if str == nil {
|
||||
return ""
|
||||
}
|
||||
b := []byte(str.val)
|
||||
if len(b) >= 2 && b[0] == 0xFE && b[1] == 0xFF {
|
||||
// UTF16BE.
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
"github.com/unidoc/unidoc/pdf/core"
|
||||
)
|
||||
|
||||
// PdfAppender appends a new Pdf content to an existing Pdf document.
|
||||
// PdfAppender appends new PDF content to an existing PDF document via incremental updates.
|
||||
type PdfAppender struct {
|
||||
rs io.ReadSeeker
|
||||
parser *core.PdfParser
|
||||
@ -30,8 +30,11 @@ type PdfAppender struct {
|
||||
xrefs core.XrefTable
|
||||
greatestObjNum int
|
||||
|
||||
// List of new objects and a map for quick lookups.
|
||||
newObjects []core.PdfObject
|
||||
hasNewObject map[core.PdfObject]struct{}
|
||||
|
||||
written bool
|
||||
}
|
||||
|
||||
func getPageResources(p *PdfPage) map[core.PdfObjectName]core.PdfObject {
|
||||
@ -94,18 +97,24 @@ func getPageResources(p *PdfPage) map[core.PdfObjectName]core.PdfObject {
|
||||
|
||||
// NewPdfAppender creates a new Pdf appender from a Pdf reader.
|
||||
func NewPdfAppender(reader *PdfReader) (*PdfAppender, error) {
|
||||
a := &PdfAppender{}
|
||||
a.rs = reader.rs
|
||||
a.Reader = reader
|
||||
a.parser = a.Reader.parser
|
||||
a := &PdfAppender{
|
||||
rs: reader.rs,
|
||||
Reader: reader,
|
||||
parser: reader.parser,
|
||||
}
|
||||
if _, err := a.rs.Seek(0, io.SeekStart); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var err error
|
||||
// Create a readonly (immutable) reader. It increases memory using.
|
||||
// Why? We can not check the original reader objects are changed or not.
|
||||
// When we merge, replace a page content. The new page will contain objects from the readonly reader and other objects.
|
||||
// The readonly objects won't append to the result Pdf file. This check is not resource demanding. It checks indirect objects owners only.
|
||||
|
||||
// Create a readonly (immutable) reader. It increases memory use but is necessary to be able
|
||||
// to detect changes in the original reader objects.
|
||||
//
|
||||
// In the case where an existing page is modified, the page contents are replaced upon merging
|
||||
// (appending). The new page will refer to objects from the read-only reader and new instances
|
||||
// of objects that have been changes. Objects from the original reader are not appended, only
|
||||
// new objects that modify the PDF. The change detection check is not resource demanding. It
|
||||
// only checks owners (source) of indirect objects.
|
||||
a.roReader, err = NewPdfReader(a.rs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -131,8 +140,8 @@ func (a *PdfAppender) addNewObjects(obj core.PdfObject) {
|
||||
}
|
||||
switch v := obj.(type) {
|
||||
case *core.PdfIndirectObject:
|
||||
// Check the object is changing.
|
||||
// If the indirect object has not the readonly parser then the object is changed.
|
||||
// If the current parser is different from the read-only parser, then
|
||||
// the object has changed.
|
||||
if v.GetParser() != a.roReader.parser {
|
||||
a.newObjects = append(a.newObjects, obj)
|
||||
a.hasNewObject[obj] = struct{}{}
|
||||
@ -147,22 +156,25 @@ func (a *PdfAppender) addNewObjects(obj core.PdfObject) {
|
||||
a.addNewObjects(v.Get(key))
|
||||
}
|
||||
case *core.PdfObjectStreams:
|
||||
// Check the object is changing.
|
||||
// If the indirect object has not the readonly parser then the object is changed.
|
||||
// If the current parser is different from the read-only parser, then
|
||||
// the object has changed.
|
||||
if v.GetParser() != a.roReader.parser {
|
||||
for _, o := range v.Elements() {
|
||||
a.addNewObjects(o)
|
||||
}
|
||||
}
|
||||
case *core.PdfObjectStream:
|
||||
// Check the object is changing.
|
||||
// If the indirect object has the readonly parser then the object is not changed.
|
||||
if v.GetParser() == a.roReader.parser {
|
||||
// If the current parser is different from the read-only parser, then
|
||||
// the object has changed.
|
||||
parser := v.GetParser()
|
||||
if parser == a.roReader.parser {
|
||||
return
|
||||
}
|
||||
// If the indirect object has not the origin parser then the object may be changed orr not.
|
||||
if v.GetParser() == a.Reader.parser {
|
||||
// Check data is not changed.
|
||||
|
||||
// If the current parser is different from the parser of the reader,
|
||||
// then the object may have changed.
|
||||
if parser == a.Reader.parser {
|
||||
// Check if data has changed.
|
||||
if streamObj, err := a.roReader.parser.LookupByReference(v.PdfObjectReference); err == nil {
|
||||
var isNotChanged bool
|
||||
if stream, ok := core.GetStream(streamObj); ok && bytes.Equal(stream.Stream, v.Stream) {
|
||||
@ -331,7 +343,7 @@ func (a *PdfAppender) MergePageWith(pageNum int, page *PdfPage) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPages adds pages to end of the source Pdf.
|
||||
// AddPages adds pages to be appended to the end of the source PDF.
|
||||
func (a *PdfAppender) AddPages(pages ...*PdfPage) {
|
||||
for _, page := range pages {
|
||||
page = page.Duplicate()
|
||||
@ -370,19 +382,74 @@ func (a *PdfAppender) ReplacePage(pageNum int, page *PdfPage) {
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceAcroForm replaces the acrobat form. It appends a new form to the Pdf which replaces the original acrobat form.
|
||||
// Sign signs a specific page with a digital signature.
|
||||
// The signature field parameter must have a valid signature dictionary
|
||||
// specified by its V field.
|
||||
func (a *PdfAppender) Sign(pageNum int, field *PdfFieldSignature) error {
|
||||
if field == nil {
|
||||
return errors.New("signature field cannot be nil")
|
||||
}
|
||||
|
||||
signature := field.V
|
||||
if signature == nil {
|
||||
return errors.New("signature dictionary cannot be nil")
|
||||
}
|
||||
|
||||
// Get a copy of the selected page.
|
||||
pageIndex := pageNum - 1
|
||||
if pageIndex < 0 || pageIndex > len(a.pages)-1 {
|
||||
return fmt.Errorf("page %d not found", pageNum)
|
||||
}
|
||||
page := a.pages[pageIndex].Duplicate()
|
||||
|
||||
// Initialize signature.
|
||||
if err := signature.Initialize(); err != nil {
|
||||
return err
|
||||
}
|
||||
a.addNewObjects(signature.container)
|
||||
|
||||
// Add signature field annotations to the page annotations.
|
||||
for _, annotation := range field.Annotations {
|
||||
annotation.P = page.ToPdfObject()
|
||||
page.Annotations = append(page.Annotations, annotation.PdfAnnotation)
|
||||
}
|
||||
|
||||
// Add signature field to the form.
|
||||
acroForm := a.Reader.AcroForm
|
||||
if acroForm == nil {
|
||||
acroForm = NewPdfAcroForm()
|
||||
}
|
||||
|
||||
acroForm.SigFlags = core.MakeInteger(3)
|
||||
acroForm.DA = core.MakeString("/F1 0 Tf 0 g")
|
||||
n2ResourcesFont := core.MakeDict()
|
||||
n2ResourcesFont.Set("F1", DefaultFont().ToPdfObject())
|
||||
acroForm.DR = NewPdfPageResources()
|
||||
acroForm.DR.Font = n2ResourcesFont
|
||||
|
||||
fields := append(acroForm.AllFields(), field.PdfField)
|
||||
|
||||
acroForm.Fields = &fields
|
||||
a.ReplaceAcroForm(acroForm)
|
||||
|
||||
// Replace original page.
|
||||
procPage(page)
|
||||
a.pages[pageIndex] = page
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReplaceAcroForm replaces the acrobat form. It appends a new form to the Pdf which
|
||||
// replaces the original AcroForm.
|
||||
func (a *PdfAppender) ReplaceAcroForm(acroForm *PdfAcroForm) {
|
||||
a.acroForm = acroForm
|
||||
}
|
||||
|
||||
// Write writes the Appender output to io.Writer.
|
||||
// It can only be called once and further invocations will result in an error.
|
||||
func (a *PdfAppender) Write(w io.Writer) error {
|
||||
if _, err := a.rs.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
offset, err := io.Copy(w, a.rs)
|
||||
if err != nil {
|
||||
return err
|
||||
if a.written {
|
||||
return errors.New("appender write can only be invoked once")
|
||||
}
|
||||
|
||||
writer := NewPdfWriter()
|
||||
@ -446,7 +513,7 @@ func (a *PdfAppender) Write(w io.Writer) error {
|
||||
common.Log.Trace("Page Parent: %T", parent)
|
||||
parentDict, ok := parent.PdfObject.(*core.PdfObjectDictionary)
|
||||
if !ok {
|
||||
return errors.New("Invalid Parent object")
|
||||
return errors.New("invalid Parent object")
|
||||
}
|
||||
for _, field := range inheritedFields {
|
||||
common.Log.Trace("Field %s", field)
|
||||
@ -473,6 +540,56 @@ func (a *PdfAppender) Write(w io.Writer) error {
|
||||
writer.SetForms(a.acroForm)
|
||||
}
|
||||
|
||||
if _, err := a.rs.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Digital signature handling: Check if any of the new objects represent a signature dictionary.
|
||||
// The byte range is later updated dynamically based on the position of the actual signature
|
||||
// Contents.
|
||||
digestWriters := make(map[SignatureHandler]io.Writer)
|
||||
byteRange := core.MakeArray()
|
||||
for _, obj := range a.newObjects {
|
||||
if ind, found := core.GetIndirect(obj); found {
|
||||
if sigDict, found := ind.PdfObject.(*pdfSignDictionary); found {
|
||||
handler := *sigDict.handler
|
||||
var err error
|
||||
digestWriters[handler], err = handler.NewDigest(sigDict.signature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
byteRange.Append(core.MakeInteger(0xfffff), core.MakeInteger(0xfffff))
|
||||
}
|
||||
}
|
||||
}
|
||||
if byteRange.Len() > 0 {
|
||||
byteRange.Append(core.MakeInteger(0xfffff), core.MakeInteger(0xfffff))
|
||||
}
|
||||
for _, obj := range a.newObjects {
|
||||
if ind, found := core.GetIndirect(obj); found {
|
||||
if sigDict, found := ind.PdfObject.(*pdfSignDictionary); found {
|
||||
sigDict.Set("ByteRange", byteRange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hasSigDict := len(digestWriters) > 0
|
||||
|
||||
var reader io.Reader = a.rs
|
||||
if hasSigDict {
|
||||
writers := make([]io.Writer, 0, len(digestWriters))
|
||||
for _, hash := range digestWriters {
|
||||
writers = append(writers, hash)
|
||||
}
|
||||
reader = io.TeeReader(a.rs, io.MultiWriter(writers...))
|
||||
}
|
||||
|
||||
// Write the original PDF.
|
||||
offset, err := io.Copy(w, reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(a.newObjects) == 0 {
|
||||
return nil
|
||||
}
|
||||
@ -481,14 +598,108 @@ func (a *PdfAppender) Write(w io.Writer) error {
|
||||
writer.ObjNumOffset = a.greatestObjNum
|
||||
writer.appendMode = true
|
||||
writer.appendToXrefs = a.xrefs
|
||||
writer.minorVersion = 7
|
||||
|
||||
for _, obj := range a.newObjects {
|
||||
writer.addObject(obj)
|
||||
}
|
||||
if err := writer.Write(w); err != nil {
|
||||
|
||||
writerW := w
|
||||
if hasSigDict {
|
||||
// For signatures, we need to write twice. First to find the byte offset
|
||||
// of the Contents and then dynamically update the file with the
|
||||
// signature and ByteRange.
|
||||
writerW = bytes.NewBuffer(nil)
|
||||
}
|
||||
|
||||
// Perform the write. For signatures will do a mock write to a buffer.
|
||||
if err := writer.Write(writerW); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
|
||||
// TODO(gunnsth): Consider whether the dynamic content can be handled efficiently with generic write hooks?
|
||||
// Logic is getting pretty complex here.
|
||||
if hasSigDict {
|
||||
// Update the byteRanges based on mock write.
|
||||
bufferData := writerW.(*bytes.Buffer).Bytes()
|
||||
byteRange := core.MakeArray()
|
||||
var sigDicts []*pdfSignDictionary
|
||||
var lastPosition int64
|
||||
for _, obj := range writer.objects {
|
||||
if ind, found := core.GetIndirect(obj); found {
|
||||
if sigDict, found := ind.PdfObject.(*pdfSignDictionary); found {
|
||||
sigDicts = append(sigDicts, sigDict)
|
||||
newPosition := sigDict.fileOffset + int64(sigDict.contentsOffsetStart)
|
||||
byteRange.Append(
|
||||
core.MakeInteger(lastPosition),
|
||||
core.MakeInteger(newPosition-lastPosition),
|
||||
)
|
||||
lastPosition = sigDict.fileOffset + int64(sigDict.contentsOffsetEnd)
|
||||
}
|
||||
}
|
||||
}
|
||||
byteRange.Append(
|
||||
core.MakeInteger(lastPosition),
|
||||
core.MakeInteger(offset+int64(len(bufferData))-lastPosition),
|
||||
)
|
||||
// set the ByteRange value
|
||||
byteRangeData := []byte(byteRange.WriteString())
|
||||
for _, sigDict := range sigDicts {
|
||||
bufferOffset := int(sigDict.fileOffset - offset)
|
||||
for i := sigDict.byteRangeOffsetStart; i < sigDict.byteRangeOffsetEnd; i++ {
|
||||
bufferData[bufferOffset+i] = ' '
|
||||
}
|
||||
dst := bufferData[bufferOffset+sigDict.byteRangeOffsetStart : bufferOffset+sigDict.byteRangeOffsetEnd]
|
||||
copy(dst, byteRangeData)
|
||||
}
|
||||
var prevOffset int
|
||||
for _, sigDict := range sigDicts {
|
||||
bufferOffset := int(sigDict.fileOffset - offset)
|
||||
data := bufferData[prevOffset : bufferOffset+sigDict.contentsOffsetStart]
|
||||
handler := *sigDict.handler
|
||||
digestWriters[handler].Write(data)
|
||||
prevOffset = bufferOffset + sigDict.contentsOffsetEnd
|
||||
}
|
||||
for _, sigDict := range sigDicts {
|
||||
data := bufferData[prevOffset:]
|
||||
handler := *sigDict.handler
|
||||
digestWriters[handler].Write(data)
|
||||
}
|
||||
for _, sigDict := range sigDicts {
|
||||
bufferOffset := int(sigDict.fileOffset - offset)
|
||||
handler := *sigDict.handler
|
||||
digest := digestWriters[handler]
|
||||
if err := handler.Sign(sigDict.signature, digest); err != nil {
|
||||
return err
|
||||
}
|
||||
contents := []byte(sigDict.signature.Contents.WriteString())
|
||||
|
||||
// Empty out the ByteRange and Content data.
|
||||
// FIXME(gunnsth): Is this needed? Seems like the correct data is copied below? Prefer
|
||||
// to keep the rest space?
|
||||
for i := sigDict.byteRangeOffsetStart; i < sigDict.byteRangeOffsetEnd; i++ {
|
||||
bufferData[bufferOffset+i] = ' '
|
||||
}
|
||||
for i := sigDict.contentsOffsetStart; i < sigDict.contentsOffsetEnd; i++ {
|
||||
bufferData[bufferOffset+i] = ' '
|
||||
}
|
||||
|
||||
// Copy the actual ByteRange and Contents data into the buffer prepared by first write.
|
||||
dst := bufferData[bufferOffset+sigDict.byteRangeOffsetStart : bufferOffset+sigDict.byteRangeOffsetEnd]
|
||||
copy(dst, byteRangeData)
|
||||
dst = bufferData[bufferOffset+sigDict.contentsOffsetStart : bufferOffset+sigDict.contentsOffsetEnd]
|
||||
copy(dst, contents)
|
||||
}
|
||||
|
||||
buffer := bytes.NewBuffer(bufferData)
|
||||
_, err = io.Copy(w, buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
a.written = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteToFile writes the Appender output to file specified by path.
|
||||
|
@ -3,20 +3,29 @@
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package model
|
||||
package model_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/pkcs12"
|
||||
|
||||
"github.com/unidoc/unidoc/common"
|
||||
"github.com/unidoc/unidoc/pdf/core"
|
||||
"github.com/unidoc/unidoc/pdf/model"
|
||||
"github.com/unidoc/unidoc/pdf/model/sighandler"
|
||||
)
|
||||
|
||||
// This test file contains multiple tests to generate PDFs from existing Pdf files. The outputs are written into /tmp as files. The files
|
||||
// themselves need to be observed to check for correctness as we don't have a good way to automatically check
|
||||
// if every detail is correct.
|
||||
// This test file contains multiple tests to generate PDFs from existing Pdf files. The outputs are written
|
||||
// into TMPDIR as files. The files themselves need to be observed to check for correctness as we don't have
|
||||
// a good way to automatically check if every detail is correct.
|
||||
|
||||
func init() {
|
||||
common.SetLogger(common.NewConsoleLogger(common.LogLevelDebug))
|
||||
@ -32,6 +41,11 @@ const imgPdfFile2 = "./testdata/img1-2.pdf"
|
||||
// source http://foersom.com/net/HowTo/data/OoPdfFormExample.pdf
|
||||
const testPdfAcroFormFile1 = "./testdata/OoPdfFormExample.pdf"
|
||||
|
||||
const testPdfSignedPDFDocument = "./testdata/SampleSignedPDFDocument.pdf"
|
||||
|
||||
const testPKS12Key = "./testdata/ks12"
|
||||
const testPKS12KeyPassword = "password"
|
||||
|
||||
func tempFile(name string) string {
|
||||
return filepath.Join(os.TempDir(), name)
|
||||
}
|
||||
@ -43,7 +57,7 @@ func TestAppenderAddPage(t *testing.T) {
|
||||
return
|
||||
}
|
||||
defer f1.Close()
|
||||
pdf1, err := NewPdfReader(f1)
|
||||
pdf1, err := model.NewPdfReader(f1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
@ -54,13 +68,13 @@ func TestAppenderAddPage(t *testing.T) {
|
||||
return
|
||||
}
|
||||
defer f2.Close()
|
||||
pdf2, err := NewPdfReader(f2)
|
||||
pdf2, err := model.NewPdfReader(f2)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
appender, err := NewPdfAppender(pdf1)
|
||||
appender, err := model.NewPdfAppender(pdf1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
@ -85,7 +99,7 @@ func TestAppenderAddPage2(t *testing.T) {
|
||||
return
|
||||
}
|
||||
defer f1.Close()
|
||||
pdf1, err := NewPdfReader(f1)
|
||||
pdf1, err := model.NewPdfReader(f1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
@ -96,13 +110,13 @@ func TestAppenderAddPage2(t *testing.T) {
|
||||
return
|
||||
}
|
||||
defer f2.Close()
|
||||
pdf2, err := NewPdfReader(f2)
|
||||
pdf2, err := model.NewPdfReader(f2)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
appender, err := NewPdfAppender(pdf1)
|
||||
appender, err := model.NewPdfAppender(pdf1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
@ -126,13 +140,13 @@ func TestAppenderRemovePage(t *testing.T) {
|
||||
return
|
||||
}
|
||||
defer f1.Close()
|
||||
pdf1, err := NewPdfReader(f1)
|
||||
pdf1, err := model.NewPdfReader(f1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
appender, err := NewPdfAppender(pdf1)
|
||||
appender, err := model.NewPdfAppender(pdf1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
@ -155,7 +169,7 @@ func TestAppenderReplacePage(t *testing.T) {
|
||||
return
|
||||
}
|
||||
defer f1.Close()
|
||||
pdf1, err := NewPdfReader(f1)
|
||||
pdf1, err := model.NewPdfReader(f1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
@ -167,13 +181,13 @@ func TestAppenderReplacePage(t *testing.T) {
|
||||
return
|
||||
}
|
||||
defer f2.Close()
|
||||
pdf2, err := NewPdfReader(f2)
|
||||
pdf2, err := model.NewPdfReader(f2)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
appender, err := NewPdfAppender(pdf1)
|
||||
appender, err := model.NewPdfAppender(pdf1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
@ -196,21 +210,21 @@ func TestAppenderAddAnnotation(t *testing.T) {
|
||||
return
|
||||
}
|
||||
defer f1.Close()
|
||||
pdf1, err := NewPdfReader(f1)
|
||||
pdf1, err := model.NewPdfReader(f1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
appender, err := NewPdfAppender(pdf1)
|
||||
appender, err := model.NewPdfAppender(pdf1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
page := pdf1.PageList[0]
|
||||
annotation := NewPdfAnnotationSquare()
|
||||
rect := PdfRectangle{Ury: 250.0, Urx: 150.0, Lly: 50.0, Llx: 50.0}
|
||||
annotation := model.NewPdfAnnotationSquare()
|
||||
rect := model.PdfRectangle{Ury: 250.0, Urx: 150.0, Lly: 50.0, Llx: 50.0}
|
||||
annotation.Rect = rect.ToPdfObject()
|
||||
annotation.IC = core.MakeArrayFromFloats([]float64{4.0, 0.0, 0.3})
|
||||
annotation.CA = core.MakeFloat(0.5)
|
||||
@ -233,7 +247,7 @@ func TestAppenderMergePage(t *testing.T) {
|
||||
return
|
||||
}
|
||||
defer f1.Close()
|
||||
pdf1, err := NewPdfReader(f1)
|
||||
pdf1, err := model.NewPdfReader(f1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
@ -245,13 +259,13 @@ func TestAppenderMergePage(t *testing.T) {
|
||||
return
|
||||
}
|
||||
defer f2.Close()
|
||||
pdf2, err := NewPdfReader(f2)
|
||||
pdf2, err := model.NewPdfReader(f2)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
appender, err := NewPdfAppender(pdf1)
|
||||
appender, err := model.NewPdfAppender(pdf1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
@ -278,7 +292,7 @@ func TestAppenderMergePage2(t *testing.T) {
|
||||
}
|
||||
defer f1.Close()
|
||||
|
||||
pdf1, err := NewPdfReader(f1)
|
||||
pdf1, err := model.NewPdfReader(f1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
@ -291,13 +305,13 @@ func TestAppenderMergePage2(t *testing.T) {
|
||||
}
|
||||
defer f2.Close()
|
||||
|
||||
pdf2, err := NewPdfReader(f2)
|
||||
pdf2, err := model.NewPdfReader(f2)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
appender, err := NewPdfAppender(pdf1)
|
||||
appender, err := model.NewPdfAppender(pdf1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
@ -325,7 +339,7 @@ func TestAppenderMergePage3(t *testing.T) {
|
||||
return
|
||||
}
|
||||
defer f1.Close()
|
||||
pdf1, err := NewPdfReader(f1)
|
||||
pdf1, err := model.NewPdfReader(f1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
@ -338,13 +352,13 @@ func TestAppenderMergePage3(t *testing.T) {
|
||||
}
|
||||
defer f2.Close()
|
||||
|
||||
pdf2, err := NewPdfReader(f2)
|
||||
pdf2, err := model.NewPdfReader(f2)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
appender, err := NewPdfAppender(pdf1)
|
||||
appender, err := model.NewPdfAppender(pdf1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
@ -362,3 +376,249 @@ func TestAppenderMergePage3(t *testing.T) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func validateFile(t *testing.T, fileName string) {
|
||||
data, err := ioutil.ReadFile(fileName)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
reader, err := model.NewPdfReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
handler, _ := sighandler.NewAdobeX509RSASHA1(nil, nil)
|
||||
handler2, _ := sighandler.NewAdobePKCS7Detached(nil, nil)
|
||||
handlers := []model.SignatureHandler{handler, handler2}
|
||||
|
||||
res, err := reader.ValidateSignatures(handlers)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
if len(res) == 0 {
|
||||
t.Errorf("Fail: signature fields not found")
|
||||
return
|
||||
}
|
||||
|
||||
if !res[0].IsSigned || !res[0].IsVerified {
|
||||
t.Errorf("Fail: validation failed")
|
||||
return
|
||||
}
|
||||
|
||||
for i, item := range res {
|
||||
t.Logf("== Signature %d", i+1)
|
||||
t.Logf("%s", item.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppenderSignPage4(t *testing.T) {
|
||||
// TODO move to reader_test.go
|
||||
validateFile(t, testPdfSignedPDFDocument)
|
||||
|
||||
f1, err := os.Open(testPdfFile1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer f1.Close()
|
||||
pdf1, err := model.NewPdfReader(f1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
appender, err := model.NewPdfAppender(pdf1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
f, _ := ioutil.ReadFile(testPKS12Key)
|
||||
privateKey, cert, err := pkcs12.Decode(f, testPKS12KeyPassword)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
handler, err := sighandler.NewAdobePKCS7Detached(privateKey.(*rsa.PrivateKey), cert)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create signature field and appearance.
|
||||
signature := model.NewPdfSignature(handler)
|
||||
signature.SetName("Test Appender")
|
||||
signature.SetReason("TestAppenderSignPage4")
|
||||
signature.SetDate(time.Now(), "")
|
||||
|
||||
sigField := model.NewPdfFieldSignature(signature)
|
||||
sigField.T = core.MakeString("Signature1")
|
||||
|
||||
widget := model.NewPdfAnnotationWidget()
|
||||
widget.F = core.MakeInteger(132)
|
||||
widget.Rect = core.MakeArray(
|
||||
core.MakeInteger(0),
|
||||
core.MakeInteger(0),
|
||||
core.MakeInteger(0),
|
||||
core.MakeInteger(0),
|
||||
)
|
||||
widget.Parent = sigField.GetContainingPdfObject()
|
||||
|
||||
sigField.Annotations = append(sigField.Annotations, widget)
|
||||
|
||||
if err = appender.Sign(1, sigField); err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = appender.WriteToFile(tempFile("appender_sign_page_4.pdf"))
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
validateFile(t, tempFile("appender_sign_page_4.pdf"))
|
||||
}
|
||||
|
||||
func TestAppenderSignMultiple(t *testing.T) {
|
||||
inputPath := testPdfFile1
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
f, err := os.Open(inputPath)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
pdfReader, err := model.NewPdfReader(f)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
f.Close()
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("Fields: %d", len(pdfReader.AcroForm.AllFields()))
|
||||
|
||||
if len(pdfReader.AcroForm.AllFields()) != i {
|
||||
t.Fatalf("fields != %d (got %d)", i, len(pdfReader.AcroForm.AllFields()))
|
||||
}
|
||||
|
||||
t.Logf("Annotations: %d", len(pdfReader.PageList[0].Annotations))
|
||||
if len(pdfReader.PageList[0].Annotations) != i {
|
||||
t.Fatalf("page annotations != %d (got %d)", i, len(pdfReader.PageList[0].Annotations))
|
||||
}
|
||||
|
||||
appender, err := model.NewPdfAppender(pdfReader)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
f.Close()
|
||||
return
|
||||
}
|
||||
|
||||
pfxData, _ := ioutil.ReadFile(testPKS12Key)
|
||||
privateKey, cert, err := pkcs12.Decode(pfxData, testPKS12KeyPassword)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
f.Close()
|
||||
return
|
||||
}
|
||||
|
||||
handler, err := sighandler.NewAdobePKCS7Detached(privateKey.(*rsa.PrivateKey), cert)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
f.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// Create signature field and appearance.
|
||||
signature := model.NewPdfSignature(handler)
|
||||
signature.SetName(fmt.Sprintf("Test Appender - Round %d", i+1))
|
||||
signature.SetReason("TestAppenderSignPage4")
|
||||
signature.SetDate(time.Now(), "")
|
||||
|
||||
sigField := model.NewPdfFieldSignature(signature)
|
||||
sigField.T = core.MakeString("Signature1")
|
||||
|
||||
widget := model.NewPdfAnnotationWidget()
|
||||
widget.F = core.MakeInteger(132)
|
||||
widget.Rect = core.MakeArray(
|
||||
core.MakeInteger(0),
|
||||
core.MakeInteger(0),
|
||||
core.MakeInteger(0),
|
||||
core.MakeInteger(0),
|
||||
)
|
||||
widget.Parent = sigField.GetContainingPdfObject()
|
||||
|
||||
sigField.Annotations = append(sigField.Annotations, widget)
|
||||
|
||||
if err = appender.Sign(1, sigField); err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
f.Close()
|
||||
return
|
||||
}
|
||||
|
||||
outPath := tempFile(fmt.Sprintf("appender_sign_multiple_%d.pdf", i+1))
|
||||
|
||||
err = appender.WriteToFile(outPath)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
f.Close()
|
||||
return
|
||||
}
|
||||
|
||||
validateFile(t, outPath)
|
||||
inputPath = outPath
|
||||
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Each Appender can only be written out once, further invokations of Write should result in an error.
|
||||
func TestAppenderAttemptMultiWrite(t *testing.T) {
|
||||
f1, err := os.Open(testPdfLoremIpsumFile)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer f1.Close()
|
||||
pdf1, err := model.NewPdfReader(f1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
f2, err := os.Open(testPdfFile1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer f2.Close()
|
||||
pdf2, err := model.NewPdfReader(f2)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
appender, err := model.NewPdfAppender(pdf1)
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
appender.AddPages(pdf1.PageList...)
|
||||
appender.AddPages(pdf2.PageList...)
|
||||
appender.AddPages(pdf2.PageList...)
|
||||
|
||||
// Write twice to buffer and compare results.
|
||||
var buf1, buf2 bytes.Buffer
|
||||
err = appender.Write(&buf1)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %v", err)
|
||||
}
|
||||
err = appender.Write(&buf2)
|
||||
if err == nil {
|
||||
t.Fatalf("Second invokation of appender.Write should yield an error")
|
||||
}
|
||||
}
|
||||
|
@ -245,50 +245,37 @@ func (f *PdfField) String() string {
|
||||
}
|
||||
|
||||
// ToPdfObject sets the common field elements.
|
||||
// Note: Call the more field context's ToPdfObject to set both the generic and non-generic information.
|
||||
// Note: Call the more field context's ToPdfObject to set both the generic and
|
||||
// non-generic information.
|
||||
func (f *PdfField) ToPdfObject() core.PdfObject {
|
||||
container := f.container
|
||||
d := container.PdfObject.(*core.PdfObjectDictionary)
|
||||
|
||||
d.SetIfNotNil("FT", f.FT)
|
||||
if f.Parent != nil {
|
||||
d.Set("Parent", f.Parent.GetContainingPdfObject())
|
||||
// Create an array of the kids (fields or widgets).
|
||||
kids := core.MakeArray()
|
||||
for _, child := range f.Kids {
|
||||
kids.Append(child.ToPdfObject())
|
||||
}
|
||||
for _, annot := range f.Annotations {
|
||||
kids.Append(annot.GetContext().ToPdfObject())
|
||||
}
|
||||
|
||||
if f.Kids != nil {
|
||||
// Create an array of the kids (fields or widgets).
|
||||
kids := core.MakeArray()
|
||||
for _, child := range f.Kids {
|
||||
kids.Append(child.ToPdfObject())
|
||||
}
|
||||
// Set fields.
|
||||
if f.Parent != nil {
|
||||
d.SetIfNotNil("Parent", f.Parent.GetContainingPdfObject())
|
||||
}
|
||||
if kids.Len() > 0 {
|
||||
d.Set("Kids", kids)
|
||||
}
|
||||
|
||||
if f.Annotations != nil {
|
||||
_, hasKids := d.Get("Kids").(*core.PdfObjectArray)
|
||||
if !hasKids {
|
||||
d.Set("Kids", &core.PdfObjectArray{})
|
||||
}
|
||||
// TODO: If only 1 widget annotation, it can be merged in.
|
||||
kids := d.Get("Kids").(*core.PdfObjectArray)
|
||||
for _, annot := range f.Annotations {
|
||||
kids.Append(annot.GetContext().ToPdfObject())
|
||||
}
|
||||
}
|
||||
|
||||
d.SetIfNotNil("FT", f.FT)
|
||||
d.SetIfNotNil("T", f.T)
|
||||
d.SetIfNotNil("TU", f.TU)
|
||||
d.SetIfNotNil("TM", f.TM)
|
||||
d.SetIfNotNil("Ff", f.Ff)
|
||||
if f.V != nil {
|
||||
d.Set("V", f.V)
|
||||
}
|
||||
if f.DV != nil {
|
||||
d.Set("DV", f.DV)
|
||||
}
|
||||
if f.AA != nil {
|
||||
d.Set("AA", f.AA)
|
||||
}
|
||||
d.SetIfNotNil("V", f.V)
|
||||
d.SetIfNotNil("DV", f.DV)
|
||||
d.SetIfNotNil("AA", f.AA)
|
||||
|
||||
return container
|
||||
}
|
||||
@ -446,28 +433,38 @@ func (ch *PdfFieldChoice) ToPdfObject() core.PdfObject {
|
||||
// the name of the signer and verifying document contents.
|
||||
type PdfFieldSignature struct {
|
||||
*PdfField
|
||||
|
||||
V *PdfSignature
|
||||
Lock *core.PdfIndirectObject
|
||||
SV *core.PdfIndirectObject
|
||||
}
|
||||
|
||||
// NewPdfFieldSignature returns an initialized signature field.
|
||||
func NewPdfFieldSignature(signature *PdfSignature) *PdfFieldSignature {
|
||||
field := &PdfFieldSignature{
|
||||
PdfField: NewPdfField(),
|
||||
V: signature,
|
||||
}
|
||||
field.PdfField.SetContext(field)
|
||||
field.FT = core.MakeName("Sig")
|
||||
|
||||
return field
|
||||
}
|
||||
|
||||
// ToPdfObject returns an indirect object containing the signature field dictionary.
|
||||
func (sig *PdfFieldSignature) ToPdfObject() core.PdfObject {
|
||||
// Set general field attributes
|
||||
sig.PdfField.ToPdfObject()
|
||||
container := sig.container
|
||||
|
||||
// Handle signature field specific attributes
|
||||
container := sig.container
|
||||
|
||||
d := container.PdfObject.(*core.PdfObjectDictionary)
|
||||
d.Set("FT", core.MakeName("Sig"))
|
||||
d.SetIfNotNil("FT", core.MakeName("Sig"))
|
||||
d.SetIfNotNil("Lock", sig.Lock)
|
||||
d.SetIfNotNil("SV", sig.SV)
|
||||
if sig.V != nil {
|
||||
d.Set("V", sig.V.ToPdfObject())
|
||||
}
|
||||
if sig.Lock != nil {
|
||||
d.Set("Lock", sig.Lock)
|
||||
}
|
||||
if sig.SV != nil {
|
||||
d.Set("SV", sig.SV)
|
||||
d.SetIfNotNil("V", sig.V.ToPdfObject())
|
||||
}
|
||||
|
||||
return container
|
||||
|
@ -53,6 +53,9 @@ func flattenFields(field *PdfField) []*PdfField {
|
||||
|
||||
// AllFields returns a flattened list of all fields in the form.
|
||||
func (form *PdfAcroForm) AllFields() []*PdfField {
|
||||
if form == nil {
|
||||
return nil
|
||||
}
|
||||
var fields []*PdfField
|
||||
if form.Fields != nil {
|
||||
for _, field := range *form.Fields {
|
||||
|
7
pdf/model/sighandler/doc.go
Normal file
7
pdf/model/sighandler/doc.go
Normal file
@ -0,0 +1,7 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
// Package sighandler implements digital signature handlers for PDF signature validation and signing.
|
||||
package sighandler
|
128
pdf/model/sighandler/sighandler_pkcs7.go
Normal file
128
pdf/model/sighandler/sighandler_pkcs7.go
Normal file
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package sighandler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
|
||||
"github.com/gunnsth/pkcs7"
|
||||
|
||||
"github.com/unidoc/unidoc/pdf/core"
|
||||
"github.com/unidoc/unidoc/pdf/model"
|
||||
)
|
||||
|
||||
// Adobe PKCS7 detached signature handler.
|
||||
type adobePKCS7Detached struct {
|
||||
privateKey *rsa.PrivateKey
|
||||
certificate *x509.Certificate
|
||||
}
|
||||
|
||||
// NewAdobePKCS7Detached creates a new Adobe.PPKMS/Adobe.PPKLite adbe.pkcs7.detached signature handler.
|
||||
// The both parameters may be nil for the signature validation.
|
||||
func NewAdobePKCS7Detached(privateKey *rsa.PrivateKey, certificate *x509.Certificate) (model.SignatureHandler, error) {
|
||||
return &adobePKCS7Detached{certificate: certificate, privateKey: privateKey}, nil
|
||||
}
|
||||
|
||||
// InitSignature initialises the PdfSignature.
|
||||
func (a *adobePKCS7Detached) InitSignature(sig *model.PdfSignature) error {
|
||||
if a.certificate == nil {
|
||||
return errors.New("certificate must not be nil")
|
||||
}
|
||||
if a.privateKey == nil {
|
||||
return errors.New("privateKey must not be nil")
|
||||
}
|
||||
|
||||
handler := *a
|
||||
sig.Handler = &handler
|
||||
sig.Filter = core.MakeName("Adobe.PPKLite")
|
||||
sig.SubFilter = core.MakeName("adbe.pkcs7.detached")
|
||||
sig.Reference = nil
|
||||
|
||||
digest, err := handler.NewDigest(sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
digest.Write([]byte("calculate the Contents field size"))
|
||||
return handler.Sign(sig, digest)
|
||||
}
|
||||
|
||||
func (a *adobePKCS7Detached) getCertificate(sig *model.PdfSignature) (*x509.Certificate, error) {
|
||||
certificate := a.certificate
|
||||
if certificate == nil {
|
||||
certData := sig.Cert.(*core.PdfObjectString).Bytes()
|
||||
certs, err := x509.ParseCertificates(certData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certificate = certs[0]
|
||||
}
|
||||
return certificate, nil
|
||||
}
|
||||
|
||||
// NewDigest creates a new digest.
|
||||
func (a *adobePKCS7Detached) NewDigest(sig *model.PdfSignature) (model.Hasher, error) {
|
||||
return bytes.NewBuffer(nil), nil
|
||||
}
|
||||
|
||||
// Validate validates PdfSignature.
|
||||
func (a *adobePKCS7Detached) Validate(sig *model.PdfSignature, digest model.Hasher) (model.SignatureValidationResult, error) {
|
||||
signed := sig.Contents.Bytes()
|
||||
p7, err := pkcs7.Parse(signed)
|
||||
if err != nil {
|
||||
return model.SignatureValidationResult{}, err
|
||||
}
|
||||
|
||||
buffer := digest.(*bytes.Buffer)
|
||||
p7.Content = buffer.Bytes()
|
||||
if err = p7.Verify(); err != nil {
|
||||
return model.SignatureValidationResult{}, err
|
||||
}
|
||||
|
||||
return model.SignatureValidationResult{
|
||||
IsSigned: true,
|
||||
IsVerified: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Sign sets the Contents fields.
|
||||
func (a *adobePKCS7Detached) Sign(sig *model.PdfSignature, digest model.Hasher) error {
|
||||
buffer := digest.(*bytes.Buffer)
|
||||
signedData, err := pkcs7.NewSignedData(buffer.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add the signing cert and private key
|
||||
if err := signedData.AddSigner(a.certificate, a.privateKey, pkcs7.SignerInfoConfig{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Call Detach() is you want to remove content from the signature
|
||||
// and generate an S/MIME detached signature
|
||||
signedData.Detach()
|
||||
// Finish() to obtain the signature bytes
|
||||
detachedSignature, err := signedData.Finish()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := make([]byte, 8192)
|
||||
copy(data, detachedSignature)
|
||||
|
||||
sig.Contents = core.MakeHexString(string(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsApplicable returns true if the signature handler is applicable for the PdfSignature
|
||||
func (a *adobePKCS7Detached) IsApplicable(sig *model.PdfSignature) bool {
|
||||
if sig == nil || sig.Filter == nil || sig.SubFilter == nil {
|
||||
return false
|
||||
}
|
||||
return (*sig.Filter == "Adobe.PPKMS" || *sig.Filter == "Adobe.PPKLite") && *sig.SubFilter == "adbe.pkcs7.detached"
|
||||
}
|
140
pdf/model/sighandler/sighandler_rsa_sha1.go
Normal file
140
pdf/model/sighandler/sighandler_rsa_sha1.go
Normal file
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package sighandler
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"hash"
|
||||
|
||||
"github.com/unidoc/unidoc/pdf/core"
|
||||
"github.com/unidoc/unidoc/pdf/model"
|
||||
)
|
||||
|
||||
// Adobe X509 RSA SHA1 signature handler.
|
||||
type adobeX509RSASHA1 struct {
|
||||
privateKey *rsa.PrivateKey
|
||||
certificate *x509.Certificate
|
||||
}
|
||||
|
||||
// NewAdobeX509RSASHA1 creates a new Adobe.PPKMS/Adobe.PPKLite adbe.x509.rsa_sha1 signature handler.
|
||||
// The both parameters may be nil for the signature validation.
|
||||
func NewAdobeX509RSASHA1(privateKey *rsa.PrivateKey, certificate *x509.Certificate) (model.SignatureHandler, error) {
|
||||
return &adobeX509RSASHA1{certificate: certificate, privateKey: privateKey}, nil
|
||||
}
|
||||
|
||||
// InitSignature initialises the PdfSignature.
|
||||
func (a *adobeX509RSASHA1) InitSignature(sig *model.PdfSignature) error {
|
||||
if a.certificate == nil {
|
||||
return errors.New("certificate must not be nil")
|
||||
}
|
||||
if a.privateKey == nil {
|
||||
return errors.New("privateKey must not be nil")
|
||||
}
|
||||
|
||||
handler := *a
|
||||
sig.Handler = &handler
|
||||
sig.Filter = core.MakeName("Adobe.PPKLite")
|
||||
sig.SubFilter = core.MakeName("adbe.x509.rsa_sha1")
|
||||
sig.Cert = core.MakeString(string(handler.certificate.Raw))
|
||||
sig.Reference = nil
|
||||
|
||||
digest, err := handler.NewDigest(sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
digest.Write([]byte("calculate the Contents field size"))
|
||||
return handler.Sign(sig, digest)
|
||||
}
|
||||
|
||||
func getHashFromSignatureAlgorithm(sa x509.SignatureAlgorithm) (crypto.Hash, bool) {
|
||||
return crypto.SHA1, true
|
||||
}
|
||||
|
||||
func (a *adobeX509RSASHA1) getCertificate(sig *model.PdfSignature) (*x509.Certificate, error) {
|
||||
certificate := a.certificate
|
||||
if certificate == nil {
|
||||
certData := sig.Cert.(*core.PdfObjectString).Bytes()
|
||||
certs, err := x509.ParseCertificates(certData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certificate = certs[0]
|
||||
}
|
||||
return certificate, nil
|
||||
}
|
||||
|
||||
// NewDigest creates a new digest.
|
||||
func (a *adobeX509RSASHA1) NewDigest(sig *model.PdfSignature) (model.Hasher, error) {
|
||||
certificate, err := a.getCertificate(sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, _ := getHashFromSignatureAlgorithm(certificate.SignatureAlgorithm)
|
||||
return h.New(), nil
|
||||
}
|
||||
|
||||
// Validate validates PdfSignature.
|
||||
func (a *adobeX509RSASHA1) Validate(sig *model.PdfSignature, digest model.Hasher) (model.SignatureValidationResult, error) {
|
||||
certData := sig.Cert.(*core.PdfObjectString).Bytes()
|
||||
certs, err := x509.ParseCertificates(certData)
|
||||
if err != nil {
|
||||
return model.SignatureValidationResult{}, err
|
||||
}
|
||||
if len(certs) == 0 {
|
||||
return model.SignatureValidationResult{}, errors.New("certificate not found")
|
||||
}
|
||||
cert := certs[0]
|
||||
signed := sig.Contents.Bytes()
|
||||
var sigHash []byte
|
||||
if _, err := asn1.Unmarshal(signed, &sigHash); err != nil {
|
||||
return model.SignatureValidationResult{}, err
|
||||
}
|
||||
h, ok := digest.(hash.Hash)
|
||||
if !ok {
|
||||
return model.SignatureValidationResult{}, errors.New("hash type error")
|
||||
}
|
||||
certificate, err := a.getCertificate(sig)
|
||||
if err != nil {
|
||||
return model.SignatureValidationResult{}, err
|
||||
}
|
||||
ha, _ := getHashFromSignatureAlgorithm(certificate.SignatureAlgorithm)
|
||||
if err := rsa.VerifyPKCS1v15(cert.PublicKey.(*rsa.PublicKey), ha, h.Sum(nil), sigHash); err != nil {
|
||||
return model.SignatureValidationResult{}, err
|
||||
}
|
||||
return model.SignatureValidationResult{IsSigned: true, IsVerified: true}, nil
|
||||
}
|
||||
|
||||
// Sign sets the Contents fields for the PdfSignature.
|
||||
func (a *adobeX509RSASHA1) Sign(sig *model.PdfSignature, digest model.Hasher) error {
|
||||
h, ok := digest.(hash.Hash)
|
||||
if !ok {
|
||||
return errors.New("hash type error")
|
||||
}
|
||||
ha, _ := getHashFromSignatureAlgorithm(a.certificate.SignatureAlgorithm)
|
||||
data, err := rsa.SignPKCS1v15(rand.Reader, a.privateKey, ha, h.Sum(nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err = asn1.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sig.Contents = core.MakeHexString(string(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsApplicable returns true if the signature handler is applicable for the PdfSignature.
|
||||
func (a *adobeX509RSASHA1) IsApplicable(sig *model.PdfSignature) bool {
|
||||
if sig == nil || sig.Filter == nil || sig.SubFilter == nil {
|
||||
return false
|
||||
}
|
||||
return (*sig.Filter == "Adobe.PPKMS" || *sig.Filter == "Adobe.PPKLite") && *sig.SubFilter == "adbe.x509.rsa_sha1"
|
||||
}
|
@ -6,14 +6,83 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/unidoc/unidoc/common"
|
||||
"github.com/unidoc/unidoc/pdf/core"
|
||||
)
|
||||
|
||||
var _ core.PdfObject = &pdfSignDictionary{}
|
||||
|
||||
// pdfSignDictionary is used as a wrapper around PdfSignature for digital checksum calculation
|
||||
// and population of /Contents and /ByteRange.
|
||||
// Implements interface core.PdfObject.
|
||||
type pdfSignDictionary struct {
|
||||
*core.PdfObjectDictionary
|
||||
handler *SignatureHandler
|
||||
signature *PdfSignature
|
||||
fileOffset int64
|
||||
contentsOffsetStart int
|
||||
contentsOffsetEnd int
|
||||
byteRangeOffsetStart int
|
||||
byteRangeOffsetEnd int
|
||||
}
|
||||
|
||||
// GetSubFilter returns SubFilter value or empty string.
|
||||
func (d *pdfSignDictionary) GetSubFilter() string {
|
||||
obj := d.Get("SubFilter")
|
||||
if obj == nil {
|
||||
return ""
|
||||
}
|
||||
if sf, found := core.GetNameVal(obj); found {
|
||||
return sf
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// WriteString outputs the object as it is to be written to file.
|
||||
func (d *pdfSignDictionary) WriteString() string {
|
||||
d.contentsOffsetStart = 0
|
||||
d.contentsOffsetEnd = 0
|
||||
d.byteRangeOffsetStart = 0
|
||||
d.byteRangeOffsetEnd = 0
|
||||
out := bytes.NewBuffer(nil)
|
||||
out.WriteString("<<")
|
||||
for _, k := range d.Keys() {
|
||||
v := d.Get(k)
|
||||
switch k {
|
||||
case "ByteRange":
|
||||
out.WriteString(k.WriteString())
|
||||
out.WriteString(" ")
|
||||
d.byteRangeOffsetStart = out.Len()
|
||||
out.WriteString(v.WriteString())
|
||||
out.WriteString(" ")
|
||||
d.byteRangeOffsetEnd = out.Len() - 1
|
||||
case "Contents":
|
||||
out.WriteString(k.WriteString())
|
||||
out.WriteString(" ")
|
||||
d.contentsOffsetStart = out.Len()
|
||||
out.WriteString(v.WriteString())
|
||||
out.WriteString(" ")
|
||||
d.contentsOffsetEnd = out.Len() - 1
|
||||
default:
|
||||
out.WriteString(k.WriteString())
|
||||
out.WriteString(" ")
|
||||
out.WriteString(v.WriteString())
|
||||
}
|
||||
}
|
||||
out.WriteString(">>")
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// PdfSignature represents a PDF signature dictionary and is used for signing via form signature fields.
|
||||
// (Section 12.8, Table 252 - Entries in a signature dictionary p. 475 in PDF32000_2008).
|
||||
type PdfSignature struct {
|
||||
Handler SignatureHandler
|
||||
container *core.PdfIndirectObject
|
||||
|
||||
// Type: Sig/DocTimeStamp
|
||||
Type *core.PdfObjectName
|
||||
Filter *core.PdfObjectName
|
||||
@ -35,14 +104,21 @@ type PdfSignature struct {
|
||||
PropAuthType *core.PdfObjectName
|
||||
}
|
||||
|
||||
// PdfSignatureReference represents a signature reference dictionary.
|
||||
// (Table 253 - p. 477 in PDF32000_2008).
|
||||
type PdfSignatureReference struct {
|
||||
// Type: SigRef
|
||||
TransformMethod *core.PdfObjectName
|
||||
TransformParams *core.PdfObjectDictionary
|
||||
Data core.PdfObject
|
||||
DigestMethod *core.PdfObjectName
|
||||
// NewPdfSignature creates a new PdfSignature object.
|
||||
func NewPdfSignature(handler SignatureHandler) *PdfSignature {
|
||||
sig := &PdfSignature{
|
||||
Type: core.MakeName("Sig"),
|
||||
Handler: handler,
|
||||
}
|
||||
|
||||
dict := &pdfSignDictionary{
|
||||
PdfObjectDictionary: core.MakeDict(),
|
||||
handler: &handler,
|
||||
signature: sig,
|
||||
}
|
||||
|
||||
sig.container = core.MakeIndirectObject(dict)
|
||||
return sig
|
||||
}
|
||||
|
||||
// GetContainingPdfObject implements interface PdfModel.
|
||||
@ -50,48 +126,68 @@ func (sig *PdfSignature) GetContainingPdfObject() core.PdfObject {
|
||||
return sig.container
|
||||
}
|
||||
|
||||
// SetName sets the `Name` field of the signature.
|
||||
func (sig *PdfSignature) SetName(name string) {
|
||||
sig.Name = core.MakeString(name)
|
||||
}
|
||||
|
||||
// SetDate sets the `M` field of the signature.
|
||||
func (sig *PdfSignature) SetDate(date time.Time, format string) {
|
||||
if format == "" {
|
||||
format = "D:20060102150405-07'00'"
|
||||
}
|
||||
|
||||
sig.M = core.MakeString(date.Format(format))
|
||||
}
|
||||
|
||||
// SetReason sets the `Reason` field of the signature.
|
||||
func (sig *PdfSignature) SetReason(reason string) {
|
||||
sig.Reason = core.MakeString(reason)
|
||||
}
|
||||
|
||||
// SetLocation sets the `Location` field of the signature.
|
||||
func (sig *PdfSignature) SetLocation(location string) {
|
||||
sig.Location = core.MakeString(location)
|
||||
}
|
||||
|
||||
// Initialize initializes the PdfSignature.
|
||||
func (sig *PdfSignature) Initialize() error {
|
||||
if sig.Handler == nil {
|
||||
return errors.New("signature handler cannot be nil")
|
||||
}
|
||||
|
||||
return sig.Handler.InitSignature(sig)
|
||||
}
|
||||
|
||||
// ToPdfObject implements interface PdfModel.
|
||||
func (sig *PdfSignature) ToPdfObject() core.PdfObject {
|
||||
container := sig.container
|
||||
dict := container.PdfObject.(*core.PdfObjectDictionary)
|
||||
|
||||
var dict *core.PdfObjectDictionary
|
||||
if sigDict, ok := container.PdfObject.(*pdfSignDictionary); ok {
|
||||
dict = sigDict.PdfObjectDictionary
|
||||
} else {
|
||||
dict = container.PdfObject.(*core.PdfObjectDictionary)
|
||||
}
|
||||
|
||||
dict.Set("Type", sig.Type)
|
||||
dict.SetIfNotNil("Filter", sig.Filter)
|
||||
dict.SetIfNotNil("SubFilter", sig.SubFilter)
|
||||
dict.SetIfNotNil("Contents", sig.Contents)
|
||||
dict.SetIfNotNil("Cert", sig.Cert)
|
||||
dict.SetIfNotNil("ByteRange", sig.ByteRange)
|
||||
dict.SetIfNotNil("Reference", sig.Reference)
|
||||
dict.SetIfNotNil("Changes", sig.Changes)
|
||||
dict.SetIfNotNil("Name", sig.Name)
|
||||
dict.SetIfNotNil("M", sig.M)
|
||||
dict.SetIfNotNil("Reason", sig.Reason)
|
||||
dict.SetIfNotNil("ContactInfo", sig.ContactInfo)
|
||||
dict.SetIfNotNil("ByteRange", sig.ByteRange)
|
||||
dict.SetIfNotNil("Contents", sig.Contents)
|
||||
|
||||
if sig.Filter != nil {
|
||||
dict.Set("Filter", sig.Filter)
|
||||
}
|
||||
if sig.SubFilter != nil {
|
||||
dict.Set("SubFilter", sig.SubFilter)
|
||||
}
|
||||
if sig.Contents != nil {
|
||||
dict.Set("Contents", sig.Contents)
|
||||
}
|
||||
if sig.Cert != nil {
|
||||
dict.Set("Cert", sig.Cert)
|
||||
}
|
||||
if sig.ByteRange != nil {
|
||||
dict.Set("ByteRange", sig.ByteRange)
|
||||
}
|
||||
if sig.Reference != nil {
|
||||
dict.Set("Reference", sig.Reference)
|
||||
}
|
||||
if sig.Changes != nil {
|
||||
dict.Set("Changes", sig.Changes)
|
||||
}
|
||||
if sig.Name != nil {
|
||||
dict.Set("Name", sig.Name)
|
||||
}
|
||||
if sig.M != nil {
|
||||
dict.Set("M", sig.M)
|
||||
}
|
||||
if sig.Reason != nil {
|
||||
dict.Set("Reason", sig.Reason)
|
||||
}
|
||||
if sig.ContactInfo != nil {
|
||||
dict.Set("ContactInfo", sig.ContactInfo)
|
||||
}
|
||||
|
||||
// FIXME: ByteRange and Contents need to be updated dynamically.
|
||||
// NOTE: ByteRange and Contents need to be updated dynamically.
|
||||
// TODO: Currently dynamic update is only in the appender, need to support
|
||||
// in the PdfWriter too for the initial signature on document creation.
|
||||
return container
|
||||
}
|
||||
|
||||
@ -148,106 +244,3 @@ func (r *PdfReader) newPdfSignatureFromIndirect(container *core.PdfIndirectObjec
|
||||
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
// PdfSignatureField represents a form field that contains a digital signature.
|
||||
// (12.7.4.5 - Signature Fields p. 454 in PDF32000_2008).
|
||||
//
|
||||
// The signature form field serves two primary purposes. 1. Define the form field that will provide the
|
||||
// visual signing properties for display but may also hold information needed when the actual signing
|
||||
// takes place such as signature method. This carries information from the author of the document to the
|
||||
// software that later does signing.
|
||||
//
|
||||
// Filling in (signing) the signature field entails updating at least the V entry and usually the AP entry of the
|
||||
// associated widget annotation. (Exporting a signature field exports the T, V, AP entries)
|
||||
//
|
||||
// The annotation rectangle (Rect) in such a dictionary shall give the position of the field on its page. Signature
|
||||
// fields that are not intended to be visible shall have an annotation rectangle that has zero height and width. PDF
|
||||
// processors shall treat such signatures as not visible. PDF processors shall also treat signatures as not
|
||||
// visible if either the Hidden bit or the NoView bit of the F entry is true
|
||||
//
|
||||
// The location of a signature within a document can have a bearing on its legal meaning. For this reason,
|
||||
// signature fields shall never refer to more than one annotation.
|
||||
type PdfSignatureField struct {
|
||||
container *core.PdfIndirectObject
|
||||
|
||||
V *PdfSignature
|
||||
// Type: /Sig
|
||||
// V: *PdfSignature...
|
||||
Lock *core.PdfIndirectObject // Shall be an indirect reference.
|
||||
SV *core.PdfIndirectObject // Shall be an indirect reference.
|
||||
}
|
||||
|
||||
// NewPdfSignatureField prepares a PdfSignatureField from a PdfSignature.
|
||||
func NewPdfSignatureField(signature *PdfSignature) *PdfSignatureField {
|
||||
return &PdfSignatureField{
|
||||
V: signature,
|
||||
container: core.MakeIndirectObject(core.MakeDict()),
|
||||
}
|
||||
}
|
||||
|
||||
// ToPdfObject implements interface PdfModel.
|
||||
func (sf *PdfSignatureField) ToPdfObject() core.PdfObject {
|
||||
container := sf.container
|
||||
dict := container.PdfObject.(*core.PdfObjectDictionary)
|
||||
|
||||
dict.Set("FT", core.MakeName("Sig"))
|
||||
|
||||
if sf.V != nil {
|
||||
dict.Set("V", sf.V.ToPdfObject())
|
||||
}
|
||||
if sf.Lock != nil {
|
||||
dict.Set("Lock", sf.Lock)
|
||||
}
|
||||
if sf.SV != nil {
|
||||
dict.Set("SV", sf.SV)
|
||||
}
|
||||
// Other standard fields...
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
// PdfSignatureFieldLock represents signature field lock dictionary.
|
||||
// (Table 233 - p. 455 in PDF32000_2008).
|
||||
type PdfSignatureFieldLock struct {
|
||||
Type core.PdfObject
|
||||
Action *core.PdfObjectName
|
||||
Fields *core.PdfObjectArray
|
||||
P *core.PdfObjectInteger
|
||||
}
|
||||
|
||||
// PdfSignatureFieldSeed represents signature field seed value dictionary.
|
||||
// (Table 234 - p. 455 in PDF32000_2008).
|
||||
type PdfSignatureFieldSeed struct {
|
||||
// Type
|
||||
Ff *core.PdfObjectInteger
|
||||
Filter *core.PdfObjectName
|
||||
SubFilter *core.PdfObjectArray
|
||||
DigestMethod *core.PdfObjectArray
|
||||
V *core.PdfObjectInteger
|
||||
Cert core.PdfObject
|
||||
Reasons *core.PdfObjectArray
|
||||
MDP *core.PdfObjectDictionary
|
||||
TimeStamp *core.PdfObjectDictionary
|
||||
LegalAttestation *core.PdfObjectArray
|
||||
AddRevInfo *core.PdfObjectBool
|
||||
LockDocument *core.PdfObjectName
|
||||
AppearanceFilter *core.PdfObjectString
|
||||
}
|
||||
|
||||
// PdfCertificateSeed represents certificate seed value dictionary.
|
||||
// (Table 235 - p. 457 in PDF32000_2008).
|
||||
type PdfCertificateSeed struct {
|
||||
// Type
|
||||
Ff *core.PdfObjectInteger
|
||||
Subject *core.PdfObjectArray
|
||||
SignaturePolicyOID *core.PdfObjectString
|
||||
SignaturePolicyHashValue *core.PdfObjectString
|
||||
SignaturePolicyHashAlgorithm *core.PdfObjectName
|
||||
SignaturePolicyCommitmentType *core.PdfObjectArray
|
||||
SubjectDN *core.PdfObjectArray
|
||||
KeyUsage *core.PdfObjectArray
|
||||
Issuer *core.PdfObjectArray
|
||||
OID *core.PdfObjectArray
|
||||
URL *core.PdfObjectString
|
||||
URLType *core.PdfObjectName
|
||||
}
|
||||
|
204
pdf/model/signature_handler.go
Normal file
204
pdf/model/signature_handler.go
Normal file
@ -0,0 +1,204 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/unidoc/unidoc/common"
|
||||
"github.com/unidoc/unidoc/pdf/core"
|
||||
)
|
||||
|
||||
// Hasher is the interface that wraps the basic Write method.
|
||||
type Hasher interface {
|
||||
Write(p []byte) (n int, err error)
|
||||
}
|
||||
|
||||
// SignatureHandler interface defines the common functionality for PDF signature handlers, which
|
||||
// need to be capable of validating digital signatures and signing PDF documents.
|
||||
type SignatureHandler interface {
|
||||
IsApplicable(sig *PdfSignature) bool
|
||||
Validate(sig *PdfSignature, digest Hasher) (SignatureValidationResult, error)
|
||||
// InitSignature sets the PdfSignature parameters.
|
||||
InitSignature(*PdfSignature) error
|
||||
NewDigest(sig *PdfSignature) (Hasher, error)
|
||||
Sign(sig *PdfSignature, digest Hasher) error
|
||||
}
|
||||
|
||||
// SignatureValidationResult defines the response from the signature validation handler.
|
||||
type SignatureValidationResult struct {
|
||||
// List of errors when validating the signature.
|
||||
Errors []string
|
||||
IsSigned bool
|
||||
IsVerified bool
|
||||
IsTrusted bool
|
||||
Fields []*PdfField
|
||||
Name string
|
||||
Date PdfDate
|
||||
Reason string
|
||||
Location string
|
||||
ContactInfo string
|
||||
|
||||
// TODO(gunnsth): Add more fields such as ability to access the certificate information (name, CN, etc).
|
||||
// TODO: Also add flags to indicate whether the signature covers the entire file, or the entire portion of
|
||||
// a revision (if incremental updates used).
|
||||
}
|
||||
|
||||
func (v SignatureValidationResult) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(fmt.Sprintf("Name: %s\n", v.Name))
|
||||
if v.Date.year > 0 {
|
||||
buf.WriteString(fmt.Sprintf("Date: %s\n", v.Date.ToGoTime().String()))
|
||||
} else {
|
||||
buf.WriteString("Date not specified\n")
|
||||
}
|
||||
if len(v.Reason) > 0 {
|
||||
buf.WriteString(fmt.Sprintf("Reason: %s\n", v.Reason))
|
||||
} else {
|
||||
buf.WriteString("No reason specified\n")
|
||||
}
|
||||
if len(v.Location) > 0 {
|
||||
buf.WriteString(fmt.Sprintf("Location: %s\n", v.Location))
|
||||
} else {
|
||||
buf.WriteString("Location not specified\n")
|
||||
}
|
||||
if len(v.ContactInfo) > 0 {
|
||||
buf.WriteString(fmt.Sprintf("Contact Info: %s\n", v.ContactInfo))
|
||||
} else {
|
||||
buf.WriteString("Contact info not specified\n")
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf("Fields: %d\n", len(v.Fields)))
|
||||
if v.IsSigned {
|
||||
buf.WriteString("Signed: Document is signed\n")
|
||||
} else {
|
||||
buf.WriteString("Signed: Not signed\n")
|
||||
}
|
||||
if v.IsVerified {
|
||||
buf.WriteString("Signature validation: Is valid\n")
|
||||
} else {
|
||||
buf.WriteString("Signature validation: Is invalid\n")
|
||||
}
|
||||
if v.IsTrusted {
|
||||
buf.WriteString("Trusted: Certificate is trusted\n")
|
||||
} else {
|
||||
buf.WriteString("Trusted: Untrusted certificate\n")
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// ValidateSignatures validates digital signatures in the document.
|
||||
func (r *PdfReader) ValidateSignatures(handlers []SignatureHandler) ([]SignatureValidationResult, error) {
|
||||
if r.AcroForm == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if r.AcroForm.Fields == nil {
|
||||
return nil, nil
|
||||
}
|
||||
type sigFieldPair struct {
|
||||
sig *PdfSignature
|
||||
field *PdfField
|
||||
handler SignatureHandler
|
||||
}
|
||||
|
||||
var pairs []*sigFieldPair
|
||||
for _, f := range r.AcroForm.AllFields() {
|
||||
if f.V == nil {
|
||||
continue
|
||||
}
|
||||
if d, found := core.GetDict(f.V); found {
|
||||
if name, ok := core.GetNameVal(d.Get("Type")); ok && name == "Sig" {
|
||||
ind, found := core.GetIndirect(f.V)
|
||||
if !found {
|
||||
common.Log.Debug("ERROR: Signature container is nil")
|
||||
return nil, ErrTypeCheck
|
||||
}
|
||||
|
||||
sig, err := r.newPdfSignatureFromIndirect(ind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Search for an appropriate handler.
|
||||
var sigHandler SignatureHandler
|
||||
for _, handler := range handlers {
|
||||
if handler.IsApplicable(sig) {
|
||||
sigHandler = handler
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
pairs = append(pairs, &sigFieldPair{
|
||||
sig: sig,
|
||||
field: f,
|
||||
handler: sigHandler,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var results []SignatureValidationResult
|
||||
for _, pair := range pairs {
|
||||
defaultResult := SignatureValidationResult{
|
||||
IsSigned: true,
|
||||
Fields: []*PdfField{pair.field},
|
||||
}
|
||||
if pair.handler == nil {
|
||||
defaultResult.Errors = append(defaultResult.Errors, "handler not set")
|
||||
results = append(results, defaultResult)
|
||||
continue
|
||||
}
|
||||
digest, err := pair.handler.NewDigest(pair.sig)
|
||||
if err != nil {
|
||||
defaultResult.Errors = append(defaultResult.Errors, "digest error", err.Error())
|
||||
results = append(results, defaultResult)
|
||||
continue
|
||||
}
|
||||
byteRange := pair.sig.ByteRange
|
||||
if byteRange == nil {
|
||||
defaultResult.Errors = append(defaultResult.Errors, "ByteRange not set")
|
||||
results = append(results, defaultResult)
|
||||
continue
|
||||
}
|
||||
|
||||
for i := 0; i < byteRange.Len(); i = i + 2 {
|
||||
start, _ := core.GetNumberAsInt64(byteRange.Get(i))
|
||||
ln, _ := core.GetIntVal(byteRange.Get(i + 1))
|
||||
if _, err := r.rs.Seek(start, io.SeekStart); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]byte, ln)
|
||||
if _, err := r.rs.Read(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
digest.Write(data)
|
||||
}
|
||||
|
||||
result, err := pair.handler.Validate(pair.sig, digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result.Name = pair.sig.Name.Decoded()
|
||||
result.Reason = pair.sig.Reason.Decoded()
|
||||
if pair.sig.M != nil {
|
||||
sigDate, err := NewPdfDate(pair.sig.M.String())
|
||||
if err != nil {
|
||||
result.Errors = append(result.Errors, err.Error())
|
||||
continue
|
||||
}
|
||||
result.Date = sigDate
|
||||
}
|
||||
result.ContactInfo = pair.sig.ContactInfo.Decoded()
|
||||
result.Location = pair.sig.Location.Decoded()
|
||||
|
||||
result.Fields = defaultResult.Fields
|
||||
results = append(results, result)
|
||||
}
|
||||
return results, nil
|
||||
}
|
@ -14,6 +14,7 @@ import (
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/unidoc/unidoc/pdf/core"
|
||||
)
|
||||
@ -93,6 +94,21 @@ type PdfDate struct {
|
||||
utOffsetMins int64 // mm (00-59)
|
||||
}
|
||||
|
||||
// ToGoTime returns the date in time.Time format.
|
||||
func (d PdfDate) ToGoTime() time.Time {
|
||||
utcOffset := int(d.utOffsetHours*60*60 + d.utOffsetMins*60)
|
||||
switch d.utOffsetSign {
|
||||
case '-':
|
||||
utcOffset = -utcOffset
|
||||
case 'Z':
|
||||
utcOffset = 0
|
||||
}
|
||||
tzName := fmt.Sprintf("UTC%c%.2d%.2d", d.utOffsetSign, d.utOffsetHours, d.utOffsetMins)
|
||||
tz := time.FixedZone(tzName, utcOffset)
|
||||
|
||||
return time.Date(int(d.year), time.Month(d.month), int(d.day), int(d.hour), int(d.minute), int(d.second), 0, tz)
|
||||
}
|
||||
|
||||
var reDate = regexp.MustCompile(`\s*D\s*:\s*(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})([+-Z])?(\d{2})?'?(\d{2})?`)
|
||||
|
||||
// NewPdfDate returns a new PdfDate object from a PDF date string (see 7.9.4 Dates).
|
||||
|
BIN
pdf/model/testdata/SampleSignedPDFDocument.pdf
vendored
Normal file
BIN
pdf/model/testdata/SampleSignedPDFDocument.pdf
vendored
Normal file
Binary file not shown.
BIN
pdf/model/testdata/ks12
vendored
Normal file
BIN
pdf/model/testdata/ks12
vendored
Normal file
Binary file not shown.
@ -225,6 +225,18 @@ func copyObject(obj core.PdfObject, objectToObjectCopyMap map[core.PdfObject]cor
|
||||
newObj := core.PdfObjectBool(*t)
|
||||
objectToObjectCopyMap[obj] = &newObj
|
||||
return &newObj
|
||||
case *pdfSignDictionary:
|
||||
newObj := &pdfSignDictionary{
|
||||
PdfObjectDictionary: core.MakeDict(),
|
||||
handler: t.handler,
|
||||
signature: t.signature,
|
||||
}
|
||||
objectToObjectCopyMap[obj] = newObj
|
||||
for _, key := range t.Keys() {
|
||||
val := t.Get(key)
|
||||
newObj.Set(key, copyObject(val, objectToObjectCopyMap))
|
||||
}
|
||||
return newObj
|
||||
default:
|
||||
common.Log.Info("TODO(a5i): implement copyObject for %+v", obj)
|
||||
}
|
||||
@ -571,6 +583,9 @@ func (w *PdfWriter) writeObject(num int, obj core.PdfObject) {
|
||||
if pobj, isIndirect := obj.(*core.PdfIndirectObject); isIndirect {
|
||||
w.crossReferenceMap[num] = crossReference{Type: 1, Offset: w.writePos, Generation: pobj.GenerationNumber}
|
||||
outStr := fmt.Sprintf("%d 0 obj\n", num)
|
||||
if sDict, ok := pobj.PdfObject.(*pdfSignDictionary); ok {
|
||||
sDict.fileOffset = w.writePos + int64(len(outStr))
|
||||
}
|
||||
outStr += pobj.PdfObject.WriteString()
|
||||
outStr += "\nendobj\n"
|
||||
w.writeString(outStr)
|
||||
|
Loading…
x
Reference in New Issue
Block a user