Refactor object handling
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
@@ -9,43 +10,46 @@ func (context *SignContext) updateByteRange() error {
|
||||
if _, err := context.OutputBuffer.Seek(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
output_file_size := int64(context.OutputBuffer.Buff.Len())
|
||||
|
||||
// Calculate ByteRange values to replace them.
|
||||
context.ByteRangeValues = make([]int64, 4)
|
||||
// Set ByteRangeValues by looking for the /Contents< filled with zeros
|
||||
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.
|
||||
context.ByteRangeValues[0] = int64(0)
|
||||
|
||||
// Signature ByteRange part 1 length always stops at the actual signature start byte.
|
||||
context.ByteRangeValues[1] = context.SignatureContentsStartByte - 1
|
||||
|
||||
// Signature ByteRange part 2 start byte directly starts after the actual signature.
|
||||
context.ByteRangeValues[2] = context.ByteRangeValues[1] + 1 + int64(context.SignatureMaxLength) + 1
|
||||
|
||||
// Signature ByteRange part 2 length is everything else of the file.
|
||||
context.ByteRangeValues[3] = output_file_size - context.ByteRangeValues[2]
|
||||
// Calculate ByteRangeValues
|
||||
signatureContentsStart := int64(contentsIndex) - 1
|
||||
signatureContentsEnd := signatureContentsStart + int64(context.SignatureMaxLength) + 2
|
||||
context.ByteRangeValues = []int64{
|
||||
0,
|
||||
signatureContentsStart,
|
||||
signatureContentsEnd,
|
||||
int64(context.OutputBuffer.Buff.Len()) - signatureContentsEnd,
|
||||
}
|
||||
|
||||
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.
|
||||
new_byte_range += strings.Repeat(" ", len(signatureByteRangePlaceholder)-len(new_byte_range))
|
||||
|
||||
if _, err := context.OutputBuffer.Seek(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
file_content := context.OutputBuffer.Buff.Bytes()
|
||||
|
||||
if _, err := context.OutputBuffer.Write(file_content[:context.ByteRangeStartByte]); err != nil {
|
||||
return err
|
||||
// Make sure our ByteRange string has the same length as the placeholder.
|
||||
if len(new_byte_range) < len(signatureByteRangePlaceholder) {
|
||||
new_byte_range += strings.Repeat(" ", len(signatureByteRangePlaceholder)-len(new_byte_range))
|
||||
} else if len(new_byte_range) != len(signatureByteRangePlaceholder) {
|
||||
return fmt.Errorf("new byte range string is the same lenght as the placeholder")
|
||||
}
|
||||
|
||||
// Write new ByteRange.
|
||||
if _, err := context.OutputBuffer.Write([]byte(new_byte_range)); err != nil {
|
||||
return err
|
||||
// Find the placeholder in the buffer
|
||||
placeholderIndex := bytes.Index(context.OutputBuffer.Buff.Bytes(), []byte(signatureByteRangePlaceholder))
|
||||
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
|
||||
}
|
||||
|
||||
|
@@ -1,16 +1,16 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (context *SignContext) createCatalog() (string, error) {
|
||||
var catalogBuilder strings.Builder
|
||||
func (context *SignContext) createCatalog() ([]byte, error) {
|
||||
var catalog_buffer bytes.Buffer
|
||||
|
||||
// Start the catalog object
|
||||
catalogBuilder.WriteString(strconv.Itoa(int(context.CatalogData.ObjectId)) + " 0 obj\n")
|
||||
catalogBuilder.WriteString("<< /Type /Catalog")
|
||||
catalog_buffer.WriteString("<<\n")
|
||||
catalog_buffer.WriteString(" /Type /Catalog")
|
||||
|
||||
// (Optional; PDF 1.4) The version of the PDF specification to which
|
||||
// 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 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
|
||||
@@ -49,33 +49,33 @@ func (context *SignContext) createCatalog() (string, error) {
|
||||
// Add Pages and Names references if they exist
|
||||
if foundPages {
|
||||
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 {
|
||||
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
|
||||
catalogBuilder.WriteString(" /AcroForm << /Fields [")
|
||||
catalog_buffer.WriteString(" /AcroForm << /Fields [")
|
||||
|
||||
// Add existing signatures to the AcroForm dictionary
|
||||
for i, sig := range context.SignData.ExistingSignatures {
|
||||
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
|
||||
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)
|
||||
//
|
||||
@@ -100,14 +100,14 @@ func (context *SignContext) createCatalog() (string, error) {
|
||||
// Set SigFlags and Permissions based on Signature Type
|
||||
switch context.SignData.Signature.CertType {
|
||||
case CertificationSignature, ApprovalSignature, TimeStampSignature:
|
||||
catalogBuilder.WriteString(" /SigFlags 3")
|
||||
catalog_buffer.WriteString(" /SigFlags 3")
|
||||
case UsageRightsSignature:
|
||||
catalogBuilder.WriteString(" /SigFlags 1")
|
||||
catalog_buffer.WriteString(" /SigFlags 1")
|
||||
}
|
||||
|
||||
// Finalize the AcroForm and Catalog object
|
||||
catalogBuilder.WriteString(" >>") // Close AcroForm
|
||||
catalogBuilder.WriteString(" >>\nendobj\n") // Close catalog object
|
||||
catalog_buffer.WriteString(" >>\n") // Close AcroForm
|
||||
catalog_buffer.WriteString(">>\n") // Close Catalog
|
||||
|
||||
return catalogBuilder.String(), nil
|
||||
return catalog_buffer.Bytes(), nil
|
||||
}
|
||||
|
@@ -15,17 +15,17 @@ var testFiles = []struct {
|
||||
{
|
||||
file: "../testfiles/testfile20.pdf",
|
||||
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",
|
||||
UsageRightsSignature: "11 0 obj\n<< /Type /Catalog /Pages 3 0 R /AcroForm << /Fields [10 0 R] /NeedAppearances false /SigFlags 1 >> >>\nendobj\n",
|
||||
ApprovalSignature: "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: "<<\n /Type /Catalog /Pages 3 0 R /AcroForm << /Fields [10 0 R] /NeedAppearances false /SigFlags 1 >>\n>>\n",
|
||||
ApprovalSignature: "<<\n /Type /Catalog /Pages 3 0 R /AcroForm << /Fields [10 0 R] /NeedAppearances false /SigFlags 3 >>\n>>\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
file: "../testfiles/testfile21.pdf",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
CertificationSignature: "<<\n /Type /Catalog /Pages 9 0 R /Names 6 0 R /AcroForm << /Fields [16 0 R] /NeedAppearances false /SigFlags 3 >>\n>>\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: "<<\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{
|
||||
Filesize: size + 1,
|
||||
PDFReader: rdr,
|
||||
InputFile: inputFile,
|
||||
VisualSignData: VisualSignData{
|
||||
@@ -80,8 +79,8 @@ func TestCreateCatalog(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
if catalog != expectedCatalog {
|
||||
st.Errorf("Catalog mismatch, expected %s, but got %s", expectedCatalog, catalog)
|
||||
if string(catalog) != expectedCatalog {
|
||||
st.Errorf("Catalog mismatch, expected\n%s\nbut got\n%s", expectedCatalog, catalog)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -21,23 +21,20 @@ import (
|
||||
|
||||
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.
|
||||
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(" /SubFilter /adbe.pkcs7.detached\n")
|
||||
|
||||
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.
|
||||
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.
|
||||
signature_buffer.WriteString(" /Contents<")
|
||||
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("\n")
|
||||
}
|
||||
signature_buffer.WriteString(" /M ")
|
||||
signature_buffer.WriteString(pdfDateTime(context.SignData.Signature.Info.Date))
|
||||
signature_buffer.WriteString("\n")
|
||||
|
||||
signature_buffer.WriteString(" >>\n")
|
||||
signature_buffer.WriteString("endobj\n")
|
||||
// (Optional) The time of signing. Depending on the signature handler, this may
|
||||
// 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
|
||||
|
||||
timestamp_buffer.WriteString(strconv.Itoa(int(context.SignData.ObjectId)) + " 0 obj\n")
|
||||
timestamp_buffer.WriteString("<< /Type /DocTimeStamp\n")
|
||||
timestamp_buffer.WriteString("<<\n")
|
||||
timestamp_buffer.WriteString(" /Type /DocTimeStamp\n")
|
||||
timestamp_buffer.WriteString(" /Filter /Adobe.PPKLite\n")
|
||||
timestamp_buffer.WriteString(" /SubFilter /ETSI.RFC3161\n")
|
||||
|
||||
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.
|
||||
timestamp_buffer.WriteString(" " + signatureByteRangePlaceholder)
|
||||
|
||||
signature_contents_start_byte = int64(timestamp_buffer.Len()) + 11
|
||||
|
||||
timestamp_buffer.WriteString(" /Contents<")
|
||||
timestamp_buffer.Write(bytes.Repeat([]byte("0"), int(context.SignatureMaxLength)))
|
||||
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 {
|
||||
@@ -446,16 +450,31 @@ func (context *SignContext) replaceSignature() error {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// Write new ByteRange.
|
||||
if _, err := context.OutputBuffer.Write([]byte(dst)); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
|
@@ -1,100 +1,91 @@
|
||||
package sign
|
||||
|
||||
// import (
|
||||
// "fmt"
|
||||
// "os"
|
||||
// "testing"
|
||||
// "time"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
// "github.com/digitorus/pdf"
|
||||
// )
|
||||
"github.com/digitorus/pdf"
|
||||
)
|
||||
|
||||
// var signatureTests = []struct {
|
||||
// file string
|
||||
// expectedSignatures map[uint]string
|
||||
// }{
|
||||
// {
|
||||
// file: "../testfiles/testfile20.pdf",
|
||||
// expectedSignatures: map[uint]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",
|
||||
// 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",
|
||||
// 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",
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
var signatureTests = []struct {
|
||||
file string
|
||||
expectedSignatures map[CertType]string
|
||||
}{
|
||||
{
|
||||
file: "../testfiles/testfile20.pdf",
|
||||
expectedSignatures: map[CertType]string{
|
||||
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: "<<\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: "<<\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) {
|
||||
// for _, testFile := range signatureTests {
|
||||
// for certType, expectedSignature := range testFile.expectedSignatures {
|
||||
// t.Run(fmt.Sprintf("%s_certType-%d", testFile.file, certType), func(st *testing.T) {
|
||||
// inputFile, err := os.Open(testFile.file)
|
||||
// if err != nil {
|
||||
// st.Errorf("Failed to load test PDF")
|
||||
// return
|
||||
// }
|
||||
func TestCreateSignaturePlaceholder(t *testing.T) {
|
||||
for _, testFile := range signatureTests {
|
||||
for certType, expectedSignature := range testFile.expectedSignatures {
|
||||
t.Run(fmt.Sprintf("%s_certType-%d", testFile.file, certType), func(st *testing.T) {
|
||||
inputFile, err := os.Open(testFile.file)
|
||||
if err != nil {
|
||||
st.Errorf("Failed to load test PDF")
|
||||
return
|
||||
}
|
||||
|
||||
// finfo, err := inputFile.Stat()
|
||||
// if err != nil {
|
||||
// st.Errorf("Failed to load test PDF")
|
||||
// return
|
||||
// }
|
||||
// size := finfo.Size()
|
||||
finfo, err := inputFile.Stat()
|
||||
if err != nil {
|
||||
st.Errorf("Failed to load test PDF")
|
||||
return
|
||||
}
|
||||
size := finfo.Size()
|
||||
|
||||
// rdr, err := pdf.NewReader(inputFile, size)
|
||||
// if err != nil {
|
||||
// st.Errorf("Failed to load test PDF")
|
||||
// return
|
||||
// }
|
||||
rdr, err := pdf.NewReader(inputFile, size)
|
||||
if err != nil {
|
||||
st.Errorf("Failed to load test PDF")
|
||||
return
|
||||
}
|
||||
|
||||
// timezone, _ := time.LoadLocation("Europe/Tallinn")
|
||||
// now := time.Date(2017, 9, 23, 14, 39, 0, 0, timezone)
|
||||
timezone, _ := time.LoadLocation("Europe/Tallinn")
|
||||
now := time.Date(2017, 9, 23, 14, 39, 0, 0, timezone)
|
||||
|
||||
// sign_data := SignData{
|
||||
// Signature: SignDataSignature{
|
||||
// Info: SignDataSignatureInfo{
|
||||
// Name: "John Doe",
|
||||
// Location: "Somewhere",
|
||||
// Reason: "Test",
|
||||
// ContactInfo: "None",
|
||||
// Date: now,
|
||||
// },
|
||||
// CertType: certType,
|
||||
// DocMDPPerm: AllowFillingExistingFormFieldsAndSignaturesPerms,
|
||||
// },
|
||||
// }
|
||||
sign_data := SignData{
|
||||
Signature: SignDataSignature{
|
||||
Info: SignDataSignatureInfo{
|
||||
Name: "John Doe",
|
||||
Location: "Somewhere",
|
||||
Reason: "Test",
|
||||
ContactInfo: "None",
|
||||
Date: now,
|
||||
},
|
||||
CertType: certType,
|
||||
DocMDPPerm: AllowFillingExistingFormFieldsAndSignaturesPerms,
|
||||
},
|
||||
}
|
||||
|
||||
// sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 3
|
||||
sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 3
|
||||
|
||||
// context := SignContext{
|
||||
// Filesize: size + 1,
|
||||
// PDFReader: rdr,
|
||||
// InputFile: inputFile,
|
||||
// VisualSignData: VisualSignData{
|
||||
// ObjectId: uint32(rdr.XrefInformation.ItemCount),
|
||||
// },
|
||||
// CatalogData: CatalogData{
|
||||
// ObjectId: uint32(rdr.XrefInformation.ItemCount) + 1,
|
||||
// },
|
||||
// InfoData: InfoData{
|
||||
// ObjectId: uint32(rdr.XrefInformation.ItemCount) + 2,
|
||||
// },
|
||||
// SignData: sign_data,
|
||||
// }
|
||||
context := SignContext{
|
||||
PDFReader: rdr,
|
||||
InputFile: inputFile,
|
||||
VisualSignData: VisualSignData{
|
||||
ObjectId: uint32(rdr.XrefInformation.ItemCount),
|
||||
},
|
||||
CatalogData: CatalogData{
|
||||
ObjectId: uint32(rdr.XrefInformation.ItemCount) + 1,
|
||||
},
|
||||
InfoData: InfoData{
|
||||
ObjectId: uint32(rdr.XrefInformation.ItemCount) + 2,
|
||||
},
|
||||
SignData: sign_data,
|
||||
}
|
||||
|
||||
// signature, byte_range_start_byte, signature_contents_start_byte := context.createSignaturePlaceholder()
|
||||
signature := context.createSignaturePlaceholder()
|
||||
|
||||
// if signature != expectedSignature {
|
||||
// st.Errorf("Signature mismatch, expected %s, but got %s", 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)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
if string(signature) != expectedSignature {
|
||||
st.Errorf("Signature mismatch, expected:\n%q\nbut got:\n%q", expectedSignature, signature)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@ func (context *SignContext) writeTrailer() error {
|
||||
new_root := "Root " + strconv.FormatInt(int64(context.CatalogData.ObjectId), 10) + " 0 R"
|
||||
|
||||
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()
|
||||
new_prev := "Prev " + strconv.FormatInt(context.PDFReader.XrefInformation.StartPos, 10)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
@@ -26,20 +27,20 @@ const (
|
||||
// pageNumber: the page number where the signature should be placed.
|
||||
// rect: the rectangle defining the position and size of the signature field.
|
||||
// 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) {
|
||||
// Initialize the visual signature object with its ID.
|
||||
visual_signature = strconv.Itoa(int(context.VisualSignData.ObjectId)) + " 0 obj\n"
|
||||
func (context *SignContext) createVisualSignature(visible bool, pageNumber int, rect [4]float64) ([]byte, error) {
|
||||
var visual_signature bytes.Buffer
|
||||
|
||||
// Define the object as an annotation.
|
||||
visual_signature += "<< /Type /Annot"
|
||||
visual_signature.WriteString("<< /Type /Annot")
|
||||
// Specify the annotation subtype as a widget.
|
||||
visual_signature += " /Subtype /Widget"
|
||||
visual_signature.WriteString(" /Subtype /Widget")
|
||||
|
||||
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 {
|
||||
// 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.
|
||||
@@ -64,7 +65,7 @@ func (context *SignContext) createVisualSignature(visible bool, pageNumber int,
|
||||
// Find the page object by its number.
|
||||
page, err := findPageByNumber(root.Key("Pages"), pageNumber)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
// 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)
|
||||
// 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.
|
||||
visual_signature += " /FT /Sig"
|
||||
visual_signature.WriteString(" /FT /Sig")
|
||||
// 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
|
||||
// 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).
|
||||
// For PDF 2.0 the following bit flags are added: 8 (Lockdocument); and 9
|
||||
// (AppearanceFilter). Default value: 0.
|
||||
visual_signature += " /Ff 0"
|
||||
visual_signature.WriteString(" /Ff 0")
|
||||
|
||||
// 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.
|
||||
visual_signature += " >>"
|
||||
visual_signature += "\nendobj\n"
|
||||
visual_signature.WriteString(" >>\n")
|
||||
|
||||
return visual_signature, nil
|
||||
return visual_signature.Bytes(), nil
|
||||
}
|
||||
|
||||
// Helper function to find a page by its number.
|
||||
|
@@ -48,7 +48,6 @@ func TestVisualSignature(t *testing.T) {
|
||||
sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 3
|
||||
|
||||
context := SignContext{
|
||||
Filesize: size + 1,
|
||||
PDFReader: rdr,
|
||||
InputFile: input_file,
|
||||
VisualSignData: VisualSignData{
|
||||
@@ -63,7 +62,7 @@ func TestVisualSignature(t *testing.T) {
|
||||
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})
|
||||
if err != nil {
|
||||
@@ -71,7 +70,7 @@ func TestVisualSignature(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
if visual_signature != expected_visual_signature {
|
||||
t.Errorf("Visual signature mismatch, expected %s, but got %s", expected_visual_signature, visual_signature)
|
||||
if string(visual_signature) != expected_visual_signature {
|
||||
t.Errorf("Visual signature mismatch, expected\n%q\nbut got\n%q", expected_visual_signature, visual_signature)
|
||||
}
|
||||
}
|
||||
|
139
sign/pdfxref.go
139
sign/pdfxref.go
@@ -12,15 +12,60 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type xrefEntry struct {
|
||||
ID uint32
|
||||
Offset int64
|
||||
}
|
||||
|
||||
const (
|
||||
xrefStreamColumns = 5
|
||||
xrefStreamPredictor = 12
|
||||
pngSubPredictor = 11
|
||||
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.
|
||||
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 {
|
||||
case "table":
|
||||
return context.writeIncrXrefTable()
|
||||
@@ -31,109 +76,56 @@ func (context *SignContext) writeXref() error {
|
||||
}
|
||||
}
|
||||
|
||||
// writeXrefTable writes the cross-reference table to the output buffer.
|
||||
//
|
||||
//nolint:unused
|
||||
func (context *SignContext) writeXrefTable() error {
|
||||
func (context *SignContext) getLastObjectIDFromXref() (uint32, 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)
|
||||
return 0, fmt.Errorf("failed to seek to xref table: %w", err)
|
||||
}
|
||||
|
||||
// Read the existing xref table
|
||||
xrefContent := make([]byte, context.PDFReader.XrefInformation.Length)
|
||||
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
|
||||
xrefLines := strings.Split(string(xrefContent), "\n")
|
||||
xrefHeader := strings.Fields(xrefLines[1])
|
||||
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 {
|
||||
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 {
|
||||
return fmt.Errorf("invalid item count: %w", err)
|
||||
return 0, fmt.Errorf("invalid item count: %w", err)
|
||||
}
|
||||
|
||||
// Calculate new entries
|
||||
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
|
||||
return uint32(firstObjectID + itemCount), nil
|
||||
}
|
||||
|
||||
// writeIncrXrefTable writes the incremental cross-reference table to the output buffer.
|
||||
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
|
||||
if _, err := context.OutputBuffer.Write([]byte("xref\n")); err != nil {
|
||||
return fmt.Errorf("failed to write incremental xref header: %w", err)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return fmt.Errorf("failed to write starting xref object: %w", err)
|
||||
}
|
||||
|
||||
// Write new entries
|
||||
for _, entry := range newEntries {
|
||||
xrefLine := fmt.Sprintf("%010d 00000 n \r\n", entry.startPosition)
|
||||
for _, entry := range context.newXrefEntries {
|
||||
xrefLine := fmt.Sprintf("%010d 00000 n\r\n", entry.Offset)
|
||||
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.
|
||||
func writeXrefStreamEntries(buffer *bytes.Buffer, context *SignContext) error {
|
||||
entries := []struct {
|
||||
offset int64
|
||||
}{
|
||||
{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)
|
||||
for _, entry := range context.newXrefEntries {
|
||||
writeXrefStreamLine(buffer, 1, int(entry.Offset), 0)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -219,7 +202,7 @@ func writeXrefStreamHeader(context *SignContext, streamLength int) error {
|
||||
xrefStreamColumns,
|
||||
xrefStreamPredictor,
|
||||
context.PDFReader.XrefInformation.StartPos,
|
||||
context.PDFReader.XrefInformation.ItemCount+4,
|
||||
context.PDFReader.XrefInformation.ItemCount+int64(len(context.newXrefEntries))+1,
|
||||
context.PDFReader.XrefInformation.ItemCount,
|
||||
newRoot,
|
||||
id0,
|
||||
|
103
sign/sign.go
103
sign/sign.go
@@ -17,7 +17,6 @@ import (
|
||||
|
||||
type CatalogData struct {
|
||||
ObjectId uint32
|
||||
Length int64
|
||||
RootString string
|
||||
}
|
||||
|
||||
@@ -45,12 +44,10 @@ type SignData struct {
|
||||
type VisualSignData struct {
|
||||
PageId uint32
|
||||
ObjectId uint32
|
||||
Length int64
|
||||
}
|
||||
|
||||
type InfoData struct {
|
||||
ObjectId uint32
|
||||
Length int64
|
||||
}
|
||||
|
||||
//go:generate stringer -type=CertType
|
||||
@@ -87,21 +84,21 @@ type SignDataSignatureInfo struct {
|
||||
}
|
||||
|
||||
type SignContext struct {
|
||||
Filesize int64
|
||||
InputFile io.ReadSeeker
|
||||
OutputFile io.Writer
|
||||
OutputBuffer *filebuffer.Buffer
|
||||
SignData SignData
|
||||
CatalogData CatalogData
|
||||
VisualSignData VisualSignData
|
||||
InfoData InfoData
|
||||
PDFReader *pdf.Reader
|
||||
NewXrefStart int64
|
||||
ByteRangeStartByte int64
|
||||
SignatureContentsStartByte int64
|
||||
ByteRangeValues []int64
|
||||
SignatureMaxLength uint32
|
||||
SignatureMaxLengthBase uint32
|
||||
InputFile io.ReadSeeker
|
||||
OutputFile io.Writer
|
||||
OutputBuffer *filebuffer.Buffer
|
||||
SignData SignData
|
||||
CatalogData CatalogData
|
||||
VisualSignData VisualSignData
|
||||
InfoData InfoData
|
||||
PDFReader *pdf.Reader
|
||||
NewXrefStart int64
|
||||
ByteRangeValues []int64
|
||||
SignatureMaxLength uint32
|
||||
SignatureMaxLengthBase uint32
|
||||
|
||||
lastXrefID uint32
|
||||
newXrefEntries []xrefEntry
|
||||
}
|
||||
|
||||
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 {
|
||||
sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 2
|
||||
|
||||
// We do size+1 because we insert a newline.
|
||||
context := SignContext{
|
||||
Filesize: size + 1,
|
||||
PDFReader: rdr,
|
||||
InputFile: input,
|
||||
OutputFile: output,
|
||||
@@ -265,6 +260,22 @@ func (context *SignContext) SignPDF() error {
|
||||
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)
|
||||
// visible := context.SignData.Signature.CertType == CertificationSignature
|
||||
// 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)
|
||||
}
|
||||
|
||||
context.VisualSignData.Length = int64(len(visual_signature))
|
||||
|
||||
// Write the new visual signature object.
|
||||
if _, err := context.OutputBuffer.Write([]byte(visual_signature)); err != nil {
|
||||
return err
|
||||
context.VisualSignData.ObjectId, err = context.addObject(visual_signature)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add visual signature object: %w", err)
|
||||
}
|
||||
|
||||
// Create a new catalog object
|
||||
catalog, err := context.createCatalog()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create catalog: %w", err)
|
||||
}
|
||||
|
||||
context.CatalogData.Length = int64(len(catalog))
|
||||
|
||||
// Write the new catalog object.
|
||||
if _, err := context.OutputBuffer.Write([]byte(catalog)); err != nil {
|
||||
return err
|
||||
// Write the new catalog object
|
||||
context.CatalogData.ObjectId, err = context.addObject(catalog)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add catalog object: %w", err)
|
||||
}
|
||||
|
||||
// Create the signature object
|
||||
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))
|
||||
|
||||
// Write xref table
|
||||
if err := context.writeXref(); err != nil {
|
||||
return fmt.Errorf("failed to write xref: %w", err)
|
||||
}
|
||||
|
||||
// Write trailer
|
||||
if err := context.writeTrailer(); err != nil {
|
||||
return fmt.Errorf("failed to write trailer: %w", err)
|
||||
}
|
||||
|
||||
// Update byte range
|
||||
if err := context.updateByteRange(); err != nil {
|
||||
return fmt.Errorf("failed to update byte range: %w", err)
|
||||
}
|
||||
|
||||
// Replace signature
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@@ -217,6 +217,8 @@ func TestSignPDFFileUTF8(t *testing.T) {
|
||||
if err := os.Rename(tmpfile.Name(), "../testfiles/failed/"+originalFileName); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
} else if len(info.Signers) == 0 {
|
||||
t.Fatalf("no signers found in %s", tmpfile.Name())
|
||||
} else {
|
||||
if info.Signers[0].Name != signerName {
|
||||
t.Fatalf("expected %q, got %q", signerName, info.Signers[0].Name)
|
||||
|
Reference in New Issue
Block a user