Merge pull request #334 from unidoc/v3-a5i-pdf-sign

WIP: Initial digital signature support (v3)
This commit is contained in:
Gunnsteinn Hall 2019-02-19 17:33:12 +00:00 committed by GitHub
commit c8b70efef3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1222 additions and 245 deletions

View File

@ -293,6 +293,9 @@ func (str *PdfObjectString) Str() string {
// UTF-16BE is applied when the first two bytes are 0xFE, 0XFF, otherwise decoding of
// PDFDocEncoding is performed.
func (str *PdfObjectString) Decoded() string {
if str == nil {
return ""
}
b := []byte(str.val)
if len(b) >= 2 && b[0] == 0xFE && b[1] == 0xFF {
// UTF16BE.

View File

@ -18,7 +18,7 @@ import (
"github.com/unidoc/unidoc/pdf/core"
)
// PdfAppender appends a new Pdf content to an existing Pdf document.
// PdfAppender appends new PDF content to an existing PDF document via incremental updates.
type PdfAppender struct {
rs io.ReadSeeker
parser *core.PdfParser
@ -30,8 +30,11 @@ type PdfAppender struct {
xrefs core.XrefTable
greatestObjNum int
// List of new objects and a map for quick lookups.
newObjects []core.PdfObject
hasNewObject map[core.PdfObject]struct{}
written bool
}
func getPageResources(p *PdfPage) map[core.PdfObjectName]core.PdfObject {
@ -94,18 +97,24 @@ func getPageResources(p *PdfPage) map[core.PdfObjectName]core.PdfObject {
// NewPdfAppender creates a new Pdf appender from a Pdf reader.
func NewPdfAppender(reader *PdfReader) (*PdfAppender, error) {
a := &PdfAppender{}
a.rs = reader.rs
a.Reader = reader
a.parser = a.Reader.parser
a := &PdfAppender{
rs: reader.rs,
Reader: reader,
parser: reader.parser,
}
if _, err := a.rs.Seek(0, io.SeekStart); err != nil {
return nil, err
}
var err error
// Create a readonly (immutable) reader. It increases memory using.
// Why? We can not check the original reader objects are changed or not.
// When we merge, replace a page content. The new page will contain objects from the readonly reader and other objects.
// The readonly objects won't append to the result Pdf file. This check is not resource demanding. It checks indirect objects owners only.
// Create a readonly (immutable) reader. It increases memory use but is necessary to be able
// to detect changes in the original reader objects.
//
// In the case where an existing page is modified, the page contents are replaced upon merging
// (appending). The new page will refer to objects from the read-only reader and new instances
// of objects that have been changes. Objects from the original reader are not appended, only
// new objects that modify the PDF. The change detection check is not resource demanding. It
// only checks owners (source) of indirect objects.
a.roReader, err = NewPdfReader(a.rs)
if err != nil {
return nil, err
@ -131,8 +140,8 @@ func (a *PdfAppender) addNewObjects(obj core.PdfObject) {
}
switch v := obj.(type) {
case *core.PdfIndirectObject:
// Check the object is changing.
// If the indirect object has not the readonly parser then the object is changed.
// If the current parser is different from the read-only parser, then
// the object has changed.
if v.GetParser() != a.roReader.parser {
a.newObjects = append(a.newObjects, obj)
a.hasNewObject[obj] = struct{}{}
@ -147,22 +156,25 @@ func (a *PdfAppender) addNewObjects(obj core.PdfObject) {
a.addNewObjects(v.Get(key))
}
case *core.PdfObjectStreams:
// Check the object is changing.
// If the indirect object has not the readonly parser then the object is changed.
// If the current parser is different from the read-only parser, then
// the object has changed.
if v.GetParser() != a.roReader.parser {
for _, o := range v.Elements() {
a.addNewObjects(o)
}
}
case *core.PdfObjectStream:
// Check the object is changing.
// If the indirect object has the readonly parser then the object is not changed.
if v.GetParser() == a.roReader.parser {
// If the current parser is different from the read-only parser, then
// the object has changed.
parser := v.GetParser()
if parser == a.roReader.parser {
return
}
// If the indirect object has not the origin parser then the object may be changed orr not.
if v.GetParser() == a.Reader.parser {
// Check data is not changed.
// If the current parser is different from the parser of the reader,
// then the object may have changed.
if parser == a.Reader.parser {
// Check if data has changed.
if streamObj, err := a.roReader.parser.LookupByReference(v.PdfObjectReference); err == nil {
var isNotChanged bool
if stream, ok := core.GetStream(streamObj); ok && bytes.Equal(stream.Stream, v.Stream) {
@ -331,7 +343,7 @@ func (a *PdfAppender) MergePageWith(pageNum int, page *PdfPage) error {
return nil
}
// AddPages adds pages to end of the source Pdf.
// AddPages adds pages to be appended to the end of the source PDF.
func (a *PdfAppender) AddPages(pages ...*PdfPage) {
for _, page := range pages {
page = page.Duplicate()
@ -370,19 +382,74 @@ func (a *PdfAppender) ReplacePage(pageNum int, page *PdfPage) {
}
}
// ReplaceAcroForm replaces the acrobat form. It appends a new form to the Pdf which replaces the original acrobat form.
// Sign signs a specific page with a digital signature.
// The signature field parameter must have a valid signature dictionary
// specified by its V field.
func (a *PdfAppender) Sign(pageNum int, field *PdfFieldSignature) error {
if field == nil {
return errors.New("signature field cannot be nil")
}
signature := field.V
if signature == nil {
return errors.New("signature dictionary cannot be nil")
}
// Get a copy of the selected page.
pageIndex := pageNum - 1
if pageIndex < 0 || pageIndex > len(a.pages)-1 {
return fmt.Errorf("page %d not found", pageNum)
}
page := a.pages[pageIndex].Duplicate()
// Initialize signature.
if err := signature.Initialize(); err != nil {
return err
}
a.addNewObjects(signature.container)
// Add signature field annotations to the page annotations.
for _, annotation := range field.Annotations {
annotation.P = page.ToPdfObject()
page.Annotations = append(page.Annotations, annotation.PdfAnnotation)
}
// Add signature field to the form.
acroForm := a.Reader.AcroForm
if acroForm == nil {
acroForm = NewPdfAcroForm()
}
acroForm.SigFlags = core.MakeInteger(3)
acroForm.DA = core.MakeString("/F1 0 Tf 0 g")
n2ResourcesFont := core.MakeDict()
n2ResourcesFont.Set("F1", DefaultFont().ToPdfObject())
acroForm.DR = NewPdfPageResources()
acroForm.DR.Font = n2ResourcesFont
fields := append(acroForm.AllFields(), field.PdfField)
acroForm.Fields = &fields
a.ReplaceAcroForm(acroForm)
// Replace original page.
procPage(page)
a.pages[pageIndex] = page
return nil
}
// ReplaceAcroForm replaces the acrobat form. It appends a new form to the Pdf which
// replaces the original AcroForm.
func (a *PdfAppender) ReplaceAcroForm(acroForm *PdfAcroForm) {
a.acroForm = acroForm
}
// Write writes the Appender output to io.Writer.
// It can only be called once and further invocations will result in an error.
func (a *PdfAppender) Write(w io.Writer) error {
if _, err := a.rs.Seek(0, io.SeekStart); err != nil {
return err
}
offset, err := io.Copy(w, a.rs)
if err != nil {
return err
if a.written {
return errors.New("appender write can only be invoked once")
}
writer := NewPdfWriter()
@ -446,7 +513,7 @@ func (a *PdfAppender) Write(w io.Writer) error {
common.Log.Trace("Page Parent: %T", parent)
parentDict, ok := parent.PdfObject.(*core.PdfObjectDictionary)
if !ok {
return errors.New("Invalid Parent object")
return errors.New("invalid Parent object")
}
for _, field := range inheritedFields {
common.Log.Trace("Field %s", field)
@ -473,6 +540,56 @@ func (a *PdfAppender) Write(w io.Writer) error {
writer.SetForms(a.acroForm)
}
if _, err := a.rs.Seek(0, io.SeekStart); err != nil {
return err
}
// Digital signature handling: Check if any of the new objects represent a signature dictionary.
// The byte range is later updated dynamically based on the position of the actual signature
// Contents.
digestWriters := make(map[SignatureHandler]io.Writer)
byteRange := core.MakeArray()
for _, obj := range a.newObjects {
if ind, found := core.GetIndirect(obj); found {
if sigDict, found := ind.PdfObject.(*pdfSignDictionary); found {
handler := *sigDict.handler
var err error
digestWriters[handler], err = handler.NewDigest(sigDict.signature)
if err != nil {
return err
}
byteRange.Append(core.MakeInteger(0xfffff), core.MakeInteger(0xfffff))
}
}
}
if byteRange.Len() > 0 {
byteRange.Append(core.MakeInteger(0xfffff), core.MakeInteger(0xfffff))
}
for _, obj := range a.newObjects {
if ind, found := core.GetIndirect(obj); found {
if sigDict, found := ind.PdfObject.(*pdfSignDictionary); found {
sigDict.Set("ByteRange", byteRange)
}
}
}
hasSigDict := len(digestWriters) > 0
var reader io.Reader = a.rs
if hasSigDict {
writers := make([]io.Writer, 0, len(digestWriters))
for _, hash := range digestWriters {
writers = append(writers, hash)
}
reader = io.TeeReader(a.rs, io.MultiWriter(writers...))
}
// Write the original PDF.
offset, err := io.Copy(w, reader)
if err != nil {
return err
}
if len(a.newObjects) == 0 {
return nil
}
@ -481,14 +598,108 @@ func (a *PdfAppender) Write(w io.Writer) error {
writer.ObjNumOffset = a.greatestObjNum
writer.appendMode = true
writer.appendToXrefs = a.xrefs
writer.minorVersion = 7
for _, obj := range a.newObjects {
writer.addObject(obj)
}
if err := writer.Write(w); err != nil {
writerW := w
if hasSigDict {
// For signatures, we need to write twice. First to find the byte offset
// of the Contents and then dynamically update the file with the
// signature and ByteRange.
writerW = bytes.NewBuffer(nil)
}
// Perform the write. For signatures will do a mock write to a buffer.
if err := writer.Write(writerW); err != nil {
return err
}
return err
// TODO(gunnsth): Consider whether the dynamic content can be handled efficiently with generic write hooks?
// Logic is getting pretty complex here.
if hasSigDict {
// Update the byteRanges based on mock write.
bufferData := writerW.(*bytes.Buffer).Bytes()
byteRange := core.MakeArray()
var sigDicts []*pdfSignDictionary
var lastPosition int64
for _, obj := range writer.objects {
if ind, found := core.GetIndirect(obj); found {
if sigDict, found := ind.PdfObject.(*pdfSignDictionary); found {
sigDicts = append(sigDicts, sigDict)
newPosition := sigDict.fileOffset + int64(sigDict.contentsOffsetStart)
byteRange.Append(
core.MakeInteger(lastPosition),
core.MakeInteger(newPosition-lastPosition),
)
lastPosition = sigDict.fileOffset + int64(sigDict.contentsOffsetEnd)
}
}
}
byteRange.Append(
core.MakeInteger(lastPosition),
core.MakeInteger(offset+int64(len(bufferData))-lastPosition),
)
// set the ByteRange value
byteRangeData := []byte(byteRange.WriteString())
for _, sigDict := range sigDicts {
bufferOffset := int(sigDict.fileOffset - offset)
for i := sigDict.byteRangeOffsetStart; i < sigDict.byteRangeOffsetEnd; i++ {
bufferData[bufferOffset+i] = ' '
}
dst := bufferData[bufferOffset+sigDict.byteRangeOffsetStart : bufferOffset+sigDict.byteRangeOffsetEnd]
copy(dst, byteRangeData)
}
var prevOffset int
for _, sigDict := range sigDicts {
bufferOffset := int(sigDict.fileOffset - offset)
data := bufferData[prevOffset : bufferOffset+sigDict.contentsOffsetStart]
handler := *sigDict.handler
digestWriters[handler].Write(data)
prevOffset = bufferOffset + sigDict.contentsOffsetEnd
}
for _, sigDict := range sigDicts {
data := bufferData[prevOffset:]
handler := *sigDict.handler
digestWriters[handler].Write(data)
}
for _, sigDict := range sigDicts {
bufferOffset := int(sigDict.fileOffset - offset)
handler := *sigDict.handler
digest := digestWriters[handler]
if err := handler.Sign(sigDict.signature, digest); err != nil {
return err
}
contents := []byte(sigDict.signature.Contents.WriteString())
// Empty out the ByteRange and Content data.
// FIXME(gunnsth): Is this needed? Seems like the correct data is copied below? Prefer
// to keep the rest space?
for i := sigDict.byteRangeOffsetStart; i < sigDict.byteRangeOffsetEnd; i++ {
bufferData[bufferOffset+i] = ' '
}
for i := sigDict.contentsOffsetStart; i < sigDict.contentsOffsetEnd; i++ {
bufferData[bufferOffset+i] = ' '
}
// Copy the actual ByteRange and Contents data into the buffer prepared by first write.
dst := bufferData[bufferOffset+sigDict.byteRangeOffsetStart : bufferOffset+sigDict.byteRangeOffsetEnd]
copy(dst, byteRangeData)
dst = bufferData[bufferOffset+sigDict.contentsOffsetStart : bufferOffset+sigDict.contentsOffsetEnd]
copy(dst, contents)
}
buffer := bytes.NewBuffer(bufferData)
_, err = io.Copy(w, buffer)
if err != nil {
return err
}
}
a.written = true
return nil
}
// WriteToFile writes the Appender output to file specified by path.

View File

@ -3,20 +3,29 @@
* file 'LICENSE.md', which is part of this source code package.
*/
package model
package model_test
import (
"bytes"
"crypto/rsa"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"time"
"golang.org/x/crypto/pkcs12"
"github.com/unidoc/unidoc/common"
"github.com/unidoc/unidoc/pdf/core"
"github.com/unidoc/unidoc/pdf/model"
"github.com/unidoc/unidoc/pdf/model/sighandler"
)
// This test file contains multiple tests to generate PDFs from existing Pdf files. The outputs are written into /tmp as files. The files
// themselves need to be observed to check for correctness as we don't have a good way to automatically check
// if every detail is correct.
// This test file contains multiple tests to generate PDFs from existing Pdf files. The outputs are written
// into TMPDIR as files. The files themselves need to be observed to check for correctness as we don't have
// a good way to automatically check if every detail is correct.
func init() {
common.SetLogger(common.NewConsoleLogger(common.LogLevelDebug))
@ -32,6 +41,11 @@ const imgPdfFile2 = "./testdata/img1-2.pdf"
// source http://foersom.com/net/HowTo/data/OoPdfFormExample.pdf
const testPdfAcroFormFile1 = "./testdata/OoPdfFormExample.pdf"
const testPdfSignedPDFDocument = "./testdata/SampleSignedPDFDocument.pdf"
const testPKS12Key = "./testdata/ks12"
const testPKS12KeyPassword = "password"
func tempFile(name string) string {
return filepath.Join(os.TempDir(), name)
}
@ -43,7 +57,7 @@ func TestAppenderAddPage(t *testing.T) {
return
}
defer f1.Close()
pdf1, err := NewPdfReader(f1)
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
@ -54,13 +68,13 @@ func TestAppenderAddPage(t *testing.T) {
return
}
defer f2.Close()
pdf2, err := NewPdfReader(f2)
pdf2, err := model.NewPdfReader(f2)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := NewPdfAppender(pdf1)
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
@ -85,7 +99,7 @@ func TestAppenderAddPage2(t *testing.T) {
return
}
defer f1.Close()
pdf1, err := NewPdfReader(f1)
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
@ -96,13 +110,13 @@ func TestAppenderAddPage2(t *testing.T) {
return
}
defer f2.Close()
pdf2, err := NewPdfReader(f2)
pdf2, err := model.NewPdfReader(f2)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := NewPdfAppender(pdf1)
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
@ -126,13 +140,13 @@ func TestAppenderRemovePage(t *testing.T) {
return
}
defer f1.Close()
pdf1, err := NewPdfReader(f1)
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := NewPdfAppender(pdf1)
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
@ -155,7 +169,7 @@ func TestAppenderReplacePage(t *testing.T) {
return
}
defer f1.Close()
pdf1, err := NewPdfReader(f1)
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
@ -167,13 +181,13 @@ func TestAppenderReplacePage(t *testing.T) {
return
}
defer f2.Close()
pdf2, err := NewPdfReader(f2)
pdf2, err := model.NewPdfReader(f2)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := NewPdfAppender(pdf1)
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
@ -196,21 +210,21 @@ func TestAppenderAddAnnotation(t *testing.T) {
return
}
defer f1.Close()
pdf1, err := NewPdfReader(f1)
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := NewPdfAppender(pdf1)
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
page := pdf1.PageList[0]
annotation := NewPdfAnnotationSquare()
rect := PdfRectangle{Ury: 250.0, Urx: 150.0, Lly: 50.0, Llx: 50.0}
annotation := model.NewPdfAnnotationSquare()
rect := model.PdfRectangle{Ury: 250.0, Urx: 150.0, Lly: 50.0, Llx: 50.0}
annotation.Rect = rect.ToPdfObject()
annotation.IC = core.MakeArrayFromFloats([]float64{4.0, 0.0, 0.3})
annotation.CA = core.MakeFloat(0.5)
@ -233,7 +247,7 @@ func TestAppenderMergePage(t *testing.T) {
return
}
defer f1.Close()
pdf1, err := NewPdfReader(f1)
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
@ -245,13 +259,13 @@ func TestAppenderMergePage(t *testing.T) {
return
}
defer f2.Close()
pdf2, err := NewPdfReader(f2)
pdf2, err := model.NewPdfReader(f2)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := NewPdfAppender(pdf1)
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
@ -278,7 +292,7 @@ func TestAppenderMergePage2(t *testing.T) {
}
defer f1.Close()
pdf1, err := NewPdfReader(f1)
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
@ -291,13 +305,13 @@ func TestAppenderMergePage2(t *testing.T) {
}
defer f2.Close()
pdf2, err := NewPdfReader(f2)
pdf2, err := model.NewPdfReader(f2)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := NewPdfAppender(pdf1)
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
@ -325,7 +339,7 @@ func TestAppenderMergePage3(t *testing.T) {
return
}
defer f1.Close()
pdf1, err := NewPdfReader(f1)
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
@ -338,13 +352,13 @@ func TestAppenderMergePage3(t *testing.T) {
}
defer f2.Close()
pdf2, err := NewPdfReader(f2)
pdf2, err := model.NewPdfReader(f2)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := NewPdfAppender(pdf1)
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
@ -362,3 +376,249 @@ func TestAppenderMergePage3(t *testing.T) {
return
}
}
func validateFile(t *testing.T, fileName string) {
data, err := ioutil.ReadFile(fileName)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
reader, err := model.NewPdfReader(bytes.NewReader(data))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
handler, _ := sighandler.NewAdobeX509RSASHA1(nil, nil)
handler2, _ := sighandler.NewAdobePKCS7Detached(nil, nil)
handlers := []model.SignatureHandler{handler, handler2}
res, err := reader.ValidateSignatures(handlers)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
if len(res) == 0 {
t.Errorf("Fail: signature fields not found")
return
}
if !res[0].IsSigned || !res[0].IsVerified {
t.Errorf("Fail: validation failed")
return
}
for i, item := range res {
t.Logf("== Signature %d", i+1)
t.Logf("%s", item.String())
}
}
func TestAppenderSignPage4(t *testing.T) {
// TODO move to reader_test.go
validateFile(t, testPdfSignedPDFDocument)
f1, err := os.Open(testPdfFile1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f1.Close()
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
f, _ := ioutil.ReadFile(testPKS12Key)
privateKey, cert, err := pkcs12.Decode(f, testPKS12KeyPassword)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
handler, err := sighandler.NewAdobePKCS7Detached(privateKey.(*rsa.PrivateKey), cert)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
// Create signature field and appearance.
signature := model.NewPdfSignature(handler)
signature.SetName("Test Appender")
signature.SetReason("TestAppenderSignPage4")
signature.SetDate(time.Now(), "")
sigField := model.NewPdfFieldSignature(signature)
sigField.T = core.MakeString("Signature1")
widget := model.NewPdfAnnotationWidget()
widget.F = core.MakeInteger(132)
widget.Rect = core.MakeArray(
core.MakeInteger(0),
core.MakeInteger(0),
core.MakeInteger(0),
core.MakeInteger(0),
)
widget.Parent = sigField.GetContainingPdfObject()
sigField.Annotations = append(sigField.Annotations, widget)
if err = appender.Sign(1, sigField); err != nil {
t.Errorf("Fail: %v\n", err)
return
}
err = appender.WriteToFile(tempFile("appender_sign_page_4.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
validateFile(t, tempFile("appender_sign_page_4.pdf"))
}
func TestAppenderSignMultiple(t *testing.T) {
inputPath := testPdfFile1
for i := 0; i < 3; i++ {
f, err := os.Open(inputPath)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
pdfReader, err := model.NewPdfReader(f)
if err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
t.Logf("Fields: %d", len(pdfReader.AcroForm.AllFields()))
if len(pdfReader.AcroForm.AllFields()) != i {
t.Fatalf("fields != %d (got %d)", i, len(pdfReader.AcroForm.AllFields()))
}
t.Logf("Annotations: %d", len(pdfReader.PageList[0].Annotations))
if len(pdfReader.PageList[0].Annotations) != i {
t.Fatalf("page annotations != %d (got %d)", i, len(pdfReader.PageList[0].Annotations))
}
appender, err := model.NewPdfAppender(pdfReader)
if err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
pfxData, _ := ioutil.ReadFile(testPKS12Key)
privateKey, cert, err := pkcs12.Decode(pfxData, testPKS12KeyPassword)
if err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
handler, err := sighandler.NewAdobePKCS7Detached(privateKey.(*rsa.PrivateKey), cert)
if err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
// Create signature field and appearance.
signature := model.NewPdfSignature(handler)
signature.SetName(fmt.Sprintf("Test Appender - Round %d", i+1))
signature.SetReason("TestAppenderSignPage4")
signature.SetDate(time.Now(), "")
sigField := model.NewPdfFieldSignature(signature)
sigField.T = core.MakeString("Signature1")
widget := model.NewPdfAnnotationWidget()
widget.F = core.MakeInteger(132)
widget.Rect = core.MakeArray(
core.MakeInteger(0),
core.MakeInteger(0),
core.MakeInteger(0),
core.MakeInteger(0),
)
widget.Parent = sigField.GetContainingPdfObject()
sigField.Annotations = append(sigField.Annotations, widget)
if err = appender.Sign(1, sigField); err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
outPath := tempFile(fmt.Sprintf("appender_sign_multiple_%d.pdf", i+1))
err = appender.WriteToFile(outPath)
if err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
validateFile(t, outPath)
inputPath = outPath
f.Close()
}
}
// Each Appender can only be written out once, further invokations of Write should result in an error.
func TestAppenderAttemptMultiWrite(t *testing.T) {
f1, err := os.Open(testPdfLoremIpsumFile)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f1.Close()
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
f2, err := os.Open(testPdfFile1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f2.Close()
pdf2, err := model.NewPdfReader(f2)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender.AddPages(pdf1.PageList...)
appender.AddPages(pdf2.PageList...)
appender.AddPages(pdf2.PageList...)
// Write twice to buffer and compare results.
var buf1, buf2 bytes.Buffer
err = appender.Write(&buf1)
if err != nil {
t.Fatalf("Error: %v", err)
}
err = appender.Write(&buf2)
if err == nil {
t.Fatalf("Second invokation of appender.Write should yield an error")
}
}

View File

@ -245,50 +245,37 @@ func (f *PdfField) String() string {
}
// ToPdfObject sets the common field elements.
// Note: Call the more field context's ToPdfObject to set both the generic and non-generic information.
// Note: Call the more field context's ToPdfObject to set both the generic and
// non-generic information.
func (f *PdfField) ToPdfObject() core.PdfObject {
container := f.container
d := container.PdfObject.(*core.PdfObjectDictionary)
d.SetIfNotNil("FT", f.FT)
if f.Parent != nil {
d.Set("Parent", f.Parent.GetContainingPdfObject())
// Create an array of the kids (fields or widgets).
kids := core.MakeArray()
for _, child := range f.Kids {
kids.Append(child.ToPdfObject())
}
for _, annot := range f.Annotations {
kids.Append(annot.GetContext().ToPdfObject())
}
if f.Kids != nil {
// Create an array of the kids (fields or widgets).
kids := core.MakeArray()
for _, child := range f.Kids {
kids.Append(child.ToPdfObject())
}
// Set fields.
if f.Parent != nil {
d.SetIfNotNil("Parent", f.Parent.GetContainingPdfObject())
}
if kids.Len() > 0 {
d.Set("Kids", kids)
}
if f.Annotations != nil {
_, hasKids := d.Get("Kids").(*core.PdfObjectArray)
if !hasKids {
d.Set("Kids", &core.PdfObjectArray{})
}
// TODO: If only 1 widget annotation, it can be merged in.
kids := d.Get("Kids").(*core.PdfObjectArray)
for _, annot := range f.Annotations {
kids.Append(annot.GetContext().ToPdfObject())
}
}
d.SetIfNotNil("FT", f.FT)
d.SetIfNotNil("T", f.T)
d.SetIfNotNil("TU", f.TU)
d.SetIfNotNil("TM", f.TM)
d.SetIfNotNil("Ff", f.Ff)
if f.V != nil {
d.Set("V", f.V)
}
if f.DV != nil {
d.Set("DV", f.DV)
}
if f.AA != nil {
d.Set("AA", f.AA)
}
d.SetIfNotNil("V", f.V)
d.SetIfNotNil("DV", f.DV)
d.SetIfNotNil("AA", f.AA)
return container
}
@ -446,28 +433,38 @@ func (ch *PdfFieldChoice) ToPdfObject() core.PdfObject {
// the name of the signer and verifying document contents.
type PdfFieldSignature struct {
*PdfField
V *PdfSignature
Lock *core.PdfIndirectObject
SV *core.PdfIndirectObject
}
// NewPdfFieldSignature returns an initialized signature field.
func NewPdfFieldSignature(signature *PdfSignature) *PdfFieldSignature {
field := &PdfFieldSignature{
PdfField: NewPdfField(),
V: signature,
}
field.PdfField.SetContext(field)
field.FT = core.MakeName("Sig")
return field
}
// ToPdfObject returns an indirect object containing the signature field dictionary.
func (sig *PdfFieldSignature) ToPdfObject() core.PdfObject {
// Set general field attributes
sig.PdfField.ToPdfObject()
container := sig.container
// Handle signature field specific attributes
container := sig.container
d := container.PdfObject.(*core.PdfObjectDictionary)
d.Set("FT", core.MakeName("Sig"))
d.SetIfNotNil("FT", core.MakeName("Sig"))
d.SetIfNotNil("Lock", sig.Lock)
d.SetIfNotNil("SV", sig.SV)
if sig.V != nil {
d.Set("V", sig.V.ToPdfObject())
}
if sig.Lock != nil {
d.Set("Lock", sig.Lock)
}
if sig.SV != nil {
d.Set("SV", sig.SV)
d.SetIfNotNil("V", sig.V.ToPdfObject())
}
return container

View File

@ -53,6 +53,9 @@ func flattenFields(field *PdfField) []*PdfField {
// AllFields returns a flattened list of all fields in the form.
func (form *PdfAcroForm) AllFields() []*PdfField {
if form == nil {
return nil
}
var fields []*PdfField
if form.Fields != nil {
for _, field := range *form.Fields {

View 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

View 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"
}

View 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"
}

View File

@ -6,14 +6,83 @@
package model
import (
"bytes"
"errors"
"time"
"github.com/unidoc/unidoc/common"
"github.com/unidoc/unidoc/pdf/core"
)
var _ core.PdfObject = &pdfSignDictionary{}
// pdfSignDictionary is used as a wrapper around PdfSignature for digital checksum calculation
// and population of /Contents and /ByteRange.
// Implements interface core.PdfObject.
type pdfSignDictionary struct {
*core.PdfObjectDictionary
handler *SignatureHandler
signature *PdfSignature
fileOffset int64
contentsOffsetStart int
contentsOffsetEnd int
byteRangeOffsetStart int
byteRangeOffsetEnd int
}
// GetSubFilter returns SubFilter value or empty string.
func (d *pdfSignDictionary) GetSubFilter() string {
obj := d.Get("SubFilter")
if obj == nil {
return ""
}
if sf, found := core.GetNameVal(obj); found {
return sf
}
return ""
}
// WriteString outputs the object as it is to be written to file.
func (d *pdfSignDictionary) WriteString() string {
d.contentsOffsetStart = 0
d.contentsOffsetEnd = 0
d.byteRangeOffsetStart = 0
d.byteRangeOffsetEnd = 0
out := bytes.NewBuffer(nil)
out.WriteString("<<")
for _, k := range d.Keys() {
v := d.Get(k)
switch k {
case "ByteRange":
out.WriteString(k.WriteString())
out.WriteString(" ")
d.byteRangeOffsetStart = out.Len()
out.WriteString(v.WriteString())
out.WriteString(" ")
d.byteRangeOffsetEnd = out.Len() - 1
case "Contents":
out.WriteString(k.WriteString())
out.WriteString(" ")
d.contentsOffsetStart = out.Len()
out.WriteString(v.WriteString())
out.WriteString(" ")
d.contentsOffsetEnd = out.Len() - 1
default:
out.WriteString(k.WriteString())
out.WriteString(" ")
out.WriteString(v.WriteString())
}
}
out.WriteString(">>")
return out.String()
}
// PdfSignature represents a PDF signature dictionary and is used for signing via form signature fields.
// (Section 12.8, Table 252 - Entries in a signature dictionary p. 475 in PDF32000_2008).
type PdfSignature struct {
Handler SignatureHandler
container *core.PdfIndirectObject
// Type: Sig/DocTimeStamp
Type *core.PdfObjectName
Filter *core.PdfObjectName
@ -35,14 +104,21 @@ type PdfSignature struct {
PropAuthType *core.PdfObjectName
}
// PdfSignatureReference represents a signature reference dictionary.
// (Table 253 - p. 477 in PDF32000_2008).
type PdfSignatureReference struct {
// Type: SigRef
TransformMethod *core.PdfObjectName
TransformParams *core.PdfObjectDictionary
Data core.PdfObject
DigestMethod *core.PdfObjectName
// NewPdfSignature creates a new PdfSignature object.
func NewPdfSignature(handler SignatureHandler) *PdfSignature {
sig := &PdfSignature{
Type: core.MakeName("Sig"),
Handler: handler,
}
dict := &pdfSignDictionary{
PdfObjectDictionary: core.MakeDict(),
handler: &handler,
signature: sig,
}
sig.container = core.MakeIndirectObject(dict)
return sig
}
// GetContainingPdfObject implements interface PdfModel.
@ -50,48 +126,68 @@ func (sig *PdfSignature) GetContainingPdfObject() core.PdfObject {
return sig.container
}
// SetName sets the `Name` field of the signature.
func (sig *PdfSignature) SetName(name string) {
sig.Name = core.MakeString(name)
}
// SetDate sets the `M` field of the signature.
func (sig *PdfSignature) SetDate(date time.Time, format string) {
if format == "" {
format = "D:20060102150405-07'00'"
}
sig.M = core.MakeString(date.Format(format))
}
// SetReason sets the `Reason` field of the signature.
func (sig *PdfSignature) SetReason(reason string) {
sig.Reason = core.MakeString(reason)
}
// SetLocation sets the `Location` field of the signature.
func (sig *PdfSignature) SetLocation(location string) {
sig.Location = core.MakeString(location)
}
// Initialize initializes the PdfSignature.
func (sig *PdfSignature) Initialize() error {
if sig.Handler == nil {
return errors.New("signature handler cannot be nil")
}
return sig.Handler.InitSignature(sig)
}
// ToPdfObject implements interface PdfModel.
func (sig *PdfSignature) ToPdfObject() core.PdfObject {
container := sig.container
dict := container.PdfObject.(*core.PdfObjectDictionary)
var dict *core.PdfObjectDictionary
if sigDict, ok := container.PdfObject.(*pdfSignDictionary); ok {
dict = sigDict.PdfObjectDictionary
} else {
dict = container.PdfObject.(*core.PdfObjectDictionary)
}
dict.Set("Type", sig.Type)
dict.SetIfNotNil("Filter", sig.Filter)
dict.SetIfNotNil("SubFilter", sig.SubFilter)
dict.SetIfNotNil("Contents", sig.Contents)
dict.SetIfNotNil("Cert", sig.Cert)
dict.SetIfNotNil("ByteRange", sig.ByteRange)
dict.SetIfNotNil("Reference", sig.Reference)
dict.SetIfNotNil("Changes", sig.Changes)
dict.SetIfNotNil("Name", sig.Name)
dict.SetIfNotNil("M", sig.M)
dict.SetIfNotNil("Reason", sig.Reason)
dict.SetIfNotNil("ContactInfo", sig.ContactInfo)
dict.SetIfNotNil("ByteRange", sig.ByteRange)
dict.SetIfNotNil("Contents", sig.Contents)
if sig.Filter != nil {
dict.Set("Filter", sig.Filter)
}
if sig.SubFilter != nil {
dict.Set("SubFilter", sig.SubFilter)
}
if sig.Contents != nil {
dict.Set("Contents", sig.Contents)
}
if sig.Cert != nil {
dict.Set("Cert", sig.Cert)
}
if sig.ByteRange != nil {
dict.Set("ByteRange", sig.ByteRange)
}
if sig.Reference != nil {
dict.Set("Reference", sig.Reference)
}
if sig.Changes != nil {
dict.Set("Changes", sig.Changes)
}
if sig.Name != nil {
dict.Set("Name", sig.Name)
}
if sig.M != nil {
dict.Set("M", sig.M)
}
if sig.Reason != nil {
dict.Set("Reason", sig.Reason)
}
if sig.ContactInfo != nil {
dict.Set("ContactInfo", sig.ContactInfo)
}
// FIXME: ByteRange and Contents need to be updated dynamically.
// NOTE: ByteRange and Contents need to be updated dynamically.
// TODO: Currently dynamic update is only in the appender, need to support
// in the PdfWriter too for the initial signature on document creation.
return container
}
@ -148,106 +244,3 @@ func (r *PdfReader) newPdfSignatureFromIndirect(container *core.PdfIndirectObjec
return sig, nil
}
// PdfSignatureField represents a form field that contains a digital signature.
// (12.7.4.5 - Signature Fields p. 454 in PDF32000_2008).
//
// The signature form field serves two primary purposes. 1. Define the form field that will provide the
// visual signing properties for display but may also hold information needed when the actual signing
// takes place such as signature method. This carries information from the author of the document to the
// software that later does signing.
//
// Filling in (signing) the signature field entails updating at least the V entry and usually the AP entry of the
// associated widget annotation. (Exporting a signature field exports the T, V, AP entries)
//
// The annotation rectangle (Rect) in such a dictionary shall give the position of the field on its page. Signature
// fields that are not intended to be visible shall have an annotation rectangle that has zero height and width. PDF
// processors shall treat such signatures as not visible. PDF processors shall also treat signatures as not
// visible if either the Hidden bit or the NoView bit of the F entry is true
//
// The location of a signature within a document can have a bearing on its legal meaning. For this reason,
// signature fields shall never refer to more than one annotation.
type PdfSignatureField struct {
container *core.PdfIndirectObject
V *PdfSignature
// Type: /Sig
// V: *PdfSignature...
Lock *core.PdfIndirectObject // Shall be an indirect reference.
SV *core.PdfIndirectObject // Shall be an indirect reference.
}
// NewPdfSignatureField prepares a PdfSignatureField from a PdfSignature.
func NewPdfSignatureField(signature *PdfSignature) *PdfSignatureField {
return &PdfSignatureField{
V: signature,
container: core.MakeIndirectObject(core.MakeDict()),
}
}
// ToPdfObject implements interface PdfModel.
func (sf *PdfSignatureField) ToPdfObject() core.PdfObject {
container := sf.container
dict := container.PdfObject.(*core.PdfObjectDictionary)
dict.Set("FT", core.MakeName("Sig"))
if sf.V != nil {
dict.Set("V", sf.V.ToPdfObject())
}
if sf.Lock != nil {
dict.Set("Lock", sf.Lock)
}
if sf.SV != nil {
dict.Set("SV", sf.SV)
}
// Other standard fields...
return container
}
// PdfSignatureFieldLock represents signature field lock dictionary.
// (Table 233 - p. 455 in PDF32000_2008).
type PdfSignatureFieldLock struct {
Type core.PdfObject
Action *core.PdfObjectName
Fields *core.PdfObjectArray
P *core.PdfObjectInteger
}
// PdfSignatureFieldSeed represents signature field seed value dictionary.
// (Table 234 - p. 455 in PDF32000_2008).
type PdfSignatureFieldSeed struct {
// Type
Ff *core.PdfObjectInteger
Filter *core.PdfObjectName
SubFilter *core.PdfObjectArray
DigestMethod *core.PdfObjectArray
V *core.PdfObjectInteger
Cert core.PdfObject
Reasons *core.PdfObjectArray
MDP *core.PdfObjectDictionary
TimeStamp *core.PdfObjectDictionary
LegalAttestation *core.PdfObjectArray
AddRevInfo *core.PdfObjectBool
LockDocument *core.PdfObjectName
AppearanceFilter *core.PdfObjectString
}
// PdfCertificateSeed represents certificate seed value dictionary.
// (Table 235 - p. 457 in PDF32000_2008).
type PdfCertificateSeed struct {
// Type
Ff *core.PdfObjectInteger
Subject *core.PdfObjectArray
SignaturePolicyOID *core.PdfObjectString
SignaturePolicyHashValue *core.PdfObjectString
SignaturePolicyHashAlgorithm *core.PdfObjectName
SignaturePolicyCommitmentType *core.PdfObjectArray
SubjectDN *core.PdfObjectArray
KeyUsage *core.PdfObjectArray
Issuer *core.PdfObjectArray
OID *core.PdfObjectArray
URL *core.PdfObjectString
URLType *core.PdfObjectName
}

View 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
}

View File

@ -14,6 +14,7 @@ import (
"math"
"regexp"
"strconv"
"time"
"github.com/unidoc/unidoc/pdf/core"
)
@ -93,6 +94,21 @@ type PdfDate struct {
utOffsetMins int64 // mm (00-59)
}
// ToGoTime returns the date in time.Time format.
func (d PdfDate) ToGoTime() time.Time {
utcOffset := int(d.utOffsetHours*60*60 + d.utOffsetMins*60)
switch d.utOffsetSign {
case '-':
utcOffset = -utcOffset
case 'Z':
utcOffset = 0
}
tzName := fmt.Sprintf("UTC%c%.2d%.2d", d.utOffsetSign, d.utOffsetHours, d.utOffsetMins)
tz := time.FixedZone(tzName, utcOffset)
return time.Date(int(d.year), time.Month(d.month), int(d.day), int(d.hour), int(d.minute), int(d.second), 0, tz)
}
var reDate = regexp.MustCompile(`\s*D\s*:\s*(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})([+-Z])?(\d{2})?'?(\d{2})?`)
// NewPdfDate returns a new PdfDate object from a PDF date string (see 7.9.4 Dates).

Binary file not shown.

BIN
pdf/model/testdata/ks12 vendored Normal file

Binary file not shown.

View File

@ -225,6 +225,18 @@ func copyObject(obj core.PdfObject, objectToObjectCopyMap map[core.PdfObject]cor
newObj := core.PdfObjectBool(*t)
objectToObjectCopyMap[obj] = &newObj
return &newObj
case *pdfSignDictionary:
newObj := &pdfSignDictionary{
PdfObjectDictionary: core.MakeDict(),
handler: t.handler,
signature: t.signature,
}
objectToObjectCopyMap[obj] = newObj
for _, key := range t.Keys() {
val := t.Get(key)
newObj.Set(key, copyObject(val, objectToObjectCopyMap))
}
return newObj
default:
common.Log.Info("TODO(a5i): implement copyObject for %+v", obj)
}
@ -571,6 +583,9 @@ func (w *PdfWriter) writeObject(num int, obj core.PdfObject) {
if pobj, isIndirect := obj.(*core.PdfIndirectObject); isIndirect {
w.crossReferenceMap[num] = crossReference{Type: 1, Offset: w.writePos, Generation: pobj.GenerationNumber}
outStr := fmt.Sprintf("%d 0 obj\n", num)
if sDict, ok := pobj.PdfObject.(*pdfSignDictionary); ok {
sDict.fileOffset = w.writePos + int64(len(outStr))
}
outStr += pobj.PdfObject.WriteString()
outStr += "\nendobj\n"
w.writeString(outStr)