Add initial support for signature appearance
This commit is contained in:
62
sign/appearance.go
Normal file
62
sign/appearance.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package sign
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (context *SignContext) createAppearance(rect [4]float64) ([]byte, error) {
|
||||||
|
text := context.SignData.Signature.Info.Name
|
||||||
|
|
||||||
|
rectWidth := rect[2] - rect[0]
|
||||||
|
rectHeight := rect[3] - rect[1]
|
||||||
|
|
||||||
|
if rectWidth < 1 || rectHeight < 1 {
|
||||||
|
return nil, fmt.Errorf("invalid rectangle dimensions: width %.2f and height %.2f must be greater than 0", rectWidth, rectHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate font size
|
||||||
|
fontSize := rectHeight * 0.8 // Initial font size
|
||||||
|
textWidth := float64(len(text)) * fontSize * 0.5 // Approximate text width
|
||||||
|
if textWidth > rectWidth {
|
||||||
|
fontSize = rectWidth / (float64(len(text)) * 0.5) // Adjust font size to fit text within rect width
|
||||||
|
}
|
||||||
|
|
||||||
|
var appearance_stream_buffer bytes.Buffer
|
||||||
|
appearance_stream_buffer.WriteString("q\n") // Save graphics state
|
||||||
|
appearance_stream_buffer.WriteString("BT\n") // Begin text
|
||||||
|
appearance_stream_buffer.WriteString(fmt.Sprintf("/F1 %.2f Tf\n", fontSize)) // Font and size
|
||||||
|
appearance_stream_buffer.WriteString(fmt.Sprintf("0 %.2f Td\n", rectHeight-fontSize)) // Position in unit square
|
||||||
|
appearance_stream_buffer.WriteString("0.2 0.2 0.6 rg\n") // Set font color to ballpoint-like color (RGB)
|
||||||
|
appearance_stream_buffer.WriteString(fmt.Sprintf("%s Tj\n", pdfString(text))) // Show text
|
||||||
|
appearance_stream_buffer.WriteString("ET\n") // End text
|
||||||
|
appearance_stream_buffer.WriteString("Q\n") // Restore graphics state
|
||||||
|
|
||||||
|
var appearance_buffer bytes.Buffer
|
||||||
|
appearance_buffer.WriteString("<<\n")
|
||||||
|
appearance_buffer.WriteString(" /Type /XObject\n")
|
||||||
|
appearance_buffer.WriteString(" /Subtype /Form\n")
|
||||||
|
appearance_buffer.WriteString(fmt.Sprintf(" /BBox [0 0 %f %f]\n", rectWidth, rectHeight))
|
||||||
|
appearance_buffer.WriteString(" /Matrix [1 0 0 1 0 0]\n") // No scaling or translation
|
||||||
|
|
||||||
|
// Resources dictionary
|
||||||
|
appearance_buffer.WriteString(" /Resources <<\n")
|
||||||
|
appearance_buffer.WriteString(" /Font <<\n")
|
||||||
|
appearance_buffer.WriteString(" /F1 <<\n")
|
||||||
|
appearance_buffer.WriteString(" /Type /Font\n")
|
||||||
|
appearance_buffer.WriteString(" /Subtype /Type1\n")
|
||||||
|
appearance_buffer.WriteString(" /BaseFont /Times-Roman\n")
|
||||||
|
appearance_buffer.WriteString(" >>\n")
|
||||||
|
appearance_buffer.WriteString(" >>\n")
|
||||||
|
appearance_buffer.WriteString(" >>\n")
|
||||||
|
|
||||||
|
appearance_buffer.WriteString(" /FormType 1\n")
|
||||||
|
appearance_buffer.WriteString(fmt.Sprintf(" /Length %d\n", appearance_stream_buffer.Len()))
|
||||||
|
appearance_buffer.WriteString(">>\n")
|
||||||
|
|
||||||
|
appearance_buffer.WriteString("stream\n")
|
||||||
|
appearance_buffer.Write(appearance_stream_buffer.Bytes())
|
||||||
|
appearance_buffer.WriteString("endstream\n")
|
||||||
|
|
||||||
|
return appearance_buffer.Bytes(), nil
|
||||||
|
}
|
@@ -10,7 +10,7 @@ func (context *SignContext) createCatalog() ([]byte, error) {
|
|||||||
|
|
||||||
// Start the catalog object
|
// Start the catalog object
|
||||||
catalog_buffer.WriteString("<<\n")
|
catalog_buffer.WriteString("<<\n")
|
||||||
catalog_buffer.WriteString(" /Type /Catalog")
|
catalog_buffer.WriteString(" /Type /Catalog\n")
|
||||||
|
|
||||||
// (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
|
||||||
@@ -49,33 +49,45 @@ func (context *SignContext) createCatalog() ([]byte, 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()
|
||||||
catalog_buffer.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\n")
|
||||||
}
|
}
|
||||||
if foundNames {
|
if foundNames {
|
||||||
names := root.Key("Names").GetPtr()
|
names := root.Key("Names").GetPtr()
|
||||||
catalog_buffer.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\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the AcroForm dictionary with /NeedAppearances
|
// Start the AcroForm dictionary with /NeedAppearances
|
||||||
catalog_buffer.WriteString(" /AcroForm << /Fields [")
|
catalog_buffer.WriteString(" /AcroForm <<\n")
|
||||||
|
catalog_buffer.WriteString(" /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.existingSignatures {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
catalog_buffer.WriteString(" ")
|
catalog_buffer.WriteString(" ")
|
||||||
}
|
}
|
||||||
catalog_buffer.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.existingSignatures) > 0 {
|
||||||
catalog_buffer.WriteString(" ")
|
catalog_buffer.WriteString(" ")
|
||||||
}
|
}
|
||||||
catalog_buffer.WriteString(strconv.Itoa(int(context.VisualSignData.ObjectId)) + " 0 R")
|
catalog_buffer.WriteString(strconv.Itoa(int(context.VisualSignData.objectId)) + " 0 R")
|
||||||
|
|
||||||
catalog_buffer.WriteString("]") // close Fields array
|
catalog_buffer.WriteString("]\n") // close Fields array
|
||||||
|
|
||||||
catalog_buffer.WriteString(" /NeedAppearances false")
|
// (Optional; deprecated in PDF 2.0) A flag specifying whether
|
||||||
|
// to construct appearance streams and appearance
|
||||||
|
// dictionaries for all widget annotations in the document (see
|
||||||
|
// 12.7.4.3, "Variable text"). Default value: false. A PDF writer
|
||||||
|
// shall include this key, with a value of true, if it has not
|
||||||
|
// provided appearance streams for all visible widget
|
||||||
|
// annotations present in the document.
|
||||||
|
// if context.SignData.Visible {
|
||||||
|
// catalog_buffer.WriteString(" /NeedAppearances true")
|
||||||
|
// } else {
|
||||||
|
// catalog_buffer.WriteString(" /NeedAppearances false")
|
||||||
|
// }
|
||||||
|
|
||||||
// Signature flags (Table 225)
|
// Signature flags (Table 225)
|
||||||
//
|
//
|
||||||
@@ -100,14 +112,14 @@ func (context *SignContext) createCatalog() ([]byte, 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:
|
||||||
catalog_buffer.WriteString(" /SigFlags 3")
|
catalog_buffer.WriteString(" /SigFlags 3\n")
|
||||||
case UsageRightsSignature:
|
case UsageRightsSignature:
|
||||||
catalog_buffer.WriteString(" /SigFlags 1")
|
catalog_buffer.WriteString(" /SigFlags 1\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finalize the AcroForm and Catalog object
|
// Finalize the AcroForm and Catalog object
|
||||||
catalog_buffer.WriteString(" >>\n") // Close AcroForm
|
catalog_buffer.WriteString(" >>\n") // Close AcroForm
|
||||||
catalog_buffer.WriteString(">>\n") // Close Catalog
|
catalog_buffer.WriteString(">>\n") // Close Catalog
|
||||||
|
|
||||||
return catalog_buffer.Bytes(), nil
|
return catalog_buffer.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
@@ -15,17 +15,17 @@ var testFiles = []struct {
|
|||||||
{
|
{
|
||||||
file: "../testfiles/testfile20.pdf",
|
file: "../testfiles/testfile20.pdf",
|
||||||
expectedCatalogs: map[CertType]string{
|
expectedCatalogs: map[CertType]string{
|
||||||
CertificationSignature: "<<\n /Type /Catalog /Pages 3 0 R /AcroForm << /Fields [10 0 R] /NeedAppearances false /SigFlags 3 >>\n>>\n",
|
CertificationSignature: "<<\n /Type /Catalog\n /Pages 3 0 R\n /AcroForm <<\n /Fields [10 0 R]\n /SigFlags 3\n >>\n>>\n",
|
||||||
UsageRightsSignature: "<<\n /Type /Catalog /Pages 3 0 R /AcroForm << /Fields [10 0 R] /NeedAppearances false /SigFlags 1 >>\n>>\n",
|
UsageRightsSignature: "<<\n /Type /Catalog\n /Pages 3 0 R\n /AcroForm <<\n /Fields [10 0 R]\n /SigFlags 1\n >>\n>>\n",
|
||||||
ApprovalSignature: "<<\n /Type /Catalog /Pages 3 0 R /AcroForm << /Fields [10 0 R] /NeedAppearances false /SigFlags 3 >>\n>>\n",
|
ApprovalSignature: "<<\n /Type /Catalog\n /Pages 3 0 R\n /AcroForm <<\n /Fields [10 0 R]\n /SigFlags 3\n >>\n>>\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
file: "../testfiles/testfile21.pdf",
|
file: "../testfiles/testfile21.pdf",
|
||||||
expectedCatalogs: map[CertType]string{
|
expectedCatalogs: map[CertType]string{
|
||||||
CertificationSignature: "<<\n /Type /Catalog /Pages 9 0 R /Names 6 0 R /AcroForm << /Fields [16 0 R] /NeedAppearances false /SigFlags 3 >>\n>>\n",
|
CertificationSignature: "<<\n /Type /Catalog\n /Pages 9 0 R\n /Names 6 0 R\n /AcroForm <<\n /Fields [16 0 R]\n /SigFlags 3\n >>\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",
|
UsageRightsSignature: "<<\n /Type /Catalog\n /Pages 9 0 R\n /Names 6 0 R\n /AcroForm <<\n /Fields [16 0 R]\n /SigFlags 1\n >>\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",
|
ApprovalSignature: "<<\n /Type /Catalog\n /Pages 9 0 R\n /Names 6 0 R\n /AcroForm <<\n /Fields [16 0 R]\n /SigFlags 3\n >>\n>>\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ var testFiles = []struct {
|
|||||||
func TestCreateCatalog(t *testing.T) {
|
func TestCreateCatalog(t *testing.T) {
|
||||||
for _, testFile := range testFiles {
|
for _, testFile := range testFiles {
|
||||||
for certType, expectedCatalog := range testFile.expectedCatalogs {
|
for certType, expectedCatalog := range testFile.expectedCatalogs {
|
||||||
t.Run(fmt.Sprintf("%s_certType-%d", testFile.file, certType), func(st *testing.T) {
|
t.Run(fmt.Sprintf("%s_%s", testFile.file, certType.String()), 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")
|
||||||
@@ -57,13 +57,7 @@ func TestCreateCatalog(t *testing.T) {
|
|||||||
PDFReader: rdr,
|
PDFReader: rdr,
|
||||||
InputFile: inputFile,
|
InputFile: inputFile,
|
||||||
VisualSignData: VisualSignData{
|
VisualSignData: VisualSignData{
|
||||||
ObjectId: uint32(rdr.XrefInformation.ItemCount),
|
objectId: uint32(rdr.XrefInformation.ItemCount),
|
||||||
},
|
|
||||||
CatalogData: CatalogData{
|
|
||||||
ObjectId: uint32(rdr.XrefInformation.ItemCount) + 1,
|
|
||||||
},
|
|
||||||
InfoData: InfoData{
|
|
||||||
ObjectId: uint32(rdr.XrefInformation.ItemCount) + 2,
|
|
||||||
},
|
},
|
||||||
SignData: SignData{
|
SignData: SignData{
|
||||||
Signature: SignDataSignature{
|
Signature: SignDataSignature{
|
||||||
@@ -80,7 +74,7 @@ func TestCreateCatalog(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if string(catalog) != expectedCatalog {
|
if string(catalog) != expectedCatalog {
|
||||||
st.Errorf("Catalog mismatch, expected\n%s\nbut got\n%s", expectedCatalog, catalog)
|
st.Errorf("Catalog mismatch, expected\n%q\nbut got\n%q", expectedCatalog, catalog)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -176,7 +176,7 @@ func (context *SignContext) createSignaturePlaceholder() []byte {
|
|||||||
//
|
//
|
||||||
// A timestamp can be embedded in a CMS binary data object (see 12.8.3.3, "CMS
|
// A timestamp can be embedded in a CMS binary data object (see 12.8.3.3, "CMS
|
||||||
// (PKCS #7) signatures").
|
// (PKCS #7) signatures").
|
||||||
if context.SignData.TSA.URL == "" {
|
if context.SignData.TSA.URL == "" && !context.SignData.Signature.Info.Date.IsZero() {
|
||||||
signature_buffer.WriteString(" /M ")
|
signature_buffer.WriteString(" /M ")
|
||||||
signature_buffer.WriteString(pdfDateTime(context.SignData.Signature.Info.Date))
|
signature_buffer.WriteString(pdfDateTime(context.SignData.Signature.Info.Date))
|
||||||
signature_buffer.WriteString("\n")
|
signature_buffer.WriteString("\n")
|
||||||
@@ -499,7 +499,7 @@ func (context *SignContext) fetchExistingSignatures() ([]SignData, error) {
|
|||||||
if field.Key("FT").Name() == "Sig" {
|
if field.Key("FT").Name() == "Sig" {
|
||||||
ptr := field.GetPtr()
|
ptr := field.GetPtr()
|
||||||
sig := SignData{
|
sig := SignData{
|
||||||
ObjectId: uint32(ptr.GetID()),
|
objectId: uint32(ptr.GetID()),
|
||||||
}
|
}
|
||||||
signatures = append(signatures, sig)
|
signatures = append(signatures, sig)
|
||||||
}
|
}
|
||||||
|
@@ -63,21 +63,12 @@ func TestCreateSignaturePlaceholder(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 3
|
sign_data.objectId = uint32(rdr.XrefInformation.ItemCount) + 3
|
||||||
|
|
||||||
context := SignContext{
|
context := SignContext{
|
||||||
PDFReader: rdr,
|
PDFReader: rdr,
|
||||||
InputFile: inputFile,
|
InputFile: inputFile,
|
||||||
VisualSignData: VisualSignData{
|
SignData: sign_data,
|
||||||
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 := context.createSignaturePlaceholder()
|
signature := context.createSignaturePlaceholder()
|
||||||
|
@@ -27,20 +27,37 @@ 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) ([]byte, error) {
|
func (context *SignContext) createVisualSignature(visible bool, pageNumber uint32, rect [4]float64) ([]byte, error) {
|
||||||
var visual_signature bytes.Buffer
|
var visual_signature bytes.Buffer
|
||||||
|
|
||||||
|
visual_signature.WriteString("<<\n")
|
||||||
|
|
||||||
// Define the object as an annotation.
|
// Define the object as an annotation.
|
||||||
visual_signature.WriteString("<< /Type /Annot")
|
visual_signature.WriteString(" /Type /Annot\n")
|
||||||
// Specify the annotation subtype as a widget.
|
// Specify the annotation subtype as a widget.
|
||||||
visual_signature.WriteString(" /Subtype /Widget")
|
visual_signature.WriteString(" /Subtype /Widget\n")
|
||||||
|
|
||||||
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.WriteString(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]\n", rect[0], rect[1], rect[2], rect[3]))
|
||||||
|
|
||||||
|
appearance, err := context.createAppearance(rect)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create appearance: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
appearanceObjectId, err := context.addObject(appearance)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to add appearance object: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An appearance dictionary specifying how the annotation
|
||||||
|
// shall be presented visually on the page (see 12.5.5, "Appearance streams").
|
||||||
|
visual_signature.WriteString(fmt.Sprintf(" /AP << /N %d 0 R >>\n", appearanceObjectId))
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Set the rectangle to zero if the signature is invisible.
|
// Set the rectangle to zero if the signature is invisible.
|
||||||
visual_signature.WriteString(" /Rect [0 0 0 0]")
|
visual_signature.WriteString(" /Rect [0 0 0 0]\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the root object from the PDF trailer.
|
// Retrieve the root object from the PDF trailer.
|
||||||
@@ -72,40 +89,72 @@ func (context *SignContext) createVisualSignature(visible bool, pageNumber int,
|
|||||||
page_ptr := page.GetPtr()
|
page_ptr := page.GetPtr()
|
||||||
|
|
||||||
// Store the page ID in the visual signature context so that we can add it to xref table later.
|
// Store the page ID in the visual signature context so that we can add it to xref table later.
|
||||||
context.VisualSignData.PageId = page_ptr.GetID()
|
context.VisualSignData.pageObjectId = page_ptr.GetID()
|
||||||
|
|
||||||
// Add the page reference to the visual signature.
|
// Add the page reference to the visual signature.
|
||||||
visual_signature.WriteString(" /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\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 | AnnotationFlagLocked
|
||||||
visual_signature.WriteString(fmt.Sprintf(" /F %d", 132))
|
visual_signature.WriteString(fmt.Sprintf(" /F %d\n", annotationFlags))
|
||||||
// Define the field type as a signature.
|
|
||||||
visual_signature.WriteString(" /FT /Sig")
|
|
||||||
// Set a unique title for the signature field.
|
|
||||||
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
|
// Define the field type as a signature.
|
||||||
// in this dictionary. A value of 1 for the flag indicates that the associated entry
|
visual_signature.WriteString(" /FT /Sig\n")
|
||||||
// is a required constraint. A value of 0 indicates that the associated entry is
|
// Set a unique title for the signature field.
|
||||||
// an optional constraint. Bit positions are 1 (Filter); 2 (SubFilter); 3 (V); 4
|
visual_signature.WriteString(fmt.Sprintf(" /T %s\n", pdfString("Signature "+strconv.Itoa(len(context.existingSignatures)+1))))
|
||||||
// (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.WriteString(" /Ff 0")
|
|
||||||
|
|
||||||
// Reference the signature dictionary.
|
// Reference the signature dictionary.
|
||||||
visual_signature.WriteString(" /V " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R")
|
visual_signature.WriteString(fmt.Sprintf(" /V %d 0 R\n", context.SignData.objectId))
|
||||||
|
|
||||||
// Close the dictionary and end the object.
|
// Close the dictionary and end the object.
|
||||||
visual_signature.WriteString(" >>\n")
|
visual_signature.WriteString(">>\n")
|
||||||
|
|
||||||
return visual_signature.Bytes(), nil
|
return visual_signature.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (context *SignContext) createIncPageUpdate(pageNumber, annot uint32) ([]byte, error) {
|
||||||
|
var page_buffer bytes.Buffer
|
||||||
|
|
||||||
|
// Retrieve the root object from the PDF trailer.
|
||||||
|
root := context.PDFReader.Trailer().Key("Root")
|
||||||
|
page, err := findPageByNumber(root.Key("Pages"), pageNumber)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
page_buffer.WriteString("<<\n")
|
||||||
|
|
||||||
|
// TODO: Update digitorus/pdf to get raw values without resolving pointers
|
||||||
|
for _, key := range page.Keys() {
|
||||||
|
switch key {
|
||||||
|
case "Contents", "Parent":
|
||||||
|
ptr := page.Key(key).GetPtr()
|
||||||
|
page_buffer.WriteString(fmt.Sprintf(" /%s %d 0 R\n", key, ptr.GetID()))
|
||||||
|
case "Annots":
|
||||||
|
page_buffer.WriteString(" /Annots [\n")
|
||||||
|
for i := 0; i < page.Key("Annots").Len(); i++ {
|
||||||
|
ptr := page.Key(key).Index(i).GetPtr()
|
||||||
|
page_buffer.WriteString(fmt.Sprintf(" %d 0 R\n", ptr.GetID()))
|
||||||
|
}
|
||||||
|
page_buffer.WriteString(fmt.Sprintf(" %d 0 R\n", annot))
|
||||||
|
page_buffer.WriteString(" ]\n")
|
||||||
|
default:
|
||||||
|
page_buffer.WriteString(fmt.Sprintf(" /%s %s\n", key, page.Key(key).String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if page.Key("Annots").IsNull() {
|
||||||
|
page_buffer.WriteString(fmt.Sprintf(" /Annots [%d 0 R]\n", annot))
|
||||||
|
}
|
||||||
|
|
||||||
|
page_buffer.WriteString(">>\n")
|
||||||
|
|
||||||
|
return page_buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to find a page by its number.
|
// Helper function to find a page by its number.
|
||||||
func findPageByNumber(pages pdf.Value, pageNumber int) (pdf.Value, error) {
|
func findPageByNumber(pages pdf.Value, pageNumber uint32) (pdf.Value, error) {
|
||||||
if pages.Key("Type").Name() == "Pages" {
|
if pages.Key("Type").Name() == "Pages" {
|
||||||
kids := pages.Key("Kids")
|
kids := pages.Key("Kids")
|
||||||
for i := 0; i < kids.Len(); i++ {
|
for i := 0; i < kids.Len(); i++ {
|
||||||
|
@@ -45,24 +45,15 @@ 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{
|
||||||
PDFReader: rdr,
|
PDFReader: rdr,
|
||||||
InputFile: input_file,
|
InputFile: input_file,
|
||||||
VisualSignData: VisualSignData{
|
SignData: sign_data,
|
||||||
ObjectId: uint32(rdr.XrefInformation.ItemCount),
|
|
||||||
},
|
|
||||||
CatalogData: CatalogData{
|
|
||||||
ObjectId: uint32(rdr.XrefInformation.ItemCount) + 1,
|
|
||||||
},
|
|
||||||
InfoData: InfoData{
|
|
||||||
ObjectId: uint32(rdr.XrefInformation.ItemCount) + 2,
|
|
||||||
},
|
|
||||||
SignData: sign_data,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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"
|
expected_visual_signature := "<<\n /Type /Annot\n /Subtype /Widget\n /Rect [0 0 0 0]\n /P 4 0 R\n /F 132\n /FT /Sig\n /T (Signature 1)\n /V 13 0 R\n>>\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 {
|
||||||
|
@@ -40,23 +40,46 @@ func (context *SignContext) addObject(object []byte) (uint32, error) {
|
|||||||
Offset: int64(context.OutputBuffer.Buff.Len()) + 1,
|
Offset: int64(context.OutputBuffer.Buff.Len()) + 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
err := context.writeObject(objectID, object)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to write object: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return objectID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *SignContext) updateObject(id uint32, object []byte) error {
|
||||||
|
context.updatedXrefEntries = append(context.updatedXrefEntries, xrefEntry{
|
||||||
|
ID: id,
|
||||||
|
Offset: int64(context.OutputBuffer.Buff.Len()) + 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
err := context.writeObject(id, object)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write object: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *SignContext) writeObject(id uint32, object []byte) error {
|
||||||
// Write the object header
|
// Write the object header
|
||||||
if _, err := context.OutputBuffer.Write([]byte(fmt.Sprintf("\n%d 0 obj\n", objectID))); err != nil {
|
if _, err := context.OutputBuffer.Write([]byte(fmt.Sprintf("\n%d 0 obj\n", id))); err != nil {
|
||||||
return 0, fmt.Errorf("failed to write object header: %w", err)
|
return fmt.Errorf("failed to write object header: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the object content
|
// Write the object content
|
||||||
object = bytes.TrimSpace(object)
|
object = bytes.TrimSpace(object)
|
||||||
if _, err := context.OutputBuffer.Write(object); err != nil {
|
if _, err := context.OutputBuffer.Write(object); err != nil {
|
||||||
return 0, fmt.Errorf("failed to write object content: %w", err)
|
return fmt.Errorf("failed to write object content: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the object footer
|
// Write the object footer
|
||||||
if _, err := context.OutputBuffer.Write([]byte(objectFooter)); err != nil {
|
if _, err := context.OutputBuffer.Write([]byte(objectFooter)); err != nil {
|
||||||
return 0, fmt.Errorf("failed to write object footer: %w", err)
|
return fmt.Errorf("failed to write object footer: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return objectID, nil
|
return 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.
|
||||||
@@ -115,6 +138,19 @@ func (context *SignContext) writeIncrXrefTable() error {
|
|||||||
return fmt.Errorf("failed to write incremental xref header: %w", err)
|
return fmt.Errorf("failed to write incremental xref header: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write updated entries
|
||||||
|
for _, entry := range context.updatedXrefEntries {
|
||||||
|
pageXrefObj := fmt.Sprintf("%d %d\n", entry.ID, 1)
|
||||||
|
if _, err := context.OutputBuffer.Write([]byte(pageXrefObj)); err != nil {
|
||||||
|
return fmt.Errorf("failed to write updated xref object: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 updated incremental xref entry: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Write xref subsection header
|
// Write xref subsection header
|
||||||
startXrefObj := fmt.Sprintf("%d %d\n", context.lastXrefID+1, len(context.newXrefEntries))
|
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 {
|
||||||
@@ -190,26 +226,25 @@ func encodeXrefStream(data []byte, predictor int64) ([]byte, error) {
|
|||||||
|
|
||||||
// writeXrefStreamHeader writes the header for the xref stream.
|
// writeXrefStreamHeader writes the header for the xref stream.
|
||||||
func writeXrefStreamHeader(context *SignContext, streamLength int) error {
|
func writeXrefStreamHeader(context *SignContext, streamLength int) error {
|
||||||
newRoot := fmt.Sprintf("Root %d 0 R", context.CatalogData.ObjectId)
|
|
||||||
|
|
||||||
id := context.PDFReader.Trailer().Key("ID")
|
id := context.PDFReader.Trailer().Key("ID")
|
||||||
id0 := hex.EncodeToString([]byte(id.Index(0).RawString()))
|
id0 := hex.EncodeToString([]byte(id.Index(0).RawString()))
|
||||||
id1 := hex.EncodeToString([]byte(id.Index(0).RawString()))
|
id1 := hex.EncodeToString([]byte(id.Index(0).RawString()))
|
||||||
|
|
||||||
newXref := fmt.Sprintf("%d 0 obj\n<< /Type /XRef /Length %d /Filter /FlateDecode /DecodeParms << /Columns %d /Predictor %d >> /W [ 1 3 1 ] /Prev %d /Size %d /Index [ %d 4 ] /%s /ID [<%s><%s>] >>\n",
|
var buffer bytes.Buffer
|
||||||
context.SignData.ObjectId+1,
|
buffer.WriteString(fmt.Sprintf("%d 0 obj\n", context.SignData.objectId))
|
||||||
streamLength,
|
buffer.WriteString("<< /Type /XRef\n")
|
||||||
xrefStreamColumns,
|
buffer.WriteString(fmt.Sprintf(" /Length %d\n", streamLength))
|
||||||
xrefStreamPredictor,
|
buffer.WriteString(" /Filter /FlateDecode\n")
|
||||||
context.PDFReader.XrefInformation.StartPos,
|
buffer.WriteString(fmt.Sprintf(" /DecodeParms << /Columns %d /Predictor %d >>\n", xrefStreamColumns, xrefStreamPredictor))
|
||||||
context.PDFReader.XrefInformation.ItemCount+int64(len(context.newXrefEntries))+1,
|
buffer.WriteString(" /W [ 1 3 1 ]\n")
|
||||||
context.PDFReader.XrefInformation.ItemCount,
|
buffer.WriteString(fmt.Sprintf(" /Prev %d\n", context.PDFReader.XrefInformation.StartPos))
|
||||||
newRoot,
|
buffer.WriteString(fmt.Sprintf(" /Size %d\n", context.PDFReader.XrefInformation.ItemCount+int64(len(context.newXrefEntries))+1))
|
||||||
id0,
|
buffer.WriteString(fmt.Sprintf(" /Index [ %d 4 ]\n", context.PDFReader.XrefInformation.ItemCount))
|
||||||
id1,
|
buffer.WriteString(fmt.Sprintf(" /Root %d 0 R\n", context.CatalogData.ObjectId))
|
||||||
)
|
buffer.WriteString(fmt.Sprintf(" /ID [<%s><%s>]\n", id0, id1))
|
||||||
|
buffer.WriteString(">>\n")
|
||||||
|
|
||||||
_, err := io.WriteString(context.OutputBuffer, newXref)
|
_, err := context.OutputBuffer.Write(buffer.Bytes())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
79
sign/sign.go
79
sign/sign.go
@@ -29,7 +29,6 @@ type TSA struct {
|
|||||||
type RevocationFunction func(cert, issuer *x509.Certificate, i *revocation.InfoArchival) error
|
type RevocationFunction func(cert, issuer *x509.Certificate, i *revocation.InfoArchival) error
|
||||||
|
|
||||||
type SignData struct {
|
type SignData struct {
|
||||||
ObjectId uint32
|
|
||||||
Signature SignDataSignature
|
Signature SignDataSignature
|
||||||
Signer crypto.Signer
|
Signer crypto.Signer
|
||||||
DigestAlgorithm crypto.Hash
|
DigestAlgorithm crypto.Hash
|
||||||
@@ -38,12 +37,24 @@ type SignData struct {
|
|||||||
TSA TSA
|
TSA TSA
|
||||||
RevocationData revocation.InfoArchival
|
RevocationData revocation.InfoArchival
|
||||||
RevocationFunction RevocationFunction
|
RevocationFunction RevocationFunction
|
||||||
ExistingSignatures []SignData
|
Appearance Appearance
|
||||||
|
|
||||||
|
objectId uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appearance represents the appearance of the signature
|
||||||
|
type Appearance struct {
|
||||||
|
Visible bool
|
||||||
|
Page uint32
|
||||||
|
LowerLeftX float64
|
||||||
|
LowerLeftY float64
|
||||||
|
UpperRightX float64
|
||||||
|
UpperRightY float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type VisualSignData struct {
|
type VisualSignData struct {
|
||||||
PageId uint32
|
pageObjectId uint32
|
||||||
ObjectId uint32
|
objectId uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
type InfoData struct {
|
type InfoData struct {
|
||||||
@@ -97,8 +108,10 @@ type SignContext struct {
|
|||||||
SignatureMaxLength uint32
|
SignatureMaxLength uint32
|
||||||
SignatureMaxLengthBase uint32
|
SignatureMaxLengthBase uint32
|
||||||
|
|
||||||
lastXrefID uint32
|
existingSignatures []SignData
|
||||||
newXrefEntries []xrefEntry
|
lastXrefID uint32
|
||||||
|
newXrefEntries []xrefEntry
|
||||||
|
updatedXrefEntries []xrefEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
func SignFile(input string, output string, sign_data SignData) error {
|
func SignFile(input string, output string, sign_data SignData) error {
|
||||||
@@ -129,21 +142,12 @@ 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
|
||||||
|
|
||||||
context := SignContext{
|
context := SignContext{
|
||||||
PDFReader: rdr,
|
PDFReader: rdr,
|
||||||
InputFile: input,
|
InputFile: input,
|
||||||
OutputFile: output,
|
OutputFile: output,
|
||||||
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,
|
SignData: sign_data,
|
||||||
SignatureMaxLengthBase: uint32(hex.EncodedLen(512)),
|
SignatureMaxLengthBase: uint32(hex.EncodedLen(512)),
|
||||||
}
|
}
|
||||||
@@ -153,7 +157,7 @@ func Sign(input io.ReadSeeker, output io.Writer, rdr *pdf.Reader, size int64, si
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
context.SignData.ExistingSignatures = existingSignatures
|
context.existingSignatures = existingSignatures
|
||||||
|
|
||||||
err = context.SignPDF()
|
err = context.SignPDF()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -174,6 +178,9 @@ func (context *SignContext) SignPDF() error {
|
|||||||
if !context.SignData.DigestAlgorithm.Available() {
|
if !context.SignData.DigestAlgorithm.Available() {
|
||||||
context.SignData.DigestAlgorithm = crypto.SHA256
|
context.SignData.DigestAlgorithm = crypto.SHA256
|
||||||
}
|
}
|
||||||
|
if context.SignData.Appearance.Page == 0 {
|
||||||
|
context.SignData.Appearance.Page = 1
|
||||||
|
}
|
||||||
|
|
||||||
context.OutputBuffer = filebuffer.New([]byte{})
|
context.OutputBuffer = filebuffer.New([]byte{})
|
||||||
|
|
||||||
@@ -271,25 +278,49 @@ func (context *SignContext) SignPDF() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write the new signature object
|
// Write the new signature object
|
||||||
context.SignData.ObjectId, err = context.addObject(signature_object)
|
context.SignData.objectId, err = context.addObject(signature_object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to add signature object: %w", err)
|
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 := false
|
||||||
|
rectangle := [4]float64{0, 0, 0, 0}
|
||||||
|
if context.SignData.Signature.CertType != ApprovalSignature && context.SignData.Appearance.Visible {
|
||||||
|
return fmt.Errorf("visible signatures are only allowed for approval signatures")
|
||||||
|
} else if context.SignData.Signature.CertType == ApprovalSignature && context.SignData.Appearance.Visible {
|
||||||
|
visible = true
|
||||||
|
rectangle = [4]float64{
|
||||||
|
context.SignData.Appearance.LowerLeftX,
|
||||||
|
context.SignData.Appearance.LowerLeftY,
|
||||||
|
context.SignData.Appearance.UpperRightX,
|
||||||
|
context.SignData.Appearance.UpperRightY,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Example usage: passing page number and default rect values
|
// Example usage: passing page number and default rect values
|
||||||
visual_signature, err := context.createVisualSignature(false, 1, [4]float64{0, 0, 0, 0})
|
visual_signature, err := context.createVisualSignature(visible, context.SignData.Appearance.Page, rectangle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create visual signature: %w", err)
|
return fmt.Errorf("failed to create visual signature: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the new visual signature object.
|
// Write the new visual signature object.
|
||||||
context.VisualSignData.ObjectId, err = context.addObject(visual_signature)
|
context.VisualSignData.objectId, err = context.addObject(visual_signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to add visual signature object: %w", err)
|
return fmt.Errorf("failed to add visual signature object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if context.SignData.Appearance.Visible {
|
||||||
|
inc_page_update, err := context.createIncPageUpdate(context.SignData.Appearance.Page, context.VisualSignData.objectId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create incremental page update: %w", err)
|
||||||
|
}
|
||||||
|
err = context.updateObject(context.VisualSignData.pageObjectId, inc_page_update)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to add incremental page update object: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new catalog object
|
// Create a new catalog object
|
||||||
catalog, err := context.createCatalog()
|
catalog, err := context.createCatalog()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -229,6 +229,54 @@ func TestSignPDFFileUTF8(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSignPDFVisible(t *testing.T) {
|
||||||
|
cert, pkey := loadCertificateAndKey(t)
|
||||||
|
inputFilePath := "../testfiles/minimal.pdf"
|
||||||
|
originalFileName := filepath.Base(inputFilePath)
|
||||||
|
|
||||||
|
tmpfile, err := os.CreateTemp("", t.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s", err.Error())
|
||||||
|
}
|
||||||
|
if !testing.Verbose() {
|
||||||
|
defer os.Remove(tmpfile.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = SignFile(inputFilePath, tmpfile.Name(), SignData{
|
||||||
|
Signature: SignDataSignature{
|
||||||
|
Info: SignDataSignatureInfo{
|
||||||
|
Name: "John Doe",
|
||||||
|
Location: "Somewhere",
|
||||||
|
Reason: "Test with visible signature",
|
||||||
|
ContactInfo: "None",
|
||||||
|
},
|
||||||
|
CertType: ApprovalSignature,
|
||||||
|
DocMDPPerm: AllowFillingExistingFormFieldsAndSignaturesPerms,
|
||||||
|
},
|
||||||
|
Appearance: Appearance{
|
||||||
|
Visible: true,
|
||||||
|
LowerLeftX: 350,
|
||||||
|
LowerLeftY: 75,
|
||||||
|
UpperRightX: 600,
|
||||||
|
UpperRightY: 100,
|
||||||
|
},
|
||||||
|
DigestAlgorithm: crypto.SHA512,
|
||||||
|
Signer: pkey,
|
||||||
|
Certificate: cert,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: %s", originalFileName, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = verify.File(tmpfile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: %s", tmpfile.Name(), err.Error())
|
||||||
|
if err := os.Rename(tmpfile.Name(), "../testfiles/failed/"+originalFileName); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkSignPDF(b *testing.B) {
|
func BenchmarkSignPDF(b *testing.B) {
|
||||||
cert, pkey := loadCertificateAndKey(&testing.T{})
|
cert, pkey := loadCertificateAndKey(&testing.T{})
|
||||||
certificateChains := [][]*x509.Certificate{}
|
certificateChains := [][]*x509.Certificate{}
|
||||||
|
Reference in New Issue
Block a user