Refactor object handling

This commit is contained in:
Paul van Brouwershaven
2024-12-11 16:24:26 +01:00
parent 21dd78d75e
commit e5cdb61bea
11 changed files with 311 additions and 327 deletions

View File

@@ -1,6 +1,7 @@
package sign package sign
import ( import (
"bytes"
"fmt" "fmt"
"strings" "strings"
) )
@@ -9,43 +10,46 @@ func (context *SignContext) updateByteRange() error {
if _, err := context.OutputBuffer.Seek(0, 0); err != nil { if _, err := context.OutputBuffer.Seek(0, 0); err != nil {
return err return err
} }
output_file_size := int64(context.OutputBuffer.Buff.Len())
// Calculate ByteRange values to replace them. // Set ByteRangeValues by looking for the /Contents< filled with zeros
context.ByteRangeValues = make([]int64, 4) contentsPlaceholder := bytes.Repeat([]byte("0"), int(context.SignatureMaxLength))
contentsIndex := bytes.Index(context.OutputBuffer.Buff.Bytes(), contentsPlaceholder)
if contentsIndex == -1 {
return fmt.Errorf("failed to find contents placeholder")
}
// Signature ByteRange part 1 start byte is always byte 0. // Calculate ByteRangeValues
context.ByteRangeValues[0] = int64(0) signatureContentsStart := int64(contentsIndex) - 1
signatureContentsEnd := signatureContentsStart + int64(context.SignatureMaxLength) + 2
// Signature ByteRange part 1 length always stops at the actual signature start byte. context.ByteRangeValues = []int64{
context.ByteRangeValues[1] = context.SignatureContentsStartByte - 1 0,
signatureContentsStart,
// Signature ByteRange part 2 start byte directly starts after the actual signature. signatureContentsEnd,
context.ByteRangeValues[2] = context.ByteRangeValues[1] + 1 + int64(context.SignatureMaxLength) + 1 int64(context.OutputBuffer.Buff.Len()) - signatureContentsEnd,
}
// Signature ByteRange part 2 length is everything else of the file.
context.ByteRangeValues[3] = output_file_size - context.ByteRangeValues[2]
new_byte_range := fmt.Sprintf("/ByteRange [%d %d %d %d]", context.ByteRangeValues[0], context.ByteRangeValues[1], context.ByteRangeValues[2], context.ByteRangeValues[3]) new_byte_range := fmt.Sprintf("/ByteRange [%d %d %d %d]", context.ByteRangeValues[0], context.ByteRangeValues[1], context.ByteRangeValues[2], context.ByteRangeValues[3])
// Make sure our ByteRange string didn't shrink in length. // Make sure our ByteRange string has the same length as the placeholder.
new_byte_range += strings.Repeat(" ", len(signatureByteRangePlaceholder)-len(new_byte_range)) if len(new_byte_range) < len(signatureByteRangePlaceholder) {
new_byte_range += strings.Repeat(" ", len(signatureByteRangePlaceholder)-len(new_byte_range))
if _, err := context.OutputBuffer.Seek(0, 0); err != nil { } else if len(new_byte_range) != len(signatureByteRangePlaceholder) {
return err return fmt.Errorf("new byte range string is the same lenght as the placeholder")
}
file_content := context.OutputBuffer.Buff.Bytes()
if _, err := context.OutputBuffer.Write(file_content[:context.ByteRangeStartByte]); err != nil {
return err
} }
// Write new ByteRange. // Find the placeholder in the buffer
if _, err := context.OutputBuffer.Write([]byte(new_byte_range)); err != nil { placeholderIndex := bytes.Index(context.OutputBuffer.Buff.Bytes(), []byte(signatureByteRangePlaceholder))
return err if placeholderIndex == -1 {
return fmt.Errorf("failed to find ByteRange placeholder")
} }
if _, err := context.OutputBuffer.Write(file_content[context.ByteRangeStartByte+int64(len(new_byte_range)):]); err != nil { // Replace the placeholder with the new byte range
bufferBytes := context.OutputBuffer.Buff.Bytes()
copy(bufferBytes[placeholderIndex:placeholderIndex+len(new_byte_range)], []byte(new_byte_range))
// Rewrite the buffer with the updated bytes
context.OutputBuffer.Buff.Reset()
if _, err := context.OutputBuffer.Buff.Write(bufferBytes); err != nil {
return err return err
} }

View File

@@ -1,16 +1,16 @@
package sign package sign
import ( import (
"bytes"
"strconv" "strconv"
"strings"
) )
func (context *SignContext) createCatalog() (string, error) { func (context *SignContext) createCatalog() ([]byte, error) {
var catalogBuilder strings.Builder var catalog_buffer bytes.Buffer
// Start the catalog object // Start the catalog object
catalogBuilder.WriteString(strconv.Itoa(int(context.CatalogData.ObjectId)) + " 0 obj\n") catalog_buffer.WriteString("<<\n")
catalogBuilder.WriteString("<< /Type /Catalog") catalog_buffer.WriteString(" /Type /Catalog")
// (Optional; PDF 1.4) The version of the PDF specification to which // (Optional; PDF 1.4) The version of the PDF specification to which
// the document conforms (for example, 1.4) if later than the version // the document conforms (for example, 1.4) if later than the version
@@ -25,7 +25,7 @@ func (context *SignContext) createCatalog() (string, error) {
// //
// If an incremental upgrade requires a version that is higher than specified by the document. // If an incremental upgrade requires a version that is higher than specified by the document.
// if context.PDFReader.PDFVersion < "2.0" { // if context.PDFReader.PDFVersion < "2.0" {
// catalogBuilder.WriteString(" /Version /2.0") // catalog_buffer.WriteString(" /Version /2.0")
// } // }
// Retrieve the root and check for necessary keys in one loop // Retrieve the root and check for necessary keys in one loop
@@ -49,33 +49,33 @@ func (context *SignContext) createCatalog() (string, error) {
// Add Pages and Names references if they exist // Add Pages and Names references if they exist
if foundPages { if foundPages {
pages := root.Key("Pages").GetPtr() pages := root.Key("Pages").GetPtr()
catalogBuilder.WriteString(" /Pages " + strconv.Itoa(int(pages.GetID())) + " " + strconv.Itoa(int(pages.GetGen())) + " R") catalog_buffer.WriteString(" /Pages " + strconv.Itoa(int(pages.GetID())) + " " + strconv.Itoa(int(pages.GetGen())) + " R")
} }
if foundNames { if foundNames {
names := root.Key("Names").GetPtr() names := root.Key("Names").GetPtr()
catalogBuilder.WriteString(" /Names " + strconv.Itoa(int(names.GetID())) + " " + strconv.Itoa(int(names.GetGen())) + " R") catalog_buffer.WriteString(" /Names " + strconv.Itoa(int(names.GetID())) + " " + strconv.Itoa(int(names.GetGen())) + " R")
} }
// Start the AcroForm dictionary with /NeedAppearances // Start the AcroForm dictionary with /NeedAppearances
catalogBuilder.WriteString(" /AcroForm << /Fields [") catalog_buffer.WriteString(" /AcroForm << /Fields [")
// Add existing signatures to the AcroForm dictionary // Add existing signatures to the AcroForm dictionary
for i, sig := range context.SignData.ExistingSignatures { for i, sig := range context.SignData.ExistingSignatures {
if i > 0 { if i > 0 {
catalogBuilder.WriteString(" ") catalog_buffer.WriteString(" ")
} }
catalogBuilder.WriteString(strconv.Itoa(int(sig.ObjectId)) + " 0 R") catalog_buffer.WriteString(strconv.Itoa(int(sig.ObjectId)) + " 0 R")
} }
// Add the visual signature field to the AcroForm dictionary // Add the visual signature field to the AcroForm dictionary
if len(context.SignData.ExistingSignatures) > 0 { if len(context.SignData.ExistingSignatures) > 0 {
catalogBuilder.WriteString(" ") catalog_buffer.WriteString(" ")
} }
catalogBuilder.WriteString(strconv.Itoa(int(context.VisualSignData.ObjectId)) + " 0 R") catalog_buffer.WriteString(strconv.Itoa(int(context.VisualSignData.ObjectId)) + " 0 R")
catalogBuilder.WriteString("]") // close Fields array catalog_buffer.WriteString("]") // close Fields array
catalogBuilder.WriteString(" /NeedAppearances false") catalog_buffer.WriteString(" /NeedAppearances false")
// Signature flags (Table 225) // Signature flags (Table 225)
// //
@@ -100,14 +100,14 @@ func (context *SignContext) createCatalog() (string, error) {
// Set SigFlags and Permissions based on Signature Type // Set SigFlags and Permissions based on Signature Type
switch context.SignData.Signature.CertType { switch context.SignData.Signature.CertType {
case CertificationSignature, ApprovalSignature, TimeStampSignature: case CertificationSignature, ApprovalSignature, TimeStampSignature:
catalogBuilder.WriteString(" /SigFlags 3") catalog_buffer.WriteString(" /SigFlags 3")
case UsageRightsSignature: case UsageRightsSignature:
catalogBuilder.WriteString(" /SigFlags 1") catalog_buffer.WriteString(" /SigFlags 1")
} }
// Finalize the AcroForm and Catalog object // Finalize the AcroForm and Catalog object
catalogBuilder.WriteString(" >>") // Close AcroForm catalog_buffer.WriteString(" >>\n") // Close AcroForm
catalogBuilder.WriteString(" >>\nendobj\n") // Close catalog object catalog_buffer.WriteString(">>\n") // Close Catalog
return catalogBuilder.String(), nil return catalog_buffer.Bytes(), nil
} }

View File

@@ -15,17 +15,17 @@ var testFiles = []struct {
{ {
file: "../testfiles/testfile20.pdf", file: "../testfiles/testfile20.pdf",
expectedCatalogs: map[CertType]string{ expectedCatalogs: map[CertType]string{
CertificationSignature: "11 0 obj\n<< /Type /Catalog /Pages 3 0 R /AcroForm << /Fields [10 0 R] /NeedAppearances false /SigFlags 3 >> >>\nendobj\n", CertificationSignature: "<<\n /Type /Catalog /Pages 3 0 R /AcroForm << /Fields [10 0 R] /NeedAppearances false /SigFlags 3 >>\n>>\n",
UsageRightsSignature: "11 0 obj\n<< /Type /Catalog /Pages 3 0 R /AcroForm << /Fields [10 0 R] /NeedAppearances false /SigFlags 1 >> >>\nendobj\n", UsageRightsSignature: "<<\n /Type /Catalog /Pages 3 0 R /AcroForm << /Fields [10 0 R] /NeedAppearances false /SigFlags 1 >>\n>>\n",
ApprovalSignature: "11 0 obj\n<< /Type /Catalog /Pages 3 0 R /AcroForm << /Fields [10 0 R] /NeedAppearances false /SigFlags 3 >> >>\nendobj\n", ApprovalSignature: "<<\n /Type /Catalog /Pages 3 0 R /AcroForm << /Fields [10 0 R] /NeedAppearances false /SigFlags 3 >>\n>>\n",
}, },
}, },
{ {
file: "../testfiles/testfile21.pdf", file: "../testfiles/testfile21.pdf",
expectedCatalogs: map[CertType]string{ expectedCatalogs: map[CertType]string{
CertificationSignature: "17 0 obj\n<< /Type /Catalog /Pages 9 0 R /Names 6 0 R /AcroForm << /Fields [16 0 R] /NeedAppearances false /SigFlags 3 >> >>\nendobj\n", CertificationSignature: "<<\n /Type /Catalog /Pages 9 0 R /Names 6 0 R /AcroForm << /Fields [16 0 R] /NeedAppearances false /SigFlags 3 >>\n>>\n",
UsageRightsSignature: "17 0 obj\n<< /Type /Catalog /Pages 9 0 R /Names 6 0 R /AcroForm << /Fields [16 0 R] /NeedAppearances false /SigFlags 1 >> >>\nendobj\n", UsageRightsSignature: "<<\n /Type /Catalog /Pages 9 0 R /Names 6 0 R /AcroForm << /Fields [16 0 R] /NeedAppearances false /SigFlags 1 >>\n>>\n",
ApprovalSignature: "17 0 obj\n<< /Type /Catalog /Pages 9 0 R /Names 6 0 R /AcroForm << /Fields [16 0 R] /NeedAppearances false /SigFlags 3 >> >>\nendobj\n", ApprovalSignature: "<<\n /Type /Catalog /Pages 9 0 R /Names 6 0 R /AcroForm << /Fields [16 0 R] /NeedAppearances false /SigFlags 3 >>\n>>\n",
}, },
}, },
} }
@@ -54,7 +54,6 @@ func TestCreateCatalog(t *testing.T) {
} }
context := SignContext{ context := SignContext{
Filesize: size + 1,
PDFReader: rdr, PDFReader: rdr,
InputFile: inputFile, InputFile: inputFile,
VisualSignData: VisualSignData{ VisualSignData: VisualSignData{
@@ -80,8 +79,8 @@ func TestCreateCatalog(t *testing.T) {
return return
} }
if catalog != expectedCatalog { if string(catalog) != expectedCatalog {
st.Errorf("Catalog mismatch, expected %s, but got %s", expectedCatalog, catalog) st.Errorf("Catalog mismatch, expected\n%s\nbut got\n%s", expectedCatalog, catalog)
} }
}) })
} }

View File

@@ -21,23 +21,20 @@ import (
const signatureByteRangePlaceholder = "/ByteRange[0 ********** ********** **********]" const signatureByteRangePlaceholder = "/ByteRange[0 ********** ********** **********]"
func (context *SignContext) createSignaturePlaceholder() (dssd string, byte_range_start_byte int64, signature_contents_start_byte int64) { func (context *SignContext) createSignaturePlaceholder() []byte {
// Using a buffer because it's way faster than concatenating. // Using a buffer because it's way faster than concatenating.
var signature_buffer bytes.Buffer var signature_buffer bytes.Buffer
signature_buffer.WriteString(strconv.Itoa(int(context.SignData.ObjectId)) + " 0 obj\n")
signature_buffer.WriteString("<< /Type /Sig\n") signature_buffer.WriteString("<<\n")
signature_buffer.WriteString(" /Type /Sig\n")
signature_buffer.WriteString(" /Filter /Adobe.PPKLite\n") signature_buffer.WriteString(" /Filter /Adobe.PPKLite\n")
signature_buffer.WriteString(" /SubFilter /adbe.pkcs7.detached\n") signature_buffer.WriteString(" /SubFilter /adbe.pkcs7.detached\n")
signature_buffer.WriteString(context.createPropBuild()) signature_buffer.WriteString(context.createPropBuild())
byte_range_start_byte = int64(signature_buffer.Len()) + 1
// Create a placeholder for the byte range string, we will replace it later. // Create a placeholder for the byte range string, we will replace it later.
signature_buffer.WriteString(" " + signatureByteRangePlaceholder) signature_buffer.WriteString(" " + signatureByteRangePlaceholder)
signature_contents_start_byte = int64(signature_buffer.Len()) + 11
// Create a placeholder for the actual signature content, we will replace it later. // Create a placeholder for the actual signature content, we will replace it later.
signature_buffer.WriteString(" /Contents<") signature_buffer.WriteString(" /Contents<")
signature_buffer.Write(bytes.Repeat([]byte("0"), int(context.SignatureMaxLength))) signature_buffer.Write(bytes.Repeat([]byte("0"), int(context.SignatureMaxLength)))
@@ -168,40 +165,47 @@ func (context *SignContext) createSignaturePlaceholder() (dssd string, byte_rang
signature_buffer.WriteString(pdfString(context.SignData.Signature.Info.ContactInfo)) signature_buffer.WriteString(pdfString(context.SignData.Signature.Info.ContactInfo))
signature_buffer.WriteString("\n") signature_buffer.WriteString("\n")
} }
signature_buffer.WriteString(" /M ")
signature_buffer.WriteString(pdfDateTime(context.SignData.Signature.Info.Date))
signature_buffer.WriteString("\n")
signature_buffer.WriteString(" >>\n") // (Optional) The time of signing. Depending on the signature handler, this may
signature_buffer.WriteString("endobj\n") // be a normal unverified computer time or a time generated in a verifiable way
// from a secure time server.
//
// This value should be used only when the time of signing is not available in the
// signature. If SubFilter is ETSI.RFC3161, this entry should not be used and
// should be ignored by a PDF processor.
//
// A timestamp can be embedded in a CMS binary data object (see 12.8.3.3, "CMS
// (PKCS #7) signatures").
if context.SignData.TSA.URL == "" {
signature_buffer.WriteString(" /M ")
signature_buffer.WriteString(pdfDateTime(context.SignData.Signature.Info.Date))
signature_buffer.WriteString("\n")
}
return signature_buffer.String(), byte_range_start_byte, signature_contents_start_byte signature_buffer.WriteString(">>\n")
return signature_buffer.Bytes()
} }
func (context *SignContext) createTimestampPlaceholder() (dssd string, byte_range_start_byte int64, signature_contents_start_byte int64) { func (context *SignContext) createTimestampPlaceholder() []byte {
var timestamp_buffer bytes.Buffer var timestamp_buffer bytes.Buffer
timestamp_buffer.WriteString(strconv.Itoa(int(context.SignData.ObjectId)) + " 0 obj\n") timestamp_buffer.WriteString("<<\n")
timestamp_buffer.WriteString("<< /Type /DocTimeStamp\n") timestamp_buffer.WriteString(" /Type /DocTimeStamp\n")
timestamp_buffer.WriteString(" /Filter /Adobe.PPKLite\n") timestamp_buffer.WriteString(" /Filter /Adobe.PPKLite\n")
timestamp_buffer.WriteString(" /SubFilter /ETSI.RFC3161\n") timestamp_buffer.WriteString(" /SubFilter /ETSI.RFC3161\n")
timestamp_buffer.WriteString(context.createPropBuild()) timestamp_buffer.WriteString(context.createPropBuild())
byte_range_start_byte = int64(timestamp_buffer.Len()) + 1
// Create a placeholder for the byte range string, we will replace it later. // Create a placeholder for the byte range string, we will replace it later.
timestamp_buffer.WriteString(" " + signatureByteRangePlaceholder) timestamp_buffer.WriteString(" " + signatureByteRangePlaceholder)
signature_contents_start_byte = int64(timestamp_buffer.Len()) + 11
timestamp_buffer.WriteString(" /Contents<") timestamp_buffer.WriteString(" /Contents<")
timestamp_buffer.Write(bytes.Repeat([]byte("0"), int(context.SignatureMaxLength))) timestamp_buffer.Write(bytes.Repeat([]byte("0"), int(context.SignatureMaxLength)))
timestamp_buffer.WriteString(">\n") timestamp_buffer.WriteString(">\n")
timestamp_buffer.WriteString(">>\n") timestamp_buffer.WriteString(">>\n")
timestamp_buffer.WriteString("endobj\n")
return timestamp_buffer.String(), byte_range_start_byte, signature_contents_start_byte return timestamp_buffer.Bytes()
} }
func (context *SignContext) fetchRevocationData() error { func (context *SignContext) fetchRevocationData() error {
@@ -446,16 +450,31 @@ func (context *SignContext) replaceSignature() error {
} }
file_content := context.OutputBuffer.Buff.Bytes() file_content := context.OutputBuffer.Buff.Bytes()
if _, err := context.OutputBuffer.Write(file_content[:(context.ByteRangeValues[0] + context.ByteRangeValues[1] + 1)]); err != nil { // Write the file content up to the signature
if _, err := context.OutputBuffer.Write(file_content[context.ByteRangeValues[0]:context.ByteRangeValues[1]]); err != nil {
return err
}
// Write new signature
if _, err := context.OutputBuffer.Write([]byte("<")); err != nil {
return err return err
} }
// Write new ByteRange.
if _, err := context.OutputBuffer.Write([]byte(dst)); err != nil { if _, err := context.OutputBuffer.Write([]byte(dst)); err != nil {
return err return err
} }
if _, err := context.OutputBuffer.Write(file_content[(context.ByteRangeValues[0]+context.ByteRangeValues[1]+1)+int64(len(dst)):]); err != nil { // Write 0s to ensure the signature remains the same size
zeroPadding := bytes.Repeat([]byte("0"), int(context.SignatureMaxLength)-len(dst))
if _, err := context.OutputBuffer.Write(zeroPadding); err != nil {
return err
}
if _, err := context.OutputBuffer.Write([]byte(">")); err != nil {
return err
}
if _, err := context.OutputBuffer.Write(file_content[context.ByteRangeValues[2] : context.ByteRangeValues[2]+context.ByteRangeValues[3]]); err != nil {
return err return err
} }

View File

@@ -1,100 +1,91 @@
package sign package sign
// import ( import (
// "fmt" "fmt"
// "os" "os"
// "testing" "testing"
// "time" "time"
// "github.com/digitorus/pdf" "github.com/digitorus/pdf"
// ) )
// var signatureTests = []struct { var signatureTests = []struct {
// file string file string
// expectedSignatures map[uint]string expectedSignatures map[CertType]string
// }{ }{
// { {
// file: "../testfiles/testfile20.pdf", file: "../testfiles/testfile20.pdf",
// expectedSignatures: map[uint]string{ expectedSignatures: map[CertType]string{
// CertificationSignature: "13 0 obj\n<< /Type /Sig /Filter /Adobe.PPKLite /SubFilter /adbe.pkcs7.detached /ByteRange[0 ********** ********** **********] /Contents<> /Reference [ << /Type /SigRef /TransformMethod /DocMDP /TransformParams << /Type /TransformParams /P 2 /V /1.2 >> >> ] /Name (John Doe) /Location (Somewhere) /Reason (Test) /ContactInfo (None) /M (D:20170923143900+03'00') >>\nendobj\n", CertificationSignature: "<<\n /Type /Sig\n /Filter /Adobe.PPKLite\n /SubFilter /adbe.pkcs7.detached\n /Prop_Build <<\n /App << /Name /Digitorus#20PDFSign >>\n >>\n /ByteRange[0 ********** ********** **********] /Contents<>\n /Reference [\n << /Type /SigRef\n /TransformMethod /DocMDP\n /TransformParams <<\n /Type /TransformParams\n /P 2 /V /1.2\n >>\n >> ] /Name (John Doe)\n /Location (Somewhere)\n /Reason (Test)\n /ContactInfo (None)\n /M (D:20170923143900+03'00')\n>>\n",
// UsageRightsSignature: "13 0 obj\n<< /Type /Sig /Filter /Adobe.PPKLite /SubFilter /adbe.pkcs7.detached /ByteRange[0 ********** ********** **********] /Contents<> /Reference [ << /Type /SigRef /TransformMethod /UR3 /TransformParams << /Type /TransformParams /V /2.2 >> >> ] /Name (John Doe) /Location (Somewhere) /Reason (Test) /ContactInfo (None) /M (D:20170923143900+03'00') >>\nendobj\n", UsageRightsSignature: "<<\n /Type /Sig\n /Filter /Adobe.PPKLite\n /SubFilter /adbe.pkcs7.detached\n /Prop_Build <<\n /App << /Name /Digitorus#20PDFSign >>\n >>\n /ByteRange[0 ********** ********** **********] /Contents<>\n /Reference [\n << /Type /SigRef\n /TransformMethod /UR3\n /TransformParams <<\n /Type /TransformParams\n /V /2.2\n >>\n >> ] /Name (John Doe)\n /Location (Somewhere)\n /Reason (Test)\n /ContactInfo (None)\n /M (D:20170923143900+03'00')\n>>\n",
// ApprovalSignature: "13 0 obj\n<< /Type /Sig /Filter /Adobe.PPKLite /SubFilter /adbe.pkcs7.detached /ByteRange[0 ********** ********** **********] /Contents<> /Reference [ << /Type /SigRef /TransformMethod /FieldMDP /TransformParams << /Type /TransformParams /Fields [<< /Type /SigFieldLock /Action /All >>] /V /1.2 >> >> ] /Name (John Doe) /Location (Somewhere) /Reason (Test) /ContactInfo (None) /M (D:20170923143900+03'00') >>\nendobj\n", ApprovalSignature: "<<\n /Type /Sig\n /Filter /Adobe.PPKLite\n /SubFilter /adbe.pkcs7.detached\n /Prop_Build <<\n /App << /Name /Digitorus#20PDFSign >>\n >>\n /ByteRange[0 ********** ********** **********] /Contents<>\n /TransformMethod /FieldMDP\n /TransformParams <<\n /Type /TransformParams\n /Action /All\n /V /1.2\n >>\n /Name (John Doe)\n /Location (Somewhere)\n /Reason (Test)\n /ContactInfo (None)\n /M (D:20170923143900+03'00')\n>>\n",
// }, },
// }, },
// } }
// func TestCreateSignaturePlaceholder(t *testing.T) { func TestCreateSignaturePlaceholder(t *testing.T) {
// for _, testFile := range signatureTests { for _, testFile := range signatureTests {
// for certType, expectedSignature := range testFile.expectedSignatures { for certType, expectedSignature := range testFile.expectedSignatures {
// t.Run(fmt.Sprintf("%s_certType-%d", testFile.file, certType), func(st *testing.T) { t.Run(fmt.Sprintf("%s_certType-%d", testFile.file, certType), func(st *testing.T) {
// inputFile, err := os.Open(testFile.file) inputFile, err := os.Open(testFile.file)
// if err != nil { if err != nil {
// st.Errorf("Failed to load test PDF") st.Errorf("Failed to load test PDF")
// return return
// } }
// finfo, err := inputFile.Stat() finfo, err := inputFile.Stat()
// if err != nil { if err != nil {
// st.Errorf("Failed to load test PDF") st.Errorf("Failed to load test PDF")
// return return
// } }
// size := finfo.Size() size := finfo.Size()
// rdr, err := pdf.NewReader(inputFile, size) rdr, err := pdf.NewReader(inputFile, size)
// if err != nil { if err != nil {
// st.Errorf("Failed to load test PDF") st.Errorf("Failed to load test PDF")
// return return
// } }
// timezone, _ := time.LoadLocation("Europe/Tallinn") timezone, _ := time.LoadLocation("Europe/Tallinn")
// now := time.Date(2017, 9, 23, 14, 39, 0, 0, timezone) now := time.Date(2017, 9, 23, 14, 39, 0, 0, timezone)
// sign_data := SignData{ sign_data := SignData{
// Signature: SignDataSignature{ Signature: SignDataSignature{
// Info: SignDataSignatureInfo{ Info: SignDataSignatureInfo{
// Name: "John Doe", Name: "John Doe",
// Location: "Somewhere", Location: "Somewhere",
// Reason: "Test", Reason: "Test",
// ContactInfo: "None", ContactInfo: "None",
// Date: now, Date: now,
// }, },
// CertType: certType, CertType: certType,
// DocMDPPerm: AllowFillingExistingFormFieldsAndSignaturesPerms, DocMDPPerm: AllowFillingExistingFormFieldsAndSignaturesPerms,
// }, },
// } }
// sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 3 sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 3
// context := SignContext{ context := SignContext{
// Filesize: size + 1, PDFReader: rdr,
// PDFReader: rdr, InputFile: inputFile,
// InputFile: inputFile, VisualSignData: VisualSignData{
// VisualSignData: VisualSignData{ ObjectId: uint32(rdr.XrefInformation.ItemCount),
// ObjectId: uint32(rdr.XrefInformation.ItemCount), },
// }, CatalogData: CatalogData{
// CatalogData: CatalogData{ ObjectId: uint32(rdr.XrefInformation.ItemCount) + 1,
// ObjectId: uint32(rdr.XrefInformation.ItemCount) + 1, },
// }, InfoData: InfoData{
// InfoData: InfoData{ ObjectId: uint32(rdr.XrefInformation.ItemCount) + 2,
// ObjectId: uint32(rdr.XrefInformation.ItemCount) + 2, },
// }, SignData: sign_data,
// SignData: sign_data, }
// }
// signature, byte_range_start_byte, signature_contents_start_byte := context.createSignaturePlaceholder() signature := context.createSignaturePlaceholder()
// if signature != expectedSignature { if string(signature) != expectedSignature {
// st.Errorf("Signature mismatch, expected %s, but got %s", expectedSignature, signature) st.Errorf("Signature mismatch, expected:\n%q\nbut got:\n%q", expectedSignature, signature)
// } }
})
// if byte_range_start_byte != 78 { }
// st.Errorf("Byte range start mismatch, expected %d, but got %d", 78, byte_range_start_byte) }
// } }
// if signature_contents_start_byte != 135 {
// st.Errorf("Signature contents start byte mismatch, expected %d, but got %d", 135, signature_contents_start_byte)
// }
// })
// }
// }
// }

View File

@@ -22,7 +22,7 @@ func (context *SignContext) writeTrailer() error {
new_root := "Root " + strconv.FormatInt(int64(context.CatalogData.ObjectId), 10) + " 0 R" new_root := "Root " + strconv.FormatInt(int64(context.CatalogData.ObjectId), 10) + " 0 R"
size_string := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount, 10) size_string := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount, 10)
new_size := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount+3, 10) new_size := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount+int64(len(context.newXrefEntries)+1), 10)
prev_string := "Prev " + context.PDFReader.Trailer().Key("Prev").String() prev_string := "Prev " + context.PDFReader.Trailer().Key("Prev").String()
new_prev := "Prev " + strconv.FormatInt(context.PDFReader.XrefInformation.StartPos, 10) new_prev := "Prev " + strconv.FormatInt(context.PDFReader.XrefInformation.StartPos, 10)

View File

@@ -1,6 +1,7 @@
package sign package sign
import ( import (
"bytes"
"fmt" "fmt"
"strconv" "strconv"
@@ -26,20 +27,20 @@ const (
// pageNumber: the page number where the signature should be placed. // pageNumber: the page number where the signature should be placed.
// rect: the rectangle defining the position and size of the signature field. // rect: the rectangle defining the position and size of the signature field.
// Returns the visual signature string and an error if any. // Returns the visual signature string and an error if any.
func (context *SignContext) createVisualSignature(visible bool, pageNumber int, rect [4]float64) (visual_signature string, err error) { func (context *SignContext) createVisualSignature(visible bool, pageNumber int, rect [4]float64) ([]byte, error) {
// Initialize the visual signature object with its ID. var visual_signature bytes.Buffer
visual_signature = strconv.Itoa(int(context.VisualSignData.ObjectId)) + " 0 obj\n"
// Define the object as an annotation. // Define the object as an annotation.
visual_signature += "<< /Type /Annot" visual_signature.WriteString("<< /Type /Annot")
// Specify the annotation subtype as a widget. // Specify the annotation subtype as a widget.
visual_signature += " /Subtype /Widget" visual_signature.WriteString(" /Subtype /Widget")
if visible { if visible {
// Set the position and size of the signature field if visible. // Set the position and size of the signature field if visible.
visual_signature += fmt.Sprintf(" /Rect [%f %f %f %f]", rect[0], rect[1], rect[2], rect[3]) visual_signature.WriteString(fmt.Sprintf(" /Rect [%f %f %f %f]", rect[0], rect[1], rect[2], rect[3]))
} else { } else {
// Set the rectangle to zero if the signature is invisible. // Set the rectangle to zero if the signature is invisible.
visual_signature += " /Rect [0 0 0 0]" visual_signature.WriteString(" /Rect [0 0 0 0]")
} }
// Retrieve the root object from the PDF trailer. // Retrieve the root object from the PDF trailer.
@@ -64,7 +65,7 @@ func (context *SignContext) createVisualSignature(visible bool, pageNumber int,
// Find the page object by its number. // Find the page object by its number.
page, err := findPageByNumber(root.Key("Pages"), pageNumber) page, err := findPageByNumber(root.Key("Pages"), pageNumber)
if err != nil { if err != nil {
return "", err return nil, err
} }
// Get the pointer to the page object. // Get the pointer to the page object.
@@ -74,16 +75,16 @@ func (context *SignContext) createVisualSignature(visible bool, pageNumber int,
context.VisualSignData.PageId = page_ptr.GetID() context.VisualSignData.PageId = page_ptr.GetID()
// Add the page reference to the visual signature. // Add the page reference to the visual signature.
visual_signature += " /P " + strconv.Itoa(int(page_ptr.GetID())) + " " + strconv.Itoa(int(page_ptr.GetGen())) + " R" visual_signature.WriteString(" /P " + strconv.Itoa(int(page_ptr.GetID())) + " " + strconv.Itoa(int(page_ptr.GetGen())) + " R")
} }
// Define the annotation flags for the signature field (132) // Define the annotation flags for the signature field (132)
// annotationFlags := AnnotationFlagPrint | AnnotationFlagNoZoom | AnnotationFlagNoRotate | AnnotationFlagReadOnly | AnnotationFlagLockedContents // annotationFlags := AnnotationFlagPrint | AnnotationFlagNoZoom | AnnotationFlagNoRotate | AnnotationFlagReadOnly | AnnotationFlagLockedContents
visual_signature += fmt.Sprintf(" /F %d", 132) visual_signature.WriteString(fmt.Sprintf(" /F %d", 132))
// Define the field type as a signature. // Define the field type as a signature.
visual_signature += " /FT /Sig" visual_signature.WriteString(" /FT /Sig")
// Set a unique title for the signature field. // Set a unique title for the signature field.
visual_signature += " /T " + pdfString("Signature "+strconv.Itoa(len(context.SignData.ExistingSignatures)+1)) visual_signature.WriteString(" /T " + pdfString("Signature "+strconv.Itoa(len(context.SignData.ExistingSignatures)+1)))
// (Optional) A set of bit flags specifying the interpretation of specific entries // (Optional) A set of bit flags specifying the interpretation of specific entries
// in this dictionary. A value of 1 for the flag indicates that the associated entry // in this dictionary. A value of 1 for the flag indicates that the associated entry
@@ -92,16 +93,15 @@ func (context *SignContext) createVisualSignature(visible bool, pageNumber int,
// (Reasons); 5 (LegalAttestation); 6 (AddRevInfo); and 7 (DigestMethod). // (Reasons); 5 (LegalAttestation); 6 (AddRevInfo); and 7 (DigestMethod).
// For PDF 2.0 the following bit flags are added: 8 (Lockdocument); and 9 // For PDF 2.0 the following bit flags are added: 8 (Lockdocument); and 9
// (AppearanceFilter). Default value: 0. // (AppearanceFilter). Default value: 0.
visual_signature += " /Ff 0" visual_signature.WriteString(" /Ff 0")
// Reference the signature dictionary. // Reference the signature dictionary.
visual_signature += " /V " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R" visual_signature.WriteString(" /V " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R")
// Close the dictionary and end the object. // Close the dictionary and end the object.
visual_signature += " >>" visual_signature.WriteString(" >>\n")
visual_signature += "\nendobj\n"
return visual_signature, nil return visual_signature.Bytes(), nil
} }
// Helper function to find a page by its number. // Helper function to find a page by its number.

View File

@@ -48,7 +48,6 @@ func TestVisualSignature(t *testing.T) {
sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 3 sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 3
context := SignContext{ context := SignContext{
Filesize: size + 1,
PDFReader: rdr, PDFReader: rdr,
InputFile: input_file, InputFile: input_file,
VisualSignData: VisualSignData{ VisualSignData: VisualSignData{
@@ -63,7 +62,7 @@ func TestVisualSignature(t *testing.T) {
SignData: sign_data, SignData: sign_data,
} }
expected_visual_signature := "10 0 obj\n<< /Type /Annot /Subtype /Widget /Rect [0 0 0 0] /P 4 0 R /F 132 /FT /Sig /T (Signature 1) /Ff 0 /V 13 0 R >>\nendobj\n" expected_visual_signature := "<< /Type /Annot /Subtype /Widget /Rect [0 0 0 0] /P 4 0 R /F 132 /FT /Sig /T (Signature 1) /Ff 0 /V 13 0 R >>\n"
visual_signature, err := context.createVisualSignature(false, 1, [4]float64{0, 0, 0, 0}) visual_signature, err := context.createVisualSignature(false, 1, [4]float64{0, 0, 0, 0})
if err != nil { if err != nil {
@@ -71,7 +70,7 @@ func TestVisualSignature(t *testing.T) {
return return
} }
if visual_signature != expected_visual_signature { if string(visual_signature) != expected_visual_signature {
t.Errorf("Visual signature mismatch, expected %s, but got %s", expected_visual_signature, visual_signature) t.Errorf("Visual signature mismatch, expected\n%q\nbut got\n%q", expected_visual_signature, visual_signature)
} }
} }

View File

@@ -12,15 +12,60 @@ import (
"strings" "strings"
) )
type xrefEntry struct {
ID uint32
Offset int64
}
const ( const (
xrefStreamColumns = 5 xrefStreamColumns = 5
xrefStreamPredictor = 12 xrefStreamPredictor = 12
pngSubPredictor = 11 pngSubPredictor = 11
pngUpPredictor = 12 pngUpPredictor = 12
objectFooter = "\nendobj\n"
) )
func (context *SignContext) addObject(object []byte) (uint32, error) {
if context.lastXrefID == 0 {
lastXrefID, err := context.getLastObjectIDFromXref()
if err != nil {
return 0, fmt.Errorf("failed to get last object ID: %w", err)
}
context.lastXrefID = lastXrefID
}
objectID := context.lastXrefID + uint32(len(context.newXrefEntries)) + 1
context.newXrefEntries = append(context.newXrefEntries, xrefEntry{
ID: objectID,
Offset: int64(context.OutputBuffer.Buff.Len()) + 1,
})
// Write the object header
if _, err := context.OutputBuffer.Write([]byte(fmt.Sprintf("\n%d 0 obj\n", objectID))); err != nil {
return 0, fmt.Errorf("failed to write object header: %w", err)
}
// Write the object content
object = bytes.TrimSpace(object)
if _, err := context.OutputBuffer.Write(object); err != nil {
return 0, fmt.Errorf("failed to write object content: %w", err)
}
// Write the object footer
if _, err := context.OutputBuffer.Write([]byte(objectFooter)); err != nil {
return 0, fmt.Errorf("failed to write object footer: %w", err)
}
return objectID, nil
}
// writeXref writes the cross-reference table or stream based on the PDF type. // writeXref writes the cross-reference table or stream based on the PDF type.
func (context *SignContext) writeXref() error { func (context *SignContext) writeXref() error {
if _, err := context.OutputBuffer.Write([]byte("\n")); err != nil {
return fmt.Errorf("failed to write newline before xref: %w", err)
}
context.NewXrefStart = int64(context.OutputBuffer.Buff.Len())
switch context.PDFReader.XrefInformation.Type { switch context.PDFReader.XrefInformation.Type {
case "table": case "table":
return context.writeIncrXrefTable() return context.writeIncrXrefTable()
@@ -31,109 +76,56 @@ func (context *SignContext) writeXref() error {
} }
} }
// writeXrefTable writes the cross-reference table to the output buffer. func (context *SignContext) getLastObjectIDFromXref() (uint32, error) {
//
//nolint:unused
func (context *SignContext) writeXrefTable() error {
// Seek to the start of the xref table // Seek to the start of the xref table
if _, err := context.InputFile.Seek(context.PDFReader.XrefInformation.StartPos, io.SeekStart); err != nil { if _, err := context.InputFile.Seek(context.PDFReader.XrefInformation.StartPos, io.SeekStart); err != nil {
return fmt.Errorf("failed to seek to xref table: %w", err) return 0, fmt.Errorf("failed to seek to xref table: %w", err)
} }
// Read the existing xref table // Read the existing xref table
xrefContent := make([]byte, context.PDFReader.XrefInformation.Length) xrefContent := make([]byte, context.PDFReader.XrefInformation.Length)
if _, err := context.InputFile.Read(xrefContent); err != nil { if _, err := context.InputFile.Read(xrefContent); err != nil {
return fmt.Errorf("failed to read xref table: %w", err) return 0, fmt.Errorf("failed to read xref table: %w", err)
} }
// Parse the xref header // Parse the xref header
xrefLines := strings.Split(string(xrefContent), "\n") xrefLines := strings.Split(string(xrefContent), "\n")
xrefHeader := strings.Fields(xrefLines[1]) xrefHeader := strings.Fields(xrefLines[1])
if len(xrefHeader) != 2 { if len(xrefHeader) != 2 {
return fmt.Errorf("invalid xref header format") return 0, fmt.Errorf("invalid xref header format")
} }
firstObjectID, err := strconv.Atoi(xrefHeader[0]) firstObjectID, err := strconv.ParseUint(xrefHeader[0], 10, 32)
if err != nil { if err != nil {
return fmt.Errorf("invalid first object ID: %w", err) return 0, fmt.Errorf("invalid first object ID: %w", err)
} }
itemCount, err := strconv.Atoi(xrefHeader[1]) itemCount, err := strconv.ParseUint(xrefHeader[1], 10, 32)
if err != nil { if err != nil {
return fmt.Errorf("invalid item count: %w", err) return 0, fmt.Errorf("invalid item count: %w", err)
} }
// Calculate new entries return uint32(firstObjectID + itemCount), nil
newEntries := []struct {
startPosition int64
name string
}{
{context.Filesize, "visual signature"},
{context.Filesize + context.VisualSignData.Length, "catalog"},
{context.Filesize + context.VisualSignData.Length + context.CatalogData.Length, "signature"},
}
// Write new xref table
newXrefHeader := fmt.Sprintf("xref\n%d %d\n", firstObjectID, itemCount+len(newEntries))
if _, err := context.OutputBuffer.Write([]byte(newXrefHeader)); err != nil {
return fmt.Errorf("failed to write new xref header: %w", err)
}
// Write existing entries
for i, line := range xrefLines[2:] {
if i >= itemCount {
break
}
if _, err := context.OutputBuffer.Write([]byte(line + "\n")); err != nil {
return fmt.Errorf("failed to write existing xref entry: %w", err)
}
}
// Write new entries
for _, entry := range newEntries {
xrefLine := fmt.Sprintf("%010d 00000 n\r\n", entry.startPosition)
if _, err := context.OutputBuffer.Write([]byte(xrefLine)); err != nil {
return fmt.Errorf("failed to write new xref entry for %s: %w", entry.name, err)
}
}
return nil
} }
// writeIncrXrefTable writes the incremental cross-reference table to the output buffer. // writeIncrXrefTable writes the incremental cross-reference table to the output buffer.
func (context *SignContext) writeIncrXrefTable() error { func (context *SignContext) writeIncrXrefTable() error {
// Seek to the start of the xref table
if _, err := context.InputFile.Seek(context.PDFReader.XrefInformation.StartPos, io.SeekStart); err != nil {
return fmt.Errorf("failed to seek to xref table: %w", err)
}
// Calculate new entries
newEntries := []struct {
objectID uint32
startPosition int64
name string
}{
{context.VisualSignData.ObjectId, context.Filesize, "visual signature"},
{context.CatalogData.ObjectId, context.Filesize + context.VisualSignData.Length, "catalog"},
{context.SignData.ObjectId, context.Filesize + context.VisualSignData.Length + context.CatalogData.Length, "signature"},
}
// Write xref header // Write xref header
if _, err := context.OutputBuffer.Write([]byte("xref\n")); err != nil { if _, err := context.OutputBuffer.Write([]byte("xref\n")); err != nil {
return fmt.Errorf("failed to write incremental xref header: %w", err) return fmt.Errorf("failed to write incremental xref header: %w", err)
} }
// Write xref subsection header // Write xref subsection header
startXrefObj := fmt.Sprintf("%d %d\n", newEntries[0].objectID, len(newEntries)) startXrefObj := fmt.Sprintf("%d %d\n", context.lastXrefID+1, len(context.newXrefEntries))
if _, err := context.OutputBuffer.Write([]byte(startXrefObj)); err != nil { if _, err := context.OutputBuffer.Write([]byte(startXrefObj)); err != nil {
return fmt.Errorf("failed to write starting xref object: %w", err) return fmt.Errorf("failed to write starting xref object: %w", err)
} }
// Write new entries // Write new entries
for _, entry := range newEntries { for _, entry := range context.newXrefEntries {
xrefLine := fmt.Sprintf("%010d 00000 n \r\n", entry.startPosition) xrefLine := fmt.Sprintf("%010d 00000 n\r\n", entry.Offset)
if _, err := context.OutputBuffer.Write([]byte(xrefLine)); err != nil { if _, err := context.OutputBuffer.Write([]byte(xrefLine)); err != nil {
return fmt.Errorf("failed to write incremental xref entry for %s: %w", entry.name, err) return fmt.Errorf("failed to write incremental xref entry: %w", err)
} }
} }
@@ -168,17 +160,8 @@ func (context *SignContext) writeXrefStream() error {
// writeXrefStreamEntries writes the individual entries for the xref stream. // writeXrefStreamEntries writes the individual entries for the xref stream.
func writeXrefStreamEntries(buffer *bytes.Buffer, context *SignContext) error { func writeXrefStreamEntries(buffer *bytes.Buffer, context *SignContext) error {
entries := []struct { for _, entry := range context.newXrefEntries {
offset int64 writeXrefStreamLine(buffer, 1, int(entry.Offset), 0)
}{
{context.Filesize},
{context.Filesize + context.VisualSignData.Length},
{context.Filesize + context.VisualSignData.Length + context.CatalogData.Length},
{context.NewXrefStart},
}
for _, entry := range entries {
writeXrefStreamLine(buffer, 1, int(entry.offset), 0)
} }
return nil return nil
@@ -219,7 +202,7 @@ func writeXrefStreamHeader(context *SignContext, streamLength int) error {
xrefStreamColumns, xrefStreamColumns,
xrefStreamPredictor, xrefStreamPredictor,
context.PDFReader.XrefInformation.StartPos, context.PDFReader.XrefInformation.StartPos,
context.PDFReader.XrefInformation.ItemCount+4, context.PDFReader.XrefInformation.ItemCount+int64(len(context.newXrefEntries))+1,
context.PDFReader.XrefInformation.ItemCount, context.PDFReader.XrefInformation.ItemCount,
newRoot, newRoot,
id0, id0,

View File

@@ -17,7 +17,6 @@ import (
type CatalogData struct { type CatalogData struct {
ObjectId uint32 ObjectId uint32
Length int64
RootString string RootString string
} }
@@ -45,12 +44,10 @@ type SignData struct {
type VisualSignData struct { type VisualSignData struct {
PageId uint32 PageId uint32
ObjectId uint32 ObjectId uint32
Length int64
} }
type InfoData struct { type InfoData struct {
ObjectId uint32 ObjectId uint32
Length int64
} }
//go:generate stringer -type=CertType //go:generate stringer -type=CertType
@@ -87,21 +84,21 @@ type SignDataSignatureInfo struct {
} }
type SignContext struct { type SignContext struct {
Filesize int64 InputFile io.ReadSeeker
InputFile io.ReadSeeker OutputFile io.Writer
OutputFile io.Writer OutputBuffer *filebuffer.Buffer
OutputBuffer *filebuffer.Buffer SignData SignData
SignData SignData CatalogData CatalogData
CatalogData CatalogData VisualSignData VisualSignData
VisualSignData VisualSignData InfoData InfoData
InfoData InfoData PDFReader *pdf.Reader
PDFReader *pdf.Reader NewXrefStart int64
NewXrefStart int64 ByteRangeValues []int64
ByteRangeStartByte int64 SignatureMaxLength uint32
SignatureContentsStartByte int64 SignatureMaxLengthBase uint32
ByteRangeValues []int64
SignatureMaxLength uint32 lastXrefID uint32
SignatureMaxLengthBase uint32 newXrefEntries []xrefEntry
} }
func SignFile(input string, output string, sign_data SignData) error { func SignFile(input string, output string, sign_data SignData) error {
@@ -134,9 +131,7 @@ func SignFile(input string, output string, sign_data SignData) error {
func Sign(input io.ReadSeeker, output io.Writer, rdr *pdf.Reader, size int64, sign_data SignData) error { func Sign(input io.ReadSeeker, output io.Writer, rdr *pdf.Reader, size int64, sign_data SignData) error {
sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 2 sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 2
// We do size+1 because we insert a newline.
context := SignContext{ context := SignContext{
Filesize: size + 1,
PDFReader: rdr, PDFReader: rdr,
InputFile: input, InputFile: input,
OutputFile: output, OutputFile: output,
@@ -265,6 +260,22 @@ func (context *SignContext) SignPDF() error {
context.SignatureMaxLength += uint32(hex.EncodedLen(9000)) context.SignatureMaxLength += uint32(hex.EncodedLen(9000))
} }
// Create the signature object
var signature_object []byte
switch context.SignData.Signature.CertType {
case TimeStampSignature:
signature_object = context.createTimestampPlaceholder()
default:
signature_object = context.createSignaturePlaceholder()
}
// Write the new signature object
context.SignData.ObjectId, err = context.addObject(signature_object)
if err != nil {
return fmt.Errorf("failed to add signature object: %w", err)
}
// Create visual signature (visible or invisible based on CertType) // Create visual signature (visible or invisible based on CertType)
// visible := context.SignData.Signature.CertType == CertificationSignature // visible := context.SignData.Signature.CertType == CertificationSignature
// Example usage: passing page number and default rect values // Example usage: passing page number and default rect values
@@ -273,69 +284,45 @@ func (context *SignContext) SignPDF() error {
return fmt.Errorf("failed to create visual signature: %w", err) return fmt.Errorf("failed to create visual signature: %w", err)
} }
context.VisualSignData.Length = int64(len(visual_signature))
// Write the new visual signature object. // Write the new visual signature object.
if _, err := context.OutputBuffer.Write([]byte(visual_signature)); err != nil { context.VisualSignData.ObjectId, err = context.addObject(visual_signature)
return err if err != nil {
return fmt.Errorf("failed to add visual signature object: %w", err)
} }
// Create a new catalog object
catalog, err := context.createCatalog() catalog, err := context.createCatalog()
if err != nil { if err != nil {
return fmt.Errorf("failed to create catalog: %w", err) return fmt.Errorf("failed to create catalog: %w", err)
} }
context.CatalogData.Length = int64(len(catalog)) // Write the new catalog object
context.CatalogData.ObjectId, err = context.addObject(catalog)
// Write the new catalog object. if err != nil {
if _, err := context.OutputBuffer.Write([]byte(catalog)); err != nil { return fmt.Errorf("failed to add catalog object: %w", err)
return err
} }
// Create the signature object // Write xref table
var signature_object string
var byte_range_start_byte, signature_contents_start_byte int64
switch context.SignData.Signature.CertType {
case TimeStampSignature:
signature_object, byte_range_start_byte, signature_contents_start_byte = context.createTimestampPlaceholder()
default:
signature_object, byte_range_start_byte, signature_contents_start_byte = context.createSignaturePlaceholder()
}
appended_bytes := context.Filesize + int64(len(catalog)) + int64(len(visual_signature))
// Positions are relative to old start position of xref table.
byte_range_start_byte += appended_bytes
signature_contents_start_byte += appended_bytes
context.ByteRangeStartByte = byte_range_start_byte
context.SignatureContentsStartByte = signature_contents_start_byte
// Write the new signature object.
if _, err := context.OutputBuffer.Write([]byte(signature_object)); err != nil {
return fmt.Errorf("failed to create the new signature object: %w", err)
}
// Calculate the new start position of the xref table.
context.NewXrefStart = appended_bytes + int64(len(signature_object))
if err := context.writeXref(); err != nil { if err := context.writeXref(); err != nil {
return fmt.Errorf("failed to write xref: %w", err) return fmt.Errorf("failed to write xref: %w", err)
} }
// Write trailer
if err := context.writeTrailer(); err != nil { if err := context.writeTrailer(); err != nil {
return fmt.Errorf("failed to write trailer: %w", err) return fmt.Errorf("failed to write trailer: %w", err)
} }
// Update byte range
if err := context.updateByteRange(); err != nil { if err := context.updateByteRange(); err != nil {
return fmt.Errorf("failed to update byte range: %w", err) return fmt.Errorf("failed to update byte range: %w", err)
} }
// Replace signature
if err := context.replaceSignature(); err != nil { if err := context.replaceSignature(); err != nil {
return err return fmt.Errorf("failed to replace signature: %w", err)
} }
// Write final output
if _, err := context.OutputBuffer.Seek(0, 0); err != nil { if _, err := context.OutputBuffer.Seek(0, 0); err != nil {
return err return err
} }

View File

@@ -217,6 +217,8 @@ func TestSignPDFFileUTF8(t *testing.T) {
if err := os.Rename(tmpfile.Name(), "../testfiles/failed/"+originalFileName); err != nil { if err := os.Rename(tmpfile.Name(), "../testfiles/failed/"+originalFileName); err != nil {
t.Error(err) t.Error(err)
} }
} else if len(info.Signers) == 0 {
t.Fatalf("no signers found in %s", tmpfile.Name())
} else { } else {
if info.Signers[0].Name != signerName { if info.Signers[0].Name != signerName {
t.Fatalf("expected %q, got %q", signerName, info.Signers[0].Name) t.Fatalf("expected %q, got %q", signerName, info.Signers[0].Name)