From 08040f9140b2345720fdab00379ca6b630c27044 Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Sat, 23 Sep 2017 15:30:32 +0200 Subject: [PATCH] Tests and benchmark --- .gitignore | 2 +- sign/helpers_test.go | 4 +- sign/pdfcatalog_test.go | 56 ++++++++ sign/pdfinfo_test.go | 138 ++++++++++++++++++++ sign/pdfsignature_test.go | 81 ++++++++++++ sign/pdfvisualsignature_test.go | 77 +++++++++++ sign/sign_test.go | 6 +- testfiles/testfile12.pdf | Bin 0 -> 16384 bytes testfiles/{benchmark.pdf => testfile20.pdf} | 0 9 files changed, 358 insertions(+), 6 deletions(-) create mode 100644 sign/pdfcatalog_test.go create mode 100644 sign/pdfinfo_test.go create mode 100644 sign/pdfsignature_test.go create mode 100644 sign/pdfvisualsignature_test.go create mode 100644 testfiles/testfile12.pdf rename testfiles/{benchmark.pdf => testfile20.pdf} (100%) diff --git a/.gitignore b/.gitignore index 44eba8a..6061d94 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .idea *.pdf *.pdf.* -!benchmark.pdf \ No newline at end of file +!testfile*.pdf \ No newline at end of file diff --git a/sign/helpers_test.go b/sign/helpers_test.go index 1ede392..a8fc4b9 100644 --- a/sign/helpers_test.go +++ b/sign/helpers_test.go @@ -118,7 +118,7 @@ func TestWritePartFromSourceFileToTargetFile(t *testing.T) { var b bytes.Buffer writer := bufio.NewWriter(&b) - input_file, err := os.Open("../testfiles/benchmark.pdf") + input_file, err := os.Open("../testfiles/testfile20.pdf") if err != nil { t.Errorf("Failed to load test PDF") return @@ -162,7 +162,7 @@ func TestWritePartFromSourceFileToTargetFile(t *testing.T) { } func loadHelpersTestPDF() (*os.File, *pdf.Reader) { - input_file, err := os.Open("../testfiles/benchmark.pdf") + input_file, err := os.Open("../testfiles/testfile20.pdf") if err != nil { return nil, nil } diff --git a/sign/pdfcatalog_test.go b/sign/pdfcatalog_test.go new file mode 100644 index 0000000..2886ffe --- /dev/null +++ b/sign/pdfcatalog_test.go @@ -0,0 +1,56 @@ +package sign + +import ( + "os" + "testing" + + "bitbucket.org/digitorus/pdf" +) + +func TestCreateCatalog(t *testing.T) { + input_file, err := os.Open("../testfiles/testfile20.pdf") + if err != nil { + t.Errorf("Failed to load test PDF") + return + } + + finfo, err := input_file.Stat() + if err != nil { + t.Errorf("Failed to load test PDF") + return + } + size := finfo.Size() + + rdr, err := pdf.NewReader(input_file, size) + if err != nil { + t.Errorf("Failed to load test PDF") + return + } + + context := SignContext{ + Filesize: size + 1, + PDFReader: rdr, + InputFile: input_file, + VisualSignData: VisualSignData{ + ObjectId: uint32(rdr.XrefInformation.ItemCount), + }, + CatalogData: CatalogData{ + ObjectId: uint32(rdr.XrefInformation.ItemCount) + 1, + }, + InfoData: InfoData{ + ObjectId: uint32(rdr.XrefInformation.ItemCount) + 2, + }, + } + + catalog, err := context.createCatalog() + if err != nil { + t.Errorf("%s", err.Error()) + return + } + + expected_catalog := "11 0 obj\n<< /Type /Catalog /Version /2.0 /Pages 3 0 R /AcroForm << /Fields [10 0 R] /NeedAppearances false /SigFlags 1 >> /Perms << /UR3 0 0 R >> >>\nendobj\n" + + if catalog != expected_catalog { + t.Errorf("Catalog mismatch, expected %s, but got %s", expected_catalog, catalog) + } +} diff --git a/sign/pdfinfo_test.go b/sign/pdfinfo_test.go new file mode 100644 index 0000000..f7b71c0 --- /dev/null +++ b/sign/pdfinfo_test.go @@ -0,0 +1,138 @@ +package sign + +import ( + "os" + "testing" + + "bitbucket.org/digitorus/pdf" + "time" +) + +func TestCreateInfoEmpty(t *testing.T) { + input_file, err := os.Open("../testfiles/testfile20.pdf") + if err != nil { + t.Errorf("Failed to load test PDF") + return + } + + finfo, err := input_file.Stat() + if err != nil { + t.Errorf("Failed to load test PDF") + return + } + size := finfo.Size() + + rdr, err := pdf.NewReader(input_file, size) + if err != nil { + t.Errorf("Failed to load test PDF") + return + } + + sign_data := SignData{ + Signature: SignDataSignature{ + Info: SignDataSignatureInfo{ + Name: "Jeroen Bobbeldijk", + Location: "Rotterdam", + Reason: "Test", + ContactInfo: "Geen", + Date: time.Now().Local(), + }, + CertType: 2, + Approval: false, + }, + } + + sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 3 + + context := SignContext{ + Filesize: size + 1, + PDFReader: rdr, + InputFile: input_file, + 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, + } + + info, err := context.createInfo() + if err != nil { + t.Errorf("%s", err.Error()) + return + } + + expected_info := "12 0 obj\n<<>>\nendobj\n" + if info != expected_info { + t.Errorf("Info mismatch, expected %s, but got %s", expected_info, info) + } +} + +func TestCreateInfo(t *testing.T) { + input_file, err := os.Open("../testfiles/testfile12.pdf") + if err != nil { + t.Errorf("Failed to load test PDF") + return + } + + finfo, err := input_file.Stat() + if err != nil { + t.Errorf("Failed to load test PDF") + return + } + size := finfo.Size() + + rdr, err := pdf.NewReader(input_file, size) + if err != nil { + t.Errorf("Failed to load test PDF") + return + } + + sign_data := SignData{ + Signature: SignDataSignature{ + Info: SignDataSignatureInfo{ + Name: "Jeroen Bobbeldijk", + Location: "Rotterdam", + Reason: "Test", + ContactInfo: "Geen", + Date: time.Now().Local(), + }, + CertType: 2, + Approval: false, + }, + } + + sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 3 + + context := SignContext{ + Filesize: size + 1, + PDFReader: rdr, + InputFile: input_file, + 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, + } + + info, err := context.createInfo() + if err != nil { + t.Errorf("%s", err.Error()) + return + } + + expected_info := "18 0 obj\n<>\nendobj\n" + + if info != expected_info { + t.Errorf("Info mismatch, expected %s, but got %s", expected_info, info) + } +} diff --git a/sign/pdfsignature_test.go b/sign/pdfsignature_test.go new file mode 100644 index 0000000..ba639e2 --- /dev/null +++ b/sign/pdfsignature_test.go @@ -0,0 +1,81 @@ +package sign + +import ( + "os" + "testing" + "time" + + "bitbucket.org/digitorus/pdf" +) + +func TestCreateSignature(t *testing.T) { + input_file, err := os.Open("../testfiles/testfile20.pdf") + if err != nil { + t.Errorf("Failed to load test PDF") + return + } + + finfo, err := input_file.Stat() + if err != nil { + t.Errorf("Failed to load test PDF") + return + } + size := finfo.Size() + + rdr, err := pdf.NewReader(input_file, size) + if err != nil { + t.Errorf("Failed to load test PDF") + return + } + + timezone, _ := time.LoadLocation("Europe/Tallinn") + now := time.Date(2017, 9, 23, 14, 39, 0, 0, timezone) + + sign_data := SignData{ + Signature: SignDataSignature{ + Info: SignDataSignatureInfo{ + Name: "Jeroen Bobbeldijk", + Location: "Rotterdam", + Reason: "Test", + ContactInfo: "Geen", + Date: now, + }, + CertType: 2, + Approval: false, + }, + } + + sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 3 + + context := SignContext{ + Filesize: size + 1, + PDFReader: rdr, + InputFile: input_file, + 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, + } + + expected_signature := "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 (Jeroen Bobbeldijk) /Location (Rotterdam) /Reason (Test) /ContactInfo (Geen) /M (D:20170923143900+03'00') >>\nendobj\n" + + signature, byte_range_start_byte, signature_contents_start_byte := context.createSignaturePlaceholder() + + if signature != expected_signature { + t.Errorf("Signature mismatch, expected %s, but got %s", expected_signature, signature) + } + + if byte_range_start_byte != 78 { + t.Errorf("Byte range start mismatch, expected %d, but got %d", 78, byte_range_start_byte) + } + + if signature_contents_start_byte != 135 { + t.Errorf("Signature contents start byte mismatch, expected %d, but got %d", 135, signature_contents_start_byte) + } +} diff --git a/sign/pdfvisualsignature_test.go b/sign/pdfvisualsignature_test.go new file mode 100644 index 0000000..bca6507 --- /dev/null +++ b/sign/pdfvisualsignature_test.go @@ -0,0 +1,77 @@ +package sign + +import ( + "os" + "testing" + "time" + + "bitbucket.org/digitorus/pdf" +) + +func TestVisualSignature(t *testing.T) { + input_file, err := os.Open("../testfiles/testfile20.pdf") + if err != nil { + t.Errorf("Failed to load test PDF") + return + } + + finfo, err := input_file.Stat() + if err != nil { + t.Errorf("Failed to load test PDF") + return + } + size := finfo.Size() + + rdr, err := pdf.NewReader(input_file, size) + if err != nil { + t.Errorf("Failed to load test PDF") + return + } + + timezone, _ := time.LoadLocation("Europe/Tallinn") + now := time.Date(2017, 9, 23, 14, 39, 0, 0, timezone) + + sign_data := SignData{ + Signature: SignDataSignature{ + Info: SignDataSignatureInfo{ + Name: "Jeroen Bobbeldijk", + Location: "Rotterdam", + Reason: "Test", + ContactInfo: "Geen", + Date: now, + }, + CertType: 2, + Approval: false, + }, + } + + sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 3 + + context := SignContext{ + Filesize: size + 1, + PDFReader: rdr, + InputFile: input_file, + 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, + } + + 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) /Ff 0 /V 13 0 R >>\nendobj\n" + + visual_signature, err := context.createVisualSignature() + if err != nil { + t.Errorf("%s", err.Error()) + return + } + + if visual_signature != expected_visual_signature { + t.Errorf("Visual signature mismatch, expected %s, but got %s", expected_visual_signature, visual_signature) + } +} diff --git a/sign/sign_test.go b/sign/sign_test.go index 12c8265..a6c0f2f 100644 --- a/sign/sign_test.go +++ b/sign/sign_test.go @@ -191,7 +191,7 @@ func BenchmarkSignPDF(b *testing.B) { certificate_chains := make([][]*x509.Certificate, 0) for n := 0; n < b.N; n++ { - err := SignFile("../testfiles/benchmark.pdf", "../testfiles/benchmark.pdf.tmp", SignData{ + err := SignFile("../testfiles/testfile20.pdf", "../testfiles/testfile20.pdf.tmp", SignData{ Signature: SignDataSignature{ Info: SignDataSignatureInfo{ Name: "Jeroen Bobbeldijk", @@ -213,10 +213,10 @@ func BenchmarkSignPDF(b *testing.B) { RevocationFunction: DefaultEmbedRevocationStatusFunction, }) - os.Remove("../testfiles/benchmark.pdf.tmp") + os.Remove("../testfiles/testfile20.pdf.tmp") if err != nil { - b.Errorf("%s: %s", "benchmark.pdf", err.Error()) + b.Errorf("%s: %s", "testfile20.pdf", err.Error()) return } } diff --git a/testfiles/testfile12.pdf b/testfiles/testfile12.pdf new file mode 100644 index 0000000000000000000000000000000000000000..77a7d3c5217b0a60a8447b9a5309ecf49485300d GIT binary patch literal 16384 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 literal 0 HcmV?d00001 diff --git a/testfiles/benchmark.pdf b/testfiles/testfile20.pdf similarity index 100% rename from testfiles/benchmark.pdf rename to testfiles/testfile20.pdf