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
|
// UTF-16BE is applied when the first two bytes are 0xFE, 0XFF, otherwise decoding of
|
||||||
// PDFDocEncoding is performed.
|
// PDFDocEncoding is performed.
|
||||||
func (str *PdfObjectString) Decoded() string {
|
func (str *PdfObjectString) Decoded() string {
|
||||||
|
if str == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
b := []byte(str.val)
|
b := []byte(str.val)
|
||||||
if len(b) >= 2 && b[0] == 0xFE && b[1] == 0xFF {
|
if len(b) >= 2 && b[0] == 0xFE && b[1] == 0xFF {
|
||||||
// UTF16BE.
|
// UTF16BE.
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
"github.com/unidoc/unidoc/pdf/core"
|
"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 {
|
type PdfAppender struct {
|
||||||
rs io.ReadSeeker
|
rs io.ReadSeeker
|
||||||
parser *core.PdfParser
|
parser *core.PdfParser
|
||||||
@ -30,8 +30,11 @@ type PdfAppender struct {
|
|||||||
xrefs core.XrefTable
|
xrefs core.XrefTable
|
||||||
greatestObjNum int
|
greatestObjNum int
|
||||||
|
|
||||||
|
// List of new objects and a map for quick lookups.
|
||||||
newObjects []core.PdfObject
|
newObjects []core.PdfObject
|
||||||
hasNewObject map[core.PdfObject]struct{}
|
hasNewObject map[core.PdfObject]struct{}
|
||||||
|
|
||||||
|
written bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPageResources(p *PdfPage) map[core.PdfObjectName]core.PdfObject {
|
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.
|
// NewPdfAppender creates a new Pdf appender from a Pdf reader.
|
||||||
func NewPdfAppender(reader *PdfReader) (*PdfAppender, error) {
|
func NewPdfAppender(reader *PdfReader) (*PdfAppender, error) {
|
||||||
a := &PdfAppender{}
|
a := &PdfAppender{
|
||||||
a.rs = reader.rs
|
rs: reader.rs,
|
||||||
a.Reader = reader
|
Reader: reader,
|
||||||
a.parser = a.Reader.parser
|
parser: reader.parser,
|
||||||
|
}
|
||||||
if _, err := a.rs.Seek(0, io.SeekStart); err != nil {
|
if _, err := a.rs.Seek(0, io.SeekStart); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var err error
|
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.
|
// Create a readonly (immutable) reader. It increases memory use but is necessary to be able
|
||||||
// When we merge, replace a page content. The new page will contain objects from the readonly reader and other objects.
|
// to detect changes in the original reader objects.
|
||||||
// The readonly objects won't append to the result Pdf file. This check is not resource demanding. It checks indirect objects owners only.
|
//
|
||||||
|
// 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)
|
a.roReader, err = NewPdfReader(a.rs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -131,8 +140,8 @@ func (a *PdfAppender) addNewObjects(obj core.PdfObject) {
|
|||||||
}
|
}
|
||||||
switch v := obj.(type) {
|
switch v := obj.(type) {
|
||||||
case *core.PdfIndirectObject:
|
case *core.PdfIndirectObject:
|
||||||
// Check the object is changing.
|
// If the current parser is different from the read-only parser, then
|
||||||
// If the indirect object has not the readonly parser then the object is changed.
|
// the object has changed.
|
||||||
if v.GetParser() != a.roReader.parser {
|
if v.GetParser() != a.roReader.parser {
|
||||||
a.newObjects = append(a.newObjects, obj)
|
a.newObjects = append(a.newObjects, obj)
|
||||||
a.hasNewObject[obj] = struct{}{}
|
a.hasNewObject[obj] = struct{}{}
|
||||||
@ -147,22 +156,25 @@ func (a *PdfAppender) addNewObjects(obj core.PdfObject) {
|
|||||||
a.addNewObjects(v.Get(key))
|
a.addNewObjects(v.Get(key))
|
||||||
}
|
}
|
||||||
case *core.PdfObjectStreams:
|
case *core.PdfObjectStreams:
|
||||||
// Check the object is changing.
|
// If the current parser is different from the read-only parser, then
|
||||||
// If the indirect object has not the readonly parser then the object is changed.
|
// the object has changed.
|
||||||
if v.GetParser() != a.roReader.parser {
|
if v.GetParser() != a.roReader.parser {
|
||||||
for _, o := range v.Elements() {
|
for _, o := range v.Elements() {
|
||||||
a.addNewObjects(o)
|
a.addNewObjects(o)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *core.PdfObjectStream:
|
case *core.PdfObjectStream:
|
||||||
// Check the object is changing.
|
// If the current parser is different from the read-only parser, then
|
||||||
// If the indirect object has the readonly parser then the object is not changed.
|
// the object has changed.
|
||||||
if v.GetParser() == a.roReader.parser {
|
parser := v.GetParser()
|
||||||
|
if parser == a.roReader.parser {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// If the indirect object has not the origin parser then the object may be changed orr not.
|
|
||||||
if v.GetParser() == a.Reader.parser {
|
// If the current parser is different from the parser of the reader,
|
||||||
// Check data is not changed.
|
// 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 {
|
if streamObj, err := a.roReader.parser.LookupByReference(v.PdfObjectReference); err == nil {
|
||||||
var isNotChanged bool
|
var isNotChanged bool
|
||||||
if stream, ok := core.GetStream(streamObj); ok && bytes.Equal(stream.Stream, v.Stream) {
|
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
|
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) {
|
func (a *PdfAppender) AddPages(pages ...*PdfPage) {
|
||||||
for _, page := range pages {
|
for _, page := range pages {
|
||||||
page = page.Duplicate()
|
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) {
|
func (a *PdfAppender) ReplaceAcroForm(acroForm *PdfAcroForm) {
|
||||||
a.acroForm = acroForm
|
a.acroForm = acroForm
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes the Appender output to io.Writer.
|
// 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 {
|
func (a *PdfAppender) Write(w io.Writer) error {
|
||||||
if _, err := a.rs.Seek(0, io.SeekStart); err != nil {
|
if a.written {
|
||||||
return err
|
return errors.New("appender write can only be invoked once")
|
||||||
}
|
|
||||||
offset, err := io.Copy(w, a.rs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writer := NewPdfWriter()
|
writer := NewPdfWriter()
|
||||||
@ -446,7 +513,7 @@ func (a *PdfAppender) Write(w io.Writer) error {
|
|||||||
common.Log.Trace("Page Parent: %T", parent)
|
common.Log.Trace("Page Parent: %T", parent)
|
||||||
parentDict, ok := parent.PdfObject.(*core.PdfObjectDictionary)
|
parentDict, ok := parent.PdfObject.(*core.PdfObjectDictionary)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("Invalid Parent object")
|
return errors.New("invalid Parent object")
|
||||||
}
|
}
|
||||||
for _, field := range inheritedFields {
|
for _, field := range inheritedFields {
|
||||||
common.Log.Trace("Field %s", field)
|
common.Log.Trace("Field %s", field)
|
||||||
@ -473,6 +540,56 @@ func (a *PdfAppender) Write(w io.Writer) error {
|
|||||||
writer.SetForms(a.acroForm)
|
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 {
|
if len(a.newObjects) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -481,14 +598,108 @@ func (a *PdfAppender) Write(w io.Writer) error {
|
|||||||
writer.ObjNumOffset = a.greatestObjNum
|
writer.ObjNumOffset = a.greatestObjNum
|
||||||
writer.appendMode = true
|
writer.appendMode = true
|
||||||
writer.appendToXrefs = a.xrefs
|
writer.appendToXrefs = a.xrefs
|
||||||
|
writer.minorVersion = 7
|
||||||
|
|
||||||
for _, obj := range a.newObjects {
|
for _, obj := range a.newObjects {
|
||||||
writer.addObject(obj)
|
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
|
||||||
}
|
}
|
||||||
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.
|
// 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.
|
* file 'LICENSE.md', which is part of this source code package.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package model
|
package model_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rsa"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/pkcs12"
|
||||||
|
|
||||||
"github.com/unidoc/unidoc/common"
|
"github.com/unidoc/unidoc/common"
|
||||||
"github.com/unidoc/unidoc/pdf/core"
|
"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
|
// This test file contains multiple tests to generate PDFs from existing Pdf files. The outputs are written
|
||||||
// themselves need to be observed to check for correctness as we don't have a good way to automatically check
|
// into TMPDIR as files. The files themselves need to be observed to check for correctness as we don't have
|
||||||
// if every detail is correct.
|
// a good way to automatically check if every detail is correct.
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
common.SetLogger(common.NewConsoleLogger(common.LogLevelDebug))
|
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
|
// source http://foersom.com/net/HowTo/data/OoPdfFormExample.pdf
|
||||||
const testPdfAcroFormFile1 = "./testdata/OoPdfFormExample.pdf"
|
const testPdfAcroFormFile1 = "./testdata/OoPdfFormExample.pdf"
|
||||||
|
|
||||||
|
const testPdfSignedPDFDocument = "./testdata/SampleSignedPDFDocument.pdf"
|
||||||
|
|
||||||
|
const testPKS12Key = "./testdata/ks12"
|
||||||
|
const testPKS12KeyPassword = "password"
|
||||||
|
|
||||||
func tempFile(name string) string {
|
func tempFile(name string) string {
|
||||||
return filepath.Join(os.TempDir(), name)
|
return filepath.Join(os.TempDir(), name)
|
||||||
}
|
}
|
||||||
@ -43,7 +57,7 @@ func TestAppenderAddPage(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f1.Close()
|
defer f1.Close()
|
||||||
pdf1, err := NewPdfReader(f1)
|
pdf1, err := model.NewPdfReader(f1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
@ -54,13 +68,13 @@ func TestAppenderAddPage(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f2.Close()
|
defer f2.Close()
|
||||||
pdf2, err := NewPdfReader(f2)
|
pdf2, err := model.NewPdfReader(f2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
appender, err := NewPdfAppender(pdf1)
|
appender, err := model.NewPdfAppender(pdf1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
@ -85,7 +99,7 @@ func TestAppenderAddPage2(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f1.Close()
|
defer f1.Close()
|
||||||
pdf1, err := NewPdfReader(f1)
|
pdf1, err := model.NewPdfReader(f1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
@ -96,13 +110,13 @@ func TestAppenderAddPage2(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f2.Close()
|
defer f2.Close()
|
||||||
pdf2, err := NewPdfReader(f2)
|
pdf2, err := model.NewPdfReader(f2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
appender, err := NewPdfAppender(pdf1)
|
appender, err := model.NewPdfAppender(pdf1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
@ -126,13 +140,13 @@ func TestAppenderRemovePage(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f1.Close()
|
defer f1.Close()
|
||||||
pdf1, err := NewPdfReader(f1)
|
pdf1, err := model.NewPdfReader(f1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
appender, err := NewPdfAppender(pdf1)
|
appender, err := model.NewPdfAppender(pdf1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
@ -155,7 +169,7 @@ func TestAppenderReplacePage(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f1.Close()
|
defer f1.Close()
|
||||||
pdf1, err := NewPdfReader(f1)
|
pdf1, err := model.NewPdfReader(f1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
@ -167,13 +181,13 @@ func TestAppenderReplacePage(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f2.Close()
|
defer f2.Close()
|
||||||
pdf2, err := NewPdfReader(f2)
|
pdf2, err := model.NewPdfReader(f2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
appender, err := NewPdfAppender(pdf1)
|
appender, err := model.NewPdfAppender(pdf1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
@ -196,21 +210,21 @@ func TestAppenderAddAnnotation(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f1.Close()
|
defer f1.Close()
|
||||||
pdf1, err := NewPdfReader(f1)
|
pdf1, err := model.NewPdfReader(f1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
appender, err := NewPdfAppender(pdf1)
|
appender, err := model.NewPdfAppender(pdf1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
page := pdf1.PageList[0]
|
page := pdf1.PageList[0]
|
||||||
annotation := NewPdfAnnotationSquare()
|
annotation := model.NewPdfAnnotationSquare()
|
||||||
rect := PdfRectangle{Ury: 250.0, Urx: 150.0, Lly: 50.0, Llx: 50.0}
|
rect := model.PdfRectangle{Ury: 250.0, Urx: 150.0, Lly: 50.0, Llx: 50.0}
|
||||||
annotation.Rect = rect.ToPdfObject()
|
annotation.Rect = rect.ToPdfObject()
|
||||||
annotation.IC = core.MakeArrayFromFloats([]float64{4.0, 0.0, 0.3})
|
annotation.IC = core.MakeArrayFromFloats([]float64{4.0, 0.0, 0.3})
|
||||||
annotation.CA = core.MakeFloat(0.5)
|
annotation.CA = core.MakeFloat(0.5)
|
||||||
@ -233,7 +247,7 @@ func TestAppenderMergePage(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f1.Close()
|
defer f1.Close()
|
||||||
pdf1, err := NewPdfReader(f1)
|
pdf1, err := model.NewPdfReader(f1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
@ -245,13 +259,13 @@ func TestAppenderMergePage(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f2.Close()
|
defer f2.Close()
|
||||||
pdf2, err := NewPdfReader(f2)
|
pdf2, err := model.NewPdfReader(f2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
appender, err := NewPdfAppender(pdf1)
|
appender, err := model.NewPdfAppender(pdf1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
@ -278,7 +292,7 @@ func TestAppenderMergePage2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer f1.Close()
|
defer f1.Close()
|
||||||
|
|
||||||
pdf1, err := NewPdfReader(f1)
|
pdf1, err := model.NewPdfReader(f1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
@ -291,13 +305,13 @@ func TestAppenderMergePage2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer f2.Close()
|
defer f2.Close()
|
||||||
|
|
||||||
pdf2, err := NewPdfReader(f2)
|
pdf2, err := model.NewPdfReader(f2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
appender, err := NewPdfAppender(pdf1)
|
appender, err := model.NewPdfAppender(pdf1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
@ -325,7 +339,7 @@ func TestAppenderMergePage3(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f1.Close()
|
defer f1.Close()
|
||||||
pdf1, err := NewPdfReader(f1)
|
pdf1, err := model.NewPdfReader(f1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
@ -338,13 +352,13 @@ func TestAppenderMergePage3(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer f2.Close()
|
defer f2.Close()
|
||||||
|
|
||||||
pdf2, err := NewPdfReader(f2)
|
pdf2, err := model.NewPdfReader(f2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
appender, err := NewPdfAppender(pdf1)
|
appender, err := model.NewPdfAppender(pdf1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Fail: %v\n", err)
|
t.Errorf("Fail: %v\n", err)
|
||||||
return
|
return
|
||||||
@ -362,3 +376,249 @@ func TestAppenderMergePage3(t *testing.T) {
|
|||||||
return
|
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.
|
// 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 {
|
func (f *PdfField) ToPdfObject() core.PdfObject {
|
||||||
container := f.container
|
container := f.container
|
||||||
d := container.PdfObject.(*core.PdfObjectDictionary)
|
d := container.PdfObject.(*core.PdfObjectDictionary)
|
||||||
|
|
||||||
d.SetIfNotNil("FT", f.FT)
|
// Create an array of the kids (fields or widgets).
|
||||||
if f.Parent != nil {
|
kids := core.MakeArray()
|
||||||
d.Set("Parent", f.Parent.GetContainingPdfObject())
|
for _, child := range f.Kids {
|
||||||
|
kids.Append(child.ToPdfObject())
|
||||||
|
}
|
||||||
|
for _, annot := range f.Annotations {
|
||||||
|
kids.Append(annot.GetContext().ToPdfObject())
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Kids != nil {
|
// Set fields.
|
||||||
// Create an array of the kids (fields or widgets).
|
if f.Parent != nil {
|
||||||
kids := core.MakeArray()
|
d.SetIfNotNil("Parent", f.Parent.GetContainingPdfObject())
|
||||||
for _, child := range f.Kids {
|
}
|
||||||
kids.Append(child.ToPdfObject())
|
if kids.Len() > 0 {
|
||||||
}
|
|
||||||
d.Set("Kids", kids)
|
d.Set("Kids", kids)
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Annotations != nil {
|
d.SetIfNotNil("FT", f.FT)
|
||||||
_, 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("T", f.T)
|
d.SetIfNotNil("T", f.T)
|
||||||
d.SetIfNotNil("TU", f.TU)
|
d.SetIfNotNil("TU", f.TU)
|
||||||
d.SetIfNotNil("TM", f.TM)
|
d.SetIfNotNil("TM", f.TM)
|
||||||
d.SetIfNotNil("Ff", f.Ff)
|
d.SetIfNotNil("Ff", f.Ff)
|
||||||
if f.V != nil {
|
d.SetIfNotNil("V", f.V)
|
||||||
d.Set("V", f.V)
|
d.SetIfNotNil("DV", f.DV)
|
||||||
}
|
d.SetIfNotNil("AA", f.AA)
|
||||||
if f.DV != nil {
|
|
||||||
d.Set("DV", f.DV)
|
|
||||||
}
|
|
||||||
if f.AA != nil {
|
|
||||||
d.Set("AA", f.AA)
|
|
||||||
}
|
|
||||||
|
|
||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
@ -446,28 +433,38 @@ func (ch *PdfFieldChoice) ToPdfObject() core.PdfObject {
|
|||||||
// the name of the signer and verifying document contents.
|
// the name of the signer and verifying document contents.
|
||||||
type PdfFieldSignature struct {
|
type PdfFieldSignature struct {
|
||||||
*PdfField
|
*PdfField
|
||||||
|
|
||||||
V *PdfSignature
|
V *PdfSignature
|
||||||
Lock *core.PdfIndirectObject
|
Lock *core.PdfIndirectObject
|
||||||
SV *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.
|
// ToPdfObject returns an indirect object containing the signature field dictionary.
|
||||||
func (sig *PdfFieldSignature) ToPdfObject() core.PdfObject {
|
func (sig *PdfFieldSignature) ToPdfObject() core.PdfObject {
|
||||||
// Set general field attributes
|
// Set general field attributes
|
||||||
sig.PdfField.ToPdfObject()
|
sig.PdfField.ToPdfObject()
|
||||||
container := sig.container
|
|
||||||
|
|
||||||
// Handle signature field specific attributes
|
// Handle signature field specific attributes
|
||||||
|
container := sig.container
|
||||||
|
|
||||||
d := container.PdfObject.(*core.PdfObjectDictionary)
|
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 {
|
if sig.V != nil {
|
||||||
d.Set("V", sig.V.ToPdfObject())
|
d.SetIfNotNil("V", sig.V.ToPdfObject())
|
||||||
}
|
|
||||||
if sig.Lock != nil {
|
|
||||||
d.Set("Lock", sig.Lock)
|
|
||||||
}
|
|
||||||
if sig.SV != nil {
|
|
||||||
d.Set("SV", sig.SV)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return container
|
return container
|
||||||
|
@ -53,6 +53,9 @@ func flattenFields(field *PdfField) []*PdfField {
|
|||||||
|
|
||||||
// AllFields returns a flattened list of all fields in the form.
|
// AllFields returns a flattened list of all fields in the form.
|
||||||
func (form *PdfAcroForm) AllFields() []*PdfField {
|
func (form *PdfAcroForm) AllFields() []*PdfField {
|
||||||
|
if form == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
var fields []*PdfField
|
var fields []*PdfField
|
||||||
if form.Fields != nil {
|
if form.Fields != nil {
|
||||||
for _, field := range *form.Fields {
|
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
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/unidoc/unidoc/common"
|
"github.com/unidoc/unidoc/common"
|
||||||
"github.com/unidoc/unidoc/pdf/core"
|
"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.
|
// 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).
|
// (Section 12.8, Table 252 - Entries in a signature dictionary p. 475 in PDF32000_2008).
|
||||||
type PdfSignature struct {
|
type PdfSignature struct {
|
||||||
|
Handler SignatureHandler
|
||||||
container *core.PdfIndirectObject
|
container *core.PdfIndirectObject
|
||||||
|
|
||||||
// Type: Sig/DocTimeStamp
|
// Type: Sig/DocTimeStamp
|
||||||
Type *core.PdfObjectName
|
Type *core.PdfObjectName
|
||||||
Filter *core.PdfObjectName
|
Filter *core.PdfObjectName
|
||||||
@ -35,14 +104,21 @@ type PdfSignature struct {
|
|||||||
PropAuthType *core.PdfObjectName
|
PropAuthType *core.PdfObjectName
|
||||||
}
|
}
|
||||||
|
|
||||||
// PdfSignatureReference represents a signature reference dictionary.
|
// NewPdfSignature creates a new PdfSignature object.
|
||||||
// (Table 253 - p. 477 in PDF32000_2008).
|
func NewPdfSignature(handler SignatureHandler) *PdfSignature {
|
||||||
type PdfSignatureReference struct {
|
sig := &PdfSignature{
|
||||||
// Type: SigRef
|
Type: core.MakeName("Sig"),
|
||||||
TransformMethod *core.PdfObjectName
|
Handler: handler,
|
||||||
TransformParams *core.PdfObjectDictionary
|
}
|
||||||
Data core.PdfObject
|
|
||||||
DigestMethod *core.PdfObjectName
|
dict := &pdfSignDictionary{
|
||||||
|
PdfObjectDictionary: core.MakeDict(),
|
||||||
|
handler: &handler,
|
||||||
|
signature: sig,
|
||||||
|
}
|
||||||
|
|
||||||
|
sig.container = core.MakeIndirectObject(dict)
|
||||||
|
return sig
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContainingPdfObject implements interface PdfModel.
|
// GetContainingPdfObject implements interface PdfModel.
|
||||||
@ -50,48 +126,68 @@ func (sig *PdfSignature) GetContainingPdfObject() core.PdfObject {
|
|||||||
return sig.container
|
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.
|
// ToPdfObject implements interface PdfModel.
|
||||||
func (sig *PdfSignature) ToPdfObject() core.PdfObject {
|
func (sig *PdfSignature) ToPdfObject() core.PdfObject {
|
||||||
container := sig.container
|
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.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 {
|
// NOTE: ByteRange and Contents need to be updated dynamically.
|
||||||
dict.Set("Filter", sig.Filter)
|
// TODO: Currently dynamic update is only in the appender, need to support
|
||||||
}
|
// in the PdfWriter too for the initial signature on document creation.
|
||||||
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.
|
|
||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,106 +244,3 @@ func (r *PdfReader) newPdfSignatureFromIndirect(container *core.PdfIndirectObjec
|
|||||||
|
|
||||||
return sig, nil
|
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"
|
"math"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/unidoc/unidoc/pdf/core"
|
"github.com/unidoc/unidoc/pdf/core"
|
||||||
)
|
)
|
||||||
@ -93,6 +94,21 @@ type PdfDate struct {
|
|||||||
utOffsetMins int64 // mm (00-59)
|
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})?`)
|
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).
|
// 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)
|
newObj := core.PdfObjectBool(*t)
|
||||||
objectToObjectCopyMap[obj] = &newObj
|
objectToObjectCopyMap[obj] = &newObj
|
||||||
return &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:
|
default:
|
||||||
common.Log.Info("TODO(a5i): implement copyObject for %+v", obj)
|
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 {
|
if pobj, isIndirect := obj.(*core.PdfIndirectObject); isIndirect {
|
||||||
w.crossReferenceMap[num] = crossReference{Type: 1, Offset: w.writePos, Generation: pobj.GenerationNumber}
|
w.crossReferenceMap[num] = crossReference{Type: 1, Offset: w.writePos, Generation: pobj.GenerationNumber}
|
||||||
outStr := fmt.Sprintf("%d 0 obj\n", num)
|
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 += pobj.PdfObject.WriteString()
|
||||||
outStr += "\nendobj\n"
|
outStr += "\nendobj\n"
|
||||||
w.writeString(outStr)
|
w.writeString(outStr)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user