Change the prototype of the appender Sign method

This commit is contained in:
Adrian-George Bostan 2019-02-15 20:59:17 +02:00
parent c2d1efb653
commit 28ad01f4b9
4 changed files with 133 additions and 220 deletions

View File

@ -13,7 +13,6 @@ import (
"os"
"strconv"
"strings"
"time"
"github.com/unidoc/unidoc/common"
"github.com/unidoc/unidoc/pdf/core"
@ -381,61 +380,58 @@ func (a *PdfAppender) ReplacePage(pageNum int, page *PdfPage) {
}
// Sign signs a specific page with a digital signature using a specified signature handler.
// Returns an Acroform and PdfFieldSignature that can be used to customize the signature appearance.
func (a *PdfAppender) Sign(pageNum int, handler SignatureHandler) (acroForm *PdfAcroForm, signatureField *PdfFieldSignature, err error) {
acroForm = a.Reader.AcroForm
// Returns a PdfFieldSignature that can be used to customize the signature appearance.
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("field signature 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()
}
pageIndex := pageNum - 1
if pageIndex < 0 || pageIndex > len(a.pages)-1 {
return nil, nil, fmt.Errorf("page %d not found", pageNum)
}
page := a.pages[pageIndex].Duplicate()
// TODO add more checks before set the fields
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
sig := NewPdfSignature()
sig.M = core.MakeString(time.Now().Format("D:20060102150405-07'00'"))
//sig.M = core.MakeString("D:20150226112648Z")
sig.Type = core.MakeName("Sig")
sig.Reason = core.MakeString("Test1")
if err := handler.InitSignature(sig); err != nil {
return nil, nil, err
}
a.addNewObjects(sig.container)
signatureField = NewPdfFieldSignature()
fields := append(acroForm.AllFields(), signatureField.PdfField)
fields := append(acroForm.AllFields(), field.PdfField)
acroForm.Fields = &fields
a.ReplaceAcroForm(acroForm)
// Replace original page.
procPage(page)
signatureField.FT = core.MakeName("Sig")
signatureField.V = sig
signatureField.T = core.MakeString("Signature1")
signatureField.F = core.MakeInteger(132)
signatureField.P = page.ToPdfObject()
signatureField.Rect = core.MakeArray(
core.MakeInteger(0),
core.MakeInteger(0),
core.MakeInteger(0),
core.MakeInteger(0),
)
// Add the signature field to the page annotations.
page.Annotations = append(page.Annotations, signatureField.PdfAnnotationWidget.PdfAnnotation)
a.pages[pageIndex] = page
// Update acroform.
a.ReplaceAcroForm(acroForm)
return acroForm, signatureField, nil
return nil
}
// ReplaceAcroForm replaces the acrobat form. It appends a new form to the Pdf which

View File

@ -13,6 +13,7 @@ import (
"os"
"path/filepath"
"testing"
"time"
"golang.org/x/crypto/pkcs12"
@ -447,15 +448,33 @@ func TestAppenderSignPage4(t *testing.T) {
t.Errorf("Fail: %v\n", err)
return
}
_, appearance, err := appender.Sign(1, handler)
if err != nil {
// 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
}
appearance.V.Name = core.MakeString("Test Appender")
appearance.V.Reason = core.MakeString("TestAppenderSignPage4")
err = appender.WriteToFile(tempFile("appender_sign_page_4.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
@ -513,16 +532,34 @@ func TestAppenderSignMultiple(t *testing.T) {
f.Close()
return
}
_, appearance, err := appender.Sign(1, handler)
if err != nil {
// 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
}
appearance.V.Name = core.MakeString(fmt.Sprintf("Test Appender - Round %d", i+1))
appearance.V.Reason = core.MakeString("TestAppenderSignPage4")
outPath := tempFile(fmt.Sprintf("appender_sign_multiple_%d.pdf", i+1))
err = appender.WriteToFile(outPath)

View File

@ -433,7 +433,6 @@ func (ch *PdfFieldChoice) ToPdfObject() core.PdfObject {
// the name of the signer and verifying document contents.
type PdfFieldSignature struct {
*PdfField
*PdfAnnotationWidget
V *PdfSignature
Lock *core.PdfIndirectObject
@ -441,22 +440,20 @@ type PdfFieldSignature struct {
}
// NewPdfFieldSignature returns an initialized signature field.
func NewPdfFieldSignature() *PdfFieldSignature {
sig := &PdfFieldSignature{}
sig.PdfField = NewPdfField()
sig.PdfAnnotationWidget = NewPdfAnnotationWidget()
sig.PdfField.SetContext(sig)
sig.PdfAnnotationWidget.SetContext(sig)
sig.PdfAnnotationWidget.container = sig.PdfField.container
return sig
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
if sig.PdfAnnotationWidget != nil {
sig.PdfAnnotation.ToPdfObject()
}
sig.PdfField.ToPdfObject()
// Handle signature field specific attributes

View File

@ -7,6 +7,8 @@ package model
import (
"bytes"
"errors"
"time"
"github.com/unidoc/unidoc/common"
"github.com/unidoc/unidoc/pdf/core"
@ -80,6 +82,7 @@ func (d *pdfSignDictionary) WriteString() string {
type PdfSignature struct {
Handler SignatureHandler
container *core.PdfIndirectObject
// Type: Sig/DocTimeStamp
Type *core.PdfObjectName
Filter *core.PdfObjectName
@ -102,25 +105,20 @@ type PdfSignature struct {
}
// NewPdfSignature creates a new PdfSignature object.
func NewPdfSignature() *PdfSignature {
sig := &PdfSignature{}
sigDict := &pdfSignDictionary{
PdfObjectDictionary: core.MakeDict(),
handler: &sig.Handler,
signature: sig,
}
sig.container = core.MakeIndirectObject(sigDict)
return sig
func NewPdfSignature(handler SignatureHandler) *PdfSignature {
sig := &PdfSignature{
Type: core.MakeName("Sig"),
Handler: handler,
}
// 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
dict := &pdfSignDictionary{
PdfObjectDictionary: core.MakeDict(),
handler: &handler,
signature: sig,
}
sig.container = core.MakeIndirectObject(dict)
return sig
}
// GetContainingPdfObject implements interface PdfModel.
@ -128,6 +126,34 @@ 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)
}
// 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
@ -213,146 +239,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
Lock *core.PdfIndirectObject
SV *core.PdfIndirectObject
Kids *core.PdfObjectArray
}
// 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)
// Set fields.
if sf.V != nil {
dict.Set("V", sf.V.ToPdfObject())
}
dict.Set("FT", core.MakeName("Sig"))
dict.SetIfNotNil("Lock", sf.Lock)
dict.SetIfNotNil("SV", sf.SV)
dict.SetIfNotNil("Kids", sf.Kids)
// 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 {
container *core.PdfIndirectObject
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
}
func (pss *PdfSignatureFieldSeed) ToPdfObject() core.PdfObject {
container := pss.container
dict := container.PdfObject.(*core.PdfObjectDictionary)
dict.SetIfNotNil("Ff", pss.Ff)
dict.SetIfNotNil("Filter", pss.Filter)
dict.SetIfNotNil("SubFilter", pss.SubFilter)
dict.SetIfNotNil("DigestMethod", pss.DigestMethod)
dict.SetIfNotNil("V", pss.V)
dict.SetIfNotNil("Cert", pss.Cert)
dict.SetIfNotNil("Reasons", pss.Reasons)
dict.SetIfNotNil("MDP", pss.MDP)
dict.SetIfNotNil("TimeStamp", pss.TimeStamp)
dict.SetIfNotNil("LegalAttestation", pss.LegalAttestation)
dict.SetIfNotNil("AddRevInfo", pss.AddRevInfo)
dict.SetIfNotNil("LockDocument", pss.LockDocument)
dict.SetIfNotNil("AppearanceFilter", pss.AppearanceFilter)
return container
}
// PdfCertificateSeed represents certificate seed value dictionary.
// (Table 235 - p. 457 in PDF32000_2008).
type PdfCertificateSeed struct {
container *core.PdfIndirectObject
// 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
}
func (pcs *PdfCertificateSeed) ToPdfObject() core.PdfObject {
container := pcs.container
dict := container.PdfObject.(*core.PdfObjectDictionary)
dict.SetIfNotNil("Ff", pcs.Ff)
dict.SetIfNotNil("Subject", pcs.Subject)
dict.SetIfNotNil("SignaturePolicyOID", pcs.SignaturePolicyOID)
dict.SetIfNotNil("SignaturePolicyHashValue", pcs.SignaturePolicyHashValue)
dict.SetIfNotNil("SignaturePolicyHashAlgorithm", pcs.SignaturePolicyHashAlgorithm)
dict.SetIfNotNil("SignaturePolicyCommitmentType", pcs.SignaturePolicyCommitmentType)
dict.SetIfNotNil("SubjectDN", pcs.SubjectDN)
dict.SetIfNotNil("KeyUsage", pcs.KeyUsage)
dict.SetIfNotNil("Issuer", pcs.Issuer)
dict.SetIfNotNil("OID", pcs.OID)
dict.SetIfNotNil("URL", pcs.URL)
dict.SetIfNotNil("URLType", pcs.URLType)
return container
}