Merged feature/pdf-signer into master

This commit is contained in:
Paul van Brouwershaven
2018-05-18 10:39:56 +02:00
27 changed files with 1449 additions and 4608 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
.idea
*.pdf
*.pdf.*
!testfile*.pdf
testfiles/*_signed.pdf
testfiles/failed/*
pdfsign
certs/*

View File

@@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDBDCCAm2gAwIBAgIJAP6vkkLP72OOMA0GCSqGSIb3DQEBCwUAMIGZMQswCQYD
VQQGEwJOTDEVMBMGA1UECAwMWnVpZC1Ib2xsYW5kMRIwEAYDVQQHDAlSb3R0ZXJk
YW0xEjAQBgNVBAoMCVVuaWNvZGVyczELMAkGA1UECwwCSVQxGjAYBgNVBAMMEUpl
cm9lbiBCb2JiZWxkaWprMSIwIAYJKoZIhvcNAQkBFhNqZXJvZW5AdW5pY29kZXJz
Lm5sMCAXDTE3MDcwNjE5MzYwOVoYDzMwMTYxMTA2MTkzNjA5WjCBmTELMAkGA1UE
BhMCTkwxFTATBgNVBAgMDFp1aWQtSG9sbGFuZDESMBAGA1UEBwwJUm90dGVyZGFt
MRIwEAYDVQQKDAlVbmljb2RlcnMxCzAJBgNVBAsMAklUMRowGAYDVQQDDBFKZXJv
ZW4gQm9iYmVsZGlqazEiMCAGCSqGSIb3DQEJARYTamVyb2VuQHVuaWNvZGVycy5u
bDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArpfaVUltYdOSISuc8V5vAy6b
jpqYuxsS5I6jpL1nMKms9IB5+uk+Glo2O/tb+W/R8zxQ3xrQ6JWZ4ZSsBhKNVink
Su3+kdAQJfHn3NLJzx0QGceo0TF2RvVGo5c91zxuA8rchdNz1QxrD6QesGKyfsXn
F+oELezafT346PbeqikCAwEAAaNQME4wHQYDVR0OBBYEFKA68BB0iwhY2RIRFIYs
gmq0l6y7MB8GA1UdIwQYMBaAFKA68BB0iwhY2RIRFIYsgmq0l6y7MAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQELBQADgYEAZ75HjcE/d/nclPTQbCN9qvUyuU76ml4O
jDN8T+loOsUKmI4VVsNLzF6DXq8sg4EP7s8kEEzM7qhoijw09OUhVniBYN3SzJYX
l8AiThPGqcIm1TrkqPULYQBu/FnMoL6SP7kAULcsUvEmn1rPcG9ESQ4sK/ceJhFZ
zk9o3rVC0PU=
-----END CERTIFICATE-----

View File

@@ -1,145 +0,0 @@
package main
import (
"flag"
"log"
"os"
"time"
"crypto/x509"
"io/ioutil"
"bitbucket.org/digitorus/pdfsign/revocation"
"bitbucket.org/digitorus/pdfsign/sign"
"bitbucket.org/digitorus/pdfsign/verify"
"bitbucket.org/digitorus/pkcs11"
)
func usage() {
log.Fatal("Usage: sign input.pdf output.pdf pkcs11-password [chain.crt] OR verify input.pdf")
}
func main() {
flag.Parse()
if len(flag.Args()) < 2 {
usage()
}
method := flag.Arg(0)
if method != "sign" && method != "verify" {
usage()
}
input := flag.Arg(1)
if len(input) == 0 {
usage()
}
if method == "verify" {
input_file, err := os.Open(input)
if err != nil {
log.Fatal(err)
}
defer input_file.Close()
resp, err := verify.Verify(input_file)
log.Println(resp)
if err != nil {
log.Println(err)
}
}
if method == "sign" {
if len(flag.Args()) < 4 {
usage()
}
output := flag.Arg(2)
if len(output) == 0 {
usage()
}
// pkcs11 key
lib, err := pkcs11.FindLib("/lib64/libeTPkcs11.so")
if err != nil {
log.Fatal(err)
}
// Load Library
ctx := pkcs11.New(lib)
if ctx == nil {
log.Fatal("Failed to load library")
}
err = ctx.Initialize()
if err != nil {
log.Fatal(err)
}
// login
session, err := pkcs11.CreateSession(ctx, 0, flag.Arg(3), false)
if err != nil {
log.Fatal(err)
}
// select the first certificate
cert, ckaId, err := pkcs11.GetCert(ctx, session, nil)
if err != nil {
log.Fatal(err)
}
// private key
pkey, err := pkcs11.InitPrivateKey(ctx, session, ckaId)
if err != nil {
log.Fatal(err)
}
certificate_chains := make([][]*x509.Certificate, 0)
if flag.Arg(4) != "" {
certificate_pool := x509.NewCertPool()
if err != nil {
log.Fatal(err)
}
chain_data, err := ioutil.ReadFile(flag.Arg(4))
if err != nil {
log.Fatal(err)
}
certificate_pool.AppendCertsFromPEM(chain_data)
certificate_chains, err = cert.Verify(x509.VerifyOptions{
Intermediates: certificate_pool,
})
if err != nil {
log.Fatal(err)
}
}
// TODO: Obtain TSA from certificate or CLI
err = sign.SignFile(input, output, sign.SignData{
Signature: sign.SignDataSignature{
Info: sign.SignDataSignatureInfo{
Name: "Jeroen Bobbeldijk",
Location: "Rotterdam",
Reason: "Test",
ContactInfo: "Geen",
Date: time.Now().Local(),
},
CertType: 2,
Approval: false,
},
Signer: pkey,
Certificate: cert,
CertificateChains: certificate_chains,
TSA: sign.TSA{
URL: "http://aatl-timestamp.globalsign.com/tsa/aohfewat2389535fnasgnlg5m23",
},
RevocationData: revocation.InfoArchival{},
RevocationFunction: sign.DefaultEmbedRevocationStatusFunction,
})
if err != nil {
log.Println(err)
} else {
log.Println("Signed PDF written to " + output)
}
}
}

View File

@@ -130,7 +130,7 @@ func main() {
Certificate: cert,
CertificateChains: certificate_chains,
TSA: sign.TSA{
URL: "http://aatl-timestamp.globalsign.com/tsa/aohfewat2389535fnasgnlg5m23",
URL: "http://aatl-timestamp.globalsign.com/tsa/aohfewat2389535fnasgnlg5m23",
},
RevocationData: revocation.InfoArchival{},
RevocationFunction: sign.DefaultEmbedRevocationStatusFunction,

View File

@@ -1,21 +1,28 @@
package sign
import (
"bitbucket.org/digitorus/pdf"
"errors"
"fmt"
"io"
"math"
"os"
"strings"
"time"
"bitbucket.org/digitorus/pdf"
)
func findFirstPage(parent pdf.Value) (pdf.Value, error) {
value_type := parent.Key("Type").String()
if value_type == "/Pages" {
recurse_parent, recurse_err := findFirstPage(parent.Key("Kids").Index(0))
return recurse_parent, recurse_err
for i := 0; i < parent.Key("Kids").Len(); i++ {
recurse_parent, recurse_err := findFirstPage(parent.Key("Kids").Index(i))
if recurse_err == nil {
return recurse_parent, recurse_err
}
}
return parent, errors.New("Could not find first page.")
}
if value_type == "/Page" {
@@ -66,10 +73,13 @@ func pdfDateTime(date time.Time) string {
}
func leftPad(s string, padStr string, pLen int) string {
if pLen <= 0 {
return s
}
return strings.Repeat(padStr, pLen) + s
}
func writePartFromSourceFileToTargetFile(input_file *os.File, output_file *os.File, offset int64, length int64) error {
func writePartFromSourceFileToTargetFile(input_file io.ReadSeeker, output_file io.Writer, offset int64, length int64) error {
input_file.Seek(offset, 0)
// Create a small buffer for proper IO handling.
@@ -83,6 +93,10 @@ func writePartFromSourceFileToTargetFile(input_file *os.File, output_file *os.Fi
// Track read/written bytes so we know when we're done.
read_bytes := int64(0)
if length <= 0 {
return nil
}
// Create a buffer for the chunks.
buf := make([]byte, max_chunk_length)
for {

183
sign/helpers_test.go Normal file
View File

@@ -0,0 +1,183 @@
package sign
import (
"bufio"
"bytes"
"os"
"testing"
"time"
"bitbucket.org/digitorus/pdf"
)
func TestFindFirstPage(t *testing.T) {
input_file, reader := loadHelpersTestPDF()
if input_file == nil || reader == nil {
t.Errorf("Failed to load test PDF")
return
}
root := reader.Trailer().Key("Root")
root_keys := root.Keys()
found_pages := false
for _, key := range root_keys {
if key == "Pages" {
found_pages = true
break
}
}
if !found_pages {
input_file.Close()
t.Errorf("Could not find pages element")
return
}
first_page, err := findFirstPage(root.Key("Pages"))
if err != nil {
input_file.Close()
t.Errorf("Could not find first page")
return
}
first_page_ptr := first_page.GetPtr()
if first_page_ptr.GetGen() != 0 {
input_file.Close()
t.Errorf("First page gen mismatch")
}
if first_page_ptr.GetID() != 4 {
t.Errorf("First page ID mismatch")
}
input_file.Close()
}
func TestPDFString(t *testing.T) {
string_compare := map[string]string{
"Test": "(Test)",
"((Test)": "(\\(\\(Test\\))",
"\\TEst": "(\\\\TEst)",
"\rnew": "(\\rnew)",
}
for text, expected := range string_compare {
if pdfString(text) != expected {
t.Errorf("Error while escaping %s. Expected %s, got %s.", text, expected, pdfString(text))
}
}
}
func TestPdfDateTime(t *testing.T) {
timezone, _ := time.LoadLocation("Europe/Tallinn")
timezone_1, _ := time.LoadLocation("Africa/Casablanca")
timezone_2, _ := time.LoadLocation("America/New_York")
timezone_3, _ := time.LoadLocation("Asia/Jerusalem")
timezone_4, _ := time.LoadLocation("Europe/Amsterdam")
timezone_5, _ := time.LoadLocation("Pacific/Honolulu")
now := time.Date(2017, 9, 23, 14, 39, 0, 0, timezone)
date_compare := map[time.Time]string{
now.In(timezone_1): "(D:20170923123900+01'00')",
now.In(timezone_2): "(D:20170923073900-04'00')",
now.In(timezone_3): "(D:20170923143900+03'00')",
now.In(timezone_4): "(D:20170923133900+02'00')",
now.In(timezone_5): "(D:20170923013900-10'00')",
}
for date, expected := range date_compare {
if pdfDateTime(date) != expected {
t.Errorf("Error while converting date %s to string. Expected %s, got %s.", date.String(), expected, pdfDateTime(date))
}
}
}
func TestLeftPad(t *testing.T) {
string_compare := map[string]string{
"123456789": "123456789",
"12345678": "12345678",
"1234567": "_1234567",
"123456": "__123456",
"12345": "___12345",
"1234": "____1234",
"123": "_____123",
"12": "______12",
"1": "_______1",
"": "________",
}
for text, expected := range string_compare {
if leftPad(text, "_", 8-len(text)) != expected {
t.Errorf("Error while left padding %s. Expected %s, got %s.", text, expected, leftPad(text, "_", 8-len(text)))
}
}
}
func TestWritePartFromSourceFileToTargetFile(t *testing.T) {
var b bytes.Buffer
writer := bufio.NewWriter(&b)
input_file, err := os.Open("../testfiles/testfile20.pdf")
if err != nil {
t.Errorf("Failed to load test PDF")
return
}
writePartFromSourceFileToTargetFile(input_file, writer, 0, 0)
writer.Flush()
if writer.Buffered() != 0 {
t.Errorf("Content was copied while length was 0")
}
writePartFromSourceFileToTargetFile(input_file, writer, 0, -20)
writer.Flush()
if writer.Buffered() != 0 {
t.Errorf("Content was copied while length was smaller than 0")
}
writePartFromSourceFileToTargetFile(input_file, writer, 0, 8)
writer.Flush()
if string(b.Bytes()) != "%PDF-2.0" {
t.Errorf("Wrong content was copied, got %s but expected %s", string(b.Bytes()), "%PDF-2.0")
}
writePartFromSourceFileToTargetFile(input_file, writer, 33, 8)
writer.Flush()
if string(b.Bytes()) != "%PDF-2.0/Catalog" {
t.Errorf("Wrong content was copied, got %s but expected %s", string(b.Bytes()), "%PDF-2.0/Catalog")
}
writePartFromSourceFileToTargetFile(input_file, writer, 0, 1200)
if writer.Buffered() != 1200 {
t.Errorf("Requested 1200 bytes but only got %d", writer.Buffered())
}
input_file.Close()
}
func loadHelpersTestPDF() (*os.File, *pdf.Reader) {
input_file, err := os.Open("../testfiles/testfile20.pdf")
if err != nil {
return nil, nil
}
finfo, err := input_file.Stat()
if err != nil {
input_file.Close()
return nil, nil
}
size := finfo.Size()
rdr, err := pdf.NewReader(input_file, size)
if err != nil {
return nil, nil
}
return input_file, rdr
}

View File

@@ -6,11 +6,8 @@ import (
)
func (context *SignContext) updateByteRange() error {
// Get current filesize. Easier than what should be the current size.
// @todo: find out of this is safe.
output_file_stat, _ := context.OutputFile.Stat()
output_file_size := output_file_stat.Size()
context.OutputBuffer.Seek(0, 0)
output_file_size := int64(context.OutputBuffer.Buff.Len())
// Calculate ByteRange values to replace them.
context.ByteRangeValues = make([]int64, 4)
@@ -32,13 +29,17 @@ func (context *SignContext) updateByteRange() error {
// Make sure our ByteRange string didn't shrink in length.
new_byte_range += strings.Repeat(" ", len(signatureByteRangePlaceholder)-len(new_byte_range))
// Seek to ByteRange position in file.
context.OutputFile.Seek(context.ByteRangeStartByte, 0)
context.OutputBuffer.Seek(0, 0)
file_content := context.OutputBuffer.Buff.Bytes()
context.OutputBuffer.Write(file_content[:context.ByteRangeStartByte])
// Write new ByteRange.
if _, err := context.OutputFile.Write([]byte(new_byte_range)); err != nil {
if _, err := context.OutputBuffer.Write([]byte(new_byte_range)); err != nil {
return err
}
context.OutputBuffer.Write(file_content[context.ByteRangeStartByte+int64(len(new_byte_range)):])
return nil
}

View File

@@ -1,7 +1,6 @@
package sign
import (
"errors"
"strconv"
)
@@ -20,15 +19,14 @@ func (context *SignContext) createCatalog() (catalog string, err error) {
}
}
if !found_pages {
return "", errors.New("Didn't find pages in PDF trailer Root.")
}
rootPtr := root.GetPtr()
context.CatalogData.RootString = strconv.Itoa(int(rootPtr.GetID())) + " " + strconv.Itoa(int(rootPtr.GetGen())) + " R"
pages := root.Key("Pages").GetPtr()
catalog += " /Pages " + strconv.Itoa(int(pages.GetID())) + " " + strconv.Itoa(int(pages.GetGen())) + " R"
if found_pages {
pages := root.Key("Pages").GetPtr()
catalog += " /Pages " + strconv.Itoa(int(pages.GetID())) + " " + strconv.Itoa(int(pages.GetGen())) + " R"
}
catalog += " /AcroForm <<"
catalog += " /Fields [" + strconv.Itoa(int(context.VisualSignData.ObjectId)) + " 0 R]"

56
sign/pdfcatalog_test.go Normal file
View File

@@ -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)
}
}

138
sign/pdfinfo_test.go Normal file
View File

@@ -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<</Author(User: Isamu Ohzawa [isamu])/CreationDate(D:19981025161109)/Creator(FastIO Systems: cover.c)/Keywords(ClibPDF, ANSI C Library, Acrobat, PDF, Dynamic Web, Graph, Plot)/Producer([ClibPDF Library 0.96] NEXTSTEP or OPENSTEP)/Subject(ANSI C Library for Direct PDF Generation)/Title(ClibPDF Reference Manual)>>\nendobj\n"
if info != expected_info {
t.Errorf("Info mismatch, expected %s, but got %s", expected_info, info)
}
}

View File

@@ -2,17 +2,15 @@ package sign
import (
"bytes"
"crypto/x509"
"encoding/asn1"
"encoding/hex"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"strconv"
"strings"
"crypto/x509"
"github.com/digitorus/pkcs7"
"github.com/digitorus/timestamp"
)
@@ -31,60 +29,69 @@ type TSAResponse struct {
var signatureByteRangePlaceholder = "/ByteRange[0 ********** ********** **********]"
func (context *SignContext) createSignaturePlaceholder() (signature string, byte_range_start_byte int64, signature_contents_start_byte int64) {
signature = strconv.Itoa(int(context.SignData.ObjectId)) + " 0 obj\n"
signature += "<< /Type /Sig"
signature += " /Filter /Adobe.PPKLite"
signature += " /SubFilter /adbe.pkcs7.detached"
func (context *SignContext) createSignaturePlaceholder() (dssd string, byte_range_start_byte int64, signature_contents_start_byte int64) {
// 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")
signature_buffer.WriteString(" /Filter /Adobe.PPKLite")
signature_buffer.WriteString(" /SubFilter /adbe.pkcs7.detached")
byte_range_start_byte = int64(len(signature)) + 1
byte_range_start_byte = int64(signature_buffer.Len()) + 1
// Create a placeholder for the byte range string, we will replace it later.
signature += " " + signatureByteRangePlaceholder
signature_buffer.WriteString(" " + signatureByteRangePlaceholder)
signature_contents_start_byte = int64(len(signature)) + 11
signature_contents_start_byte = int64(signature_buffer.Len()) + 11
// Create a placeholder for the actual signature content, we wil replace it later.
signature += " /Contents<" + strings.Repeat("0", int(context.SignatureMaxLength)) + ">"
signature_buffer.WriteString(" /Contents<")
signature_buffer.Write(bytes.Repeat([]byte("0"), int(context.SignatureMaxLength)))
signature_buffer.WriteString(">")
if !context.SignData.Signature.Approval {
signature += " /Reference [" // array of signature reference dictionaries
signature += " << /Type /SigRef"
signature_buffer.WriteString(" /Reference [") // array of signature reference dictionaries
signature_buffer.WriteString(" << /Type /SigRef")
if context.SignData.Signature.CertType > 0 {
signature += " /TransformMethod /DocMDP"
signature += " /TransformParams <<"
signature += " /Type /TransformParams"
signature += " /P " + strconv.Itoa(int(context.SignData.Signature.CertType))
signature += " /V /1.2"
signature_buffer.WriteString(" /TransformMethod /DocMDP")
signature_buffer.WriteString(" /TransformParams <<")
signature_buffer.WriteString(" /Type /TransformParams")
signature_buffer.WriteString(" /P " + strconv.Itoa(int(context.SignData.Signature.CertType)))
signature_buffer.WriteString(" /V /1.2")
} else {
signature += " /TransformMethod /UR3"
signature += " /TransformParams <<"
signature += " /Type /TransformParams"
signature += " /V /2.2"
signature_buffer.WriteString(" /TransformMethod /UR3")
signature_buffer.WriteString(" /TransformParams <<")
signature_buffer.WriteString(" /Type /TransformParams")
signature_buffer.WriteString(" /V /2.2")
}
signature += " >>" // close TransformParams
signature += " >>"
signature += " ]" // end of reference
signature_buffer.WriteString(" >>") // close TransformParams
signature_buffer.WriteString(" >>")
signature_buffer.WriteString(" ]") // end of reference
}
if context.SignData.Signature.Info.Name != "" {
signature += " /Name " + pdfString(context.SignData.Signature.Info.Name)
signature_buffer.WriteString(" /Name ")
signature_buffer.WriteString(pdfString(context.SignData.Signature.Info.Name))
}
if context.SignData.Signature.Info.Location != "" {
signature += " /Location " + pdfString(context.SignData.Signature.Info.Location)
signature_buffer.WriteString(" /Location ")
signature_buffer.WriteString(pdfString(context.SignData.Signature.Info.Location))
}
if context.SignData.Signature.Info.Reason != "" {
signature += " /Reason " + pdfString(context.SignData.Signature.Info.Reason)
signature_buffer.WriteString(" /Reason ")
signature_buffer.WriteString(pdfString(context.SignData.Signature.Info.Reason))
}
if context.SignData.Signature.Info.ContactInfo != "" {
signature += " /ContactInfo " + pdfString(context.SignData.Signature.Info.ContactInfo)
signature_buffer.WriteString(" /ContactInfo ")
signature_buffer.WriteString(pdfString(context.SignData.Signature.Info.ContactInfo))
}
signature += " /M " + pdfDateTime(context.SignData.Signature.Info.Date)
signature += " >>"
signature += "\nendobj\n"
signature_buffer.WriteString(" /M ")
signature_buffer.WriteString(pdfDateTime(context.SignData.Signature.Info.Date))
signature_buffer.WriteString(" >>")
signature_buffer.WriteString("\nendobj\n")
return signature, byte_range_start_byte, signature_contents_start_byte
return signature_buffer.String(), byte_range_start_byte, signature_contents_start_byte
}
func (context *SignContext) fetchRevocationData() error {
@@ -111,22 +118,20 @@ func (context *SignContext) fetchRevocationData() error {
// Calculate space needed for signature.
for _, crl := range context.SignData.RevocationData.CRL {
context.SignatureMaxLength += uint32(len(crl.FullBytes) * 2)
context.SignatureMaxLength += uint32(hex.EncodedLen(len(crl.FullBytes)))
}
for _, ocsp := range context.SignData.RevocationData.OCSP {
context.SignatureMaxLength += uint32(len(ocsp.FullBytes) * 2)
context.SignatureMaxLength += uint32(hex.EncodedLen(len(ocsp.FullBytes)))
}
return nil
}
func (context *SignContext) createSignature() ([]byte, error) {
context.OutputBuffer.Seek(0, 0)
// Sadly we can't efficiently sign a file, we need to read all the bytes we want to sign.
context.OutputFile.Seek(0, 0)
sign_buf := bytes.NewBuffer(nil)
io.Copy(sign_buf, context.OutputFile)
file_content := sign_buf.Bytes()
file_content := context.OutputBuffer.Buff.Bytes()
// Collect the parts to sign.
sign_content := make([]byte, 0)
@@ -266,7 +271,17 @@ func (context *SignContext) replaceSignature() error {
return errors.New("Signature is too big to fit in reserved space.")
}
context.OutputFile.WriteAt(dst, context.ByteRangeValues[0]+context.ByteRangeValues[1]+1)
context.OutputBuffer.Seek(0, 0)
file_content := context.OutputBuffer.Buff.Bytes()
context.OutputBuffer.Write(file_content[:(context.ByteRangeValues[0] + context.ByteRangeValues[1] + 1)])
// Write new ByteRange.
if _, err := context.OutputBuffer.Write([]byte(dst)); err != nil {
return err
}
context.OutputBuffer.Write(file_content[(context.ByteRangeValues[0]+context.ByteRangeValues[1]+1)+int64(len(dst)):])
return nil
}

81
sign/pdfsignature_test.go Normal file
View File

@@ -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)
}
}

View File

@@ -6,44 +6,47 @@ import (
)
func (context *SignContext) writeTrailer() error {
trailer_length := context.PDFReader.XrefInformation.IncludingTrailerEndPos - context.PDFReader.XrefInformation.EndPos
// Read the trailer so we can replace the size.
context.InputFile.Seek(context.PDFReader.XrefInformation.EndPos+1, 0)
trailer_buf := make([]byte, trailer_length)
if _, err := context.InputFile.Read(trailer_buf); err != nil {
return err
}
if context.PDFReader.XrefInformation.Type == "table" {
trailer_length := context.PDFReader.XrefInformation.IncludingTrailerEndPos - context.PDFReader.XrefInformation.EndPos
root_string := "Root " + context.CatalogData.RootString
new_root := "Root " + strconv.FormatInt(int64(context.CatalogData.ObjectId), 10) + " 0 R"
// Read the trailer so we can replace the size.
context.InputFile.Seek(context.PDFReader.XrefInformation.EndPos+1, 0)
trailer_buf := make([]byte, trailer_length)
if _, err := context.InputFile.Read(trailer_buf); err != nil {
return err
}
size_string := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount, 10)
new_size := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount+4, 10)
root_string := "Root " + context.CatalogData.RootString
new_root := "Root " + strconv.FormatInt(int64(context.CatalogData.ObjectId), 10) + " 0 R"
info := context.PDFReader.Trailer().Key("Info")
infoPtr := info.GetPtr()
size_string := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount, 10)
new_size := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount+4, 10)
info_string := "Info " + strconv.Itoa(int(infoPtr.GetID())) + " " + strconv.Itoa(int(infoPtr.GetGen())) + " R"
new_info := "Info " + strconv.FormatInt(int64(context.InfoData.ObjectId), 10) + " 0 R"
info := context.PDFReader.Trailer().Key("Info")
infoPtr := info.GetPtr()
trailer_string := string(trailer_buf)
trailer_string = strings.Replace(trailer_string, root_string, new_root, -1)
trailer_string = strings.Replace(trailer_string, size_string, new_size, -1)
trailer_string = strings.Replace(trailer_string, info_string, new_info, -1)
info_string := "Info " + strconv.Itoa(int(infoPtr.GetID())) + " " + strconv.Itoa(int(infoPtr.GetGen())) + " R"
new_info := "Info " + strconv.FormatInt(int64(context.InfoData.ObjectId), 10) + " 0 R"
// Write the new trailer.
if _, err := context.OutputFile.Write([]byte(trailer_string)); err != nil {
return err
trailer_string := string(trailer_buf)
trailer_string = strings.Replace(trailer_string, root_string, new_root, -1)
trailer_string = strings.Replace(trailer_string, size_string, new_size, -1)
trailer_string = strings.Replace(trailer_string, info_string, new_info, -1)
// Write the new trailer.
if _, err := context.OutputBuffer.Write([]byte(trailer_string)); err != nil {
return err
}
}
// Write the new xref start position.
if _, err := context.OutputFile.Write([]byte(strconv.FormatInt(context.NewXrefStart, 10) + "\n")); err != nil {
if _, err := context.OutputBuffer.Write([]byte(strconv.FormatInt(context.NewXrefStart, 10) + "\n")); err != nil {
return err
}
// Write PDF ending.
if _, err := context.OutputFile.Write([]byte("%%EOF")); err != nil {
if _, err := context.OutputBuffer.Write([]byte("%%EOF")); err != nil {
return err
}

View File

@@ -1,7 +1,6 @@
package sign
import (
"errors"
"strconv"
)
@@ -21,22 +20,20 @@ func (context *SignContext) createVisualSignature() (visual_signature string, er
}
}
if !found_pages {
return "", errors.New("Didn't find pages in PDF trailer Root.")
}
rootPtr := root.GetPtr()
context.CatalogData.RootString = strconv.Itoa(int(rootPtr.GetID())) + " " + strconv.Itoa(int(rootPtr.GetGen())) + " R"
first_page, err := findFirstPage(root.Key("Pages"))
if err != nil {
return "", err
if found_pages {
first_page, err := findFirstPage(root.Key("Pages"))
if err != nil {
return "", err
}
first_page_ptr := first_page.GetPtr()
visual_signature += " /P " + strconv.Itoa(int(first_page_ptr.GetID())) + " " + strconv.Itoa(int(first_page_ptr.GetGen())) + " R"
}
first_page_ptr := first_page.GetPtr()
visual_signature += " /P " + strconv.Itoa(int(first_page_ptr.GetID())) + " " + strconv.Itoa(int(first_page_ptr.GetGen())) + " R"
visual_signature += " /F 132"
visual_signature += " /FT /Sig"
visual_signature += " /T " + pdfString("Signature")

View File

@@ -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)
}
}

View File

@@ -1,18 +1,24 @@
package sign
import (
"bytes"
"compress/zlib"
"encoding/binary"
"encoding/hex"
"errors"
"strconv"
)
func (context *SignContext) writeXref() error {
// @todo: support stream xref.
if context.PDFReader.XrefInformation.Type == "table" {
if err := context.writeXrefTable(); err != nil {
return err
}
} else if context.PDFReader.XrefInformation.Type == "stream" {
if err := context.writeXrefStream(); err != nil {
return err
}
} else {
return errors.New("Unkwn xref type: " + context.PDFReader.XrefInformation.Type)
}
@@ -21,15 +27,16 @@ func (context *SignContext) writeXref() error {
}
func (context *SignContext) writeXrefTable() error {
// @todo: maybe we need a prev here too.
xref_size := "xref\n0 " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount, 10)
new_xref_size := "xref\n0 " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount+4, 10)
if _, err := context.OutputFile.Write([]byte(new_xref_size)); err != nil {
if _, err := context.OutputBuffer.Write([]byte(new_xref_size)); err != nil {
return err
}
// Write the old xref table to the output pdf.
if err := writePartFromSourceFileToTargetFile(context.InputFile, context.OutputFile, context.PDFReader.XrefInformation.StartPos+int64(len(xref_size)), context.PDFReader.XrefInformation.Length-int64(len(xref_size))); err != nil {
if err := writePartFromSourceFileToTargetFile(context.InputFile, context.OutputBuffer, context.PDFReader.XrefInformation.StartPos+int64(len(xref_size)), context.PDFReader.XrefInformation.Length-int64(len(xref_size))); err != nil {
return err
}
@@ -38,7 +45,7 @@ func (context *SignContext) writeXrefTable() error {
visual_signature_xref_line := leftPad(visual_signature_object_start_position, "0", 10-len(visual_signature_object_start_position)) + " 00000 n \n"
// Write the new catalog xref line.
if _, err := context.OutputFile.Write([]byte(visual_signature_xref_line)); err != nil {
if _, err := context.OutputBuffer.Write([]byte(visual_signature_xref_line)); err != nil {
return err
}
@@ -47,7 +54,7 @@ func (context *SignContext) writeXrefTable() error {
catalog_xref_line := leftPad(catalog_object_start_position, "0", 10-len(catalog_object_start_position)) + " 00000 n \n"
// Write the new catalog xref line.
if _, err := context.OutputFile.Write([]byte(catalog_xref_line)); err != nil {
if _, err := context.OutputBuffer.Write([]byte(catalog_xref_line)); err != nil {
return err
}
@@ -56,7 +63,7 @@ func (context *SignContext) writeXrefTable() error {
info_xref_line := leftPad(info_object_start_position, "0", 10-len(info_object_start_position)) + " 00000 n \n"
// Write the new signature xref line.
if _, err := context.OutputFile.Write([]byte(info_xref_line)); err != nil {
if _, err := context.OutputBuffer.Write([]byte(info_xref_line)); err != nil {
return err
}
@@ -65,9 +72,145 @@ func (context *SignContext) writeXrefTable() error {
signature_xref_line := leftPad(signature_object_start_position, "0", 10-len(signature_object_start_position)) + " 00000 n \n"
// Write the new signature xref line.
if _, err := context.OutputFile.Write([]byte(signature_xref_line)); err != nil {
if _, err := context.OutputBuffer.Write([]byte(signature_xref_line)); err != nil {
return err
}
return nil
}
func (context *SignContext) writeXrefStream() error {
buffer := bytes.NewBuffer(nil)
predictor := context.PDFReader.Trailer().Key("DecodeParms").Key("Predictor").Int64()
streamBytes := []byte{}
err := errors.New("")
writeXrefStreamLine(buffer, 1, int(context.Filesize), 0)
writeXrefStreamLine(buffer, 1, int(context.Filesize+context.VisualSignData.Length), 0)
writeXrefStreamLine(buffer, 1, int(context.Filesize+context.VisualSignData.Length+context.CatalogData.Length), 0)
writeXrefStreamLine(buffer, 1, int(context.Filesize+context.VisualSignData.Length+context.CatalogData.Length+context.InfoData.Length), 0)
writeXrefStreamLine(buffer, 1, int(context.NewXrefStart), 0)
// If original uses PNG Sub, use that.
if predictor == 11 {
streamBytes, err = EncodePNGSUBBytes(5, buffer.Bytes())
if err != nil {
return err
}
} else {
// Do PNG - Up by default.
streamBytes, err = EncodePNGUPBytes(5, buffer.Bytes())
if err != nil {
return err
}
}
new_info := "Info " + strconv.FormatInt(int64(context.InfoData.ObjectId), 10) + " 0 R"
new_root := "Root " + strconv.FormatInt(int64(context.CatalogData.ObjectId), 10) + " 0 R"
id := context.PDFReader.Trailer().Key("ID")
id0 := hex.EncodeToString([]byte(id.Index(0).RawString()))
id1 := hex.EncodeToString([]byte(id.Index(0).RawString()))
new_xref := strconv.Itoa(int(context.SignData.ObjectId+1)) + " 0 obj\n"
new_xref += "<< /Type /XRef /Length " + strconv.Itoa(len(streamBytes)) + " /Filter /FlateDecode /DecodeParms << /Columns 5 /Predictor 12 >> /W [ 1 3 1 ] /Prev " + strconv.FormatInt(context.PDFReader.XrefInformation.StartPos, 10) + " /Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount+5, 10) + " /Index [ " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount, 10) + " 5 ] /" + new_info + " /" + new_root + " /ID [<" + id0 + "><" + id1 + ">] >>\n"
if _, err := context.OutputBuffer.Write([]byte(new_xref)); err != nil {
return err
}
if _, err := context.OutputBuffer.Write([]byte("stream\n")); err != nil {
return err
}
if _, err := context.OutputBuffer.Write(streamBytes); err != nil {
return err
}
if _, err := context.OutputBuffer.Write([]byte("\nendstream\n")); err != nil {
return err
}
return nil
}
func writeXrefStreamLine(b *bytes.Buffer, xreftype byte, offset int, gen byte) {
b.WriteByte(xreftype)
b.Write(encodeInt(offset))
b.WriteByte(gen)
}
func encodeInt(i int) []byte {
result := make([]byte, 4)
binary.BigEndian.PutUint32(result, uint32(i))
return result[1:4]
}
func EncodePNGSUBBytes(columns int, data []byte) ([]byte, error) {
rowCount := len(data) / columns
if len(data)%columns != 0 {
return nil, errors.New("Invalid row/column length")
}
buffer := bytes.NewBuffer(nil)
tmpRowData := make([]byte, columns)
for i := 0; i < rowCount; i++ {
rowData := data[columns*i : columns*(i+1)]
tmpRowData[0] = rowData[0]
for j := 1; j < columns; j++ {
tmpRowData[j] = byte(int(rowData[j]-rowData[j-1]) % 256)
}
buffer.WriteByte(1)
buffer.Write(tmpRowData)
}
data = buffer.Bytes()
var b bytes.Buffer
w := zlib.NewWriter(&b)
w.Write(data)
w.Close()
return b.Bytes(), nil
}
func EncodePNGUPBytes(columns int, data []byte) ([]byte, error) {
rowCount := len(data) / columns
if len(data)%columns != 0 {
return nil, errors.New("Invalid row/column length")
}
prevRowData := make([]byte, columns)
// Initially all previous data is zero.
for i := 0; i < columns; i++ {
prevRowData[i] = 0
}
buffer := bytes.NewBuffer(nil)
tmpRowData := make([]byte, columns)
for i := 0; i < rowCount; i++ {
rowData := data[columns*i : columns*(i+1)]
for j := 0; j < columns; j++ {
tmpRowData[j] = byte(int(rowData[j]-prevRowData[j]) % 256)
}
// Save the previous row for prediction.
copy(prevRowData, rowData)
buffer.WriteByte(2)
buffer.Write(tmpRowData)
}
data = buffer.Bytes()
var b bytes.Buffer
w := zlib.NewWriter(&b)
w.Write(data)
w.Close()
return b.Bytes(), nil
}

View File

@@ -3,12 +3,15 @@ package sign
import (
"crypto"
"crypto/x509"
"encoding/hex"
"io"
"os"
"time"
"bitbucket.org/digitorus/pdf"
"bitbucket.org/digitorus/pdfsign/revocation"
"github.com/digitorus/pkcs7"
"github.com/mattetti/filebuffer"
)
type CatalogData struct {
@@ -62,8 +65,9 @@ type SignDataSignatureInfo struct {
type SignContext struct {
Filesize int64
InputFile *os.File
OutputFile *os.File
InputFile io.ReadSeeker
OutputFile io.Writer
OutputBuffer *filebuffer.Buffer
SignData SignData
CatalogData CatalogData
VisualSignData VisualSignData
@@ -100,14 +104,18 @@ func SignFile(input string, output string, sign_data SignData) error {
return err
}
return Sign(input_file, output_file, rdr, size, sign_data)
}
func Sign(input io.ReadSeeker, output io.Writer, rdr *pdf.Reader, size int64, sign_data SignData) error {
sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 3
// We do size+1 because we insert a newline.
context := SignContext{
Filesize: size + 1,
PDFReader: rdr,
InputFile: input_file,
OutputFile: output_file,
InputFile: input,
OutputFile: output,
VisualSignData: VisualSignData{
ObjectId: uint32(rdr.XrefInformation.ItemCount),
},
@@ -120,7 +128,7 @@ func SignFile(input string, output string, sign_data SignData) error {
SignData: sign_data,
}
err = context.SignPDF()
err := context.SignPDF()
if err != nil {
return err
}
@@ -129,28 +137,71 @@ func SignFile(input string, output string, sign_data SignData) error {
}
func (context *SignContext) SignPDF() error {
// Copy old file into new file.
if _, err := io.Copy(context.OutputFile, context.InputFile); err != nil {
return err
}
context.OutputBuffer = filebuffer.New([]byte{})
err := context.OutputFile.Sync()
if err != nil {
// Copy old file into new file.
if _, err := io.Copy(context.OutputBuffer, context.InputFile); err != nil {
return err
}
// File always needs an empty line after %%EOF.
if _, err := context.OutputFile.Write([]byte("\n")); err != nil {
if _, err := context.OutputBuffer.Write([]byte("\n")); err != nil {
return err
}
// Base size for signature.
context.SignatureMaxLength = 100000
context.SignatureMaxLength = uint32(hex.EncodedLen(512))
switch string(context.SignData.Certificate.SignatureAlgorithm) {
case "SHA1-RSA":
case "ECDSA-SHA1":
case "DSA-SHA1":
context.SignatureMaxLength += uint32(hex.EncodedLen(128))
break
case "SHA256-RSA":
case "ECDSA-SHA256":
case "DSA-SHA256":
context.SignatureMaxLength += uint32(hex.EncodedLen(256))
break
case "SHA384-RSA":
case "ECDSA-SHA384":
context.SignatureMaxLength += uint32(hex.EncodedLen(384))
break
case "SHA512-RSA":
case "ECDSA-SHA512":
context.SignatureMaxLength += uint32(hex.EncodedLen(512))
break
}
// Add size for my certificate.
degenerated, err := pkcs7.DegenerateCertificate(context.SignData.Certificate.Raw)
if err != nil {
return err
}
context.SignatureMaxLength += uint32(hex.EncodedLen(len(degenerated)))
// Add size for certificate chain.
var certificate_chain []*x509.Certificate
if len(context.SignData.CertificateChains) > 0 && len(context.SignData.CertificateChains[0]) > 1 {
certificate_chain = context.SignData.CertificateChains[0][1:]
}
if len(certificate_chain) > 0 {
for _, cert := range certificate_chain {
degenerated, err := pkcs7.DegenerateCertificate(cert.Raw)
if err != nil {
return err
}
context.SignatureMaxLength += uint32(hex.EncodedLen(len(degenerated)))
}
}
// Add estimated size for TSA.
// We can't kow actual size of TSA until after signing.
if context.SignData.TSA.URL != "" {
context.SignatureMaxLength += 10000
context.SignatureMaxLength += uint32(hex.EncodedLen(4000))
}
// Fetch revocation data before adding signature placeholder.
@@ -165,7 +216,7 @@ func (context *SignContext) SignPDF() error {
context.VisualSignData.Length = int64(len(visual_signature))
// Write the new catalog object.
if _, err := context.OutputFile.Write([]byte(visual_signature)); err != nil {
if _, err := context.OutputBuffer.Write([]byte(visual_signature)); err != nil {
return err
}
@@ -177,7 +228,7 @@ func (context *SignContext) SignPDF() error {
context.CatalogData.Length = int64(len(catalog))
// Write the new catalog object.
if _, err := context.OutputFile.Write([]byte(catalog)); err != nil {
if _, err := context.OutputBuffer.Write([]byte(catalog)); err != nil {
return err
}
@@ -192,7 +243,7 @@ func (context *SignContext) SignPDF() error {
context.InfoData.Length = int64(len(info))
// Write the new catalog object.
if _, err := context.OutputFile.Write([]byte(info)); err != nil {
if _, err := context.OutputBuffer.Write([]byte(info)); err != nil {
return err
}
@@ -206,7 +257,7 @@ func (context *SignContext) SignPDF() error {
context.SignatureContentsStartByte = signature_contents_start_byte
// Write the new signature object.
if _, err := context.OutputFile.Write([]byte(signature_object)); err != nil {
if _, err := context.OutputBuffer.Write([]byte(signature_object)); err != nil {
return err
}
@@ -229,10 +280,10 @@ func (context *SignContext) SignPDF() error {
return err
}
err = context.OutputFile.Sync()
if err != nil {
return err
}
context.OutputBuffer.Seek(0, 0)
file_content := context.OutputBuffer.Buff.Bytes()
context.OutputFile.Write(file_content)
return nil
}

351
sign/sign_test.go Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

0
testfiles/.gitkeep Normal file
View File

BIN
testfiles/testfile12.pdf Normal file

Binary file not shown.

201
testfiles/testfile20.pdf Normal file
View File

@@ -0,0 +1,201 @@
%PDF-2.0
1 0 obj
<<
/Type /Catalog
/Metadata 2 0 R
/Pages 3 0 R
>>
endobj
2 0 obj
<<
/Length 2351
/Type /Metadata
/Subtype /XML
>>
stream
<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Insert XMP tool name here.'>
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
<pdf:Producer>Datalogics - example producer program name here</pdf:Producer>
<pdf:Copyright>Copyright 2017 PDF Association</pdf:Copyright>
<pdf:Keywords>PDF 2.0 sample example</pdf:Keywords>
</rdf:Description>
<rdf:Description rdf:about="" xmlns:xap="http://ns.adobe.com/xap/1.0/">
<xap:CreateDate>2017-05-24T10:30:11Z</xap:CreateDate>
<xap:MetadataDate>2017-07-11T07:55:11Z</xap:MetadataDate>
<xap:ModifyDate>2017-07-11T07:55:11Z</xap:ModifyDate>
<xap:CreatorTool>Datalogics - example creator tool name here</xap:CreatorTool>
</rdf:Description>
<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:format>application/pdf</dc:format>
<dc:title>
<rdf:Alt>
<rdf:li xml:lang="x-default">A simple PDF 2.0 example file</rdf:li>
</rdf:Alt>
</dc:title>
<dc:creator>
<rdf:Seq>
<rdf:li>Datalogics Incorporated</rdf:li>
</rdf:Seq>
</dc:creator>
<dc:description>
<rdf:Alt>
<rdf:li xml:lang="x-default">Demonstration of a simple PDF 2.0 file.</rdf:li>
</rdf:Alt>
</dc:description>
<dc:rights>
<rdf:Alt>
<rdf:li xml:lang="x-default">Copyright 2017 PDF Association. Licensed to the public under Creative Commons Attribution-ShareAlike 4.0 International license.</rdf:li>
</rdf:Alt>
</dc:rights>
</rdf:Description>
<rdf:Description rdf:about="" xmlns:xapRights="http://ns.adobe.com/xap/1.0/rights/">
<xapRights:Marked>True</xapRights:Marked>
</rdf:Description>
<rdf:Description rdf:about="" xmlns:cc="http://creativecommons.org/ns#">
<cc:license rdf:resource="https://creativecommons.org/licenses/sa/4.0/" />
</rdf:Description>
<rdf:Description rdf:about="" xmlns:xapMM="http://ns.adobe.com/xap/1.0/mm/">
<xapMM:DocumentID>uuid:3eef2166-8332-abb4-3d31-77334578873f</xapMM:DocumentID>
<xapMM:InstanceID>uuid:991bcce7-ee70-11a3-91aa-77bbe2181fd8</xapMM:InstanceID>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
endstream
endobj
3 0 obj
<<
/Type /Pages
/Kids [4 0 R]
/Count 1
>>
endobj
4 0 obj
<<
/Type /Page
/Parent 3 0 R
/MediaBox [0 0 612 396]
/Contents [5 0 R 6 0 R]
/Resources <<
/Font << /F1 7 0 R >>
>>
>>
endobj
5 0 obj
<< /Length 744 >>
stream
% Save the current graphic state
q
% Draw a black line segment, using the default line width.
150 250 m
150 350 l
S
% Draw a thicker, dashed line segment.
4 w % Set line width to 4 points
[4 6] 0 d % Set dash pattern to 4 units on, 6 units off
150 250 m
400 250 l
S
[] 0 d % Reset dash pattern to a solid line
1 w % Reset line width to 1 unit
% Draw a rectangle with a 1-unit red border, filled with light blue.
1.0 0.0 0.0 RG % Red for stroke color
0.5 0.75 1.0 rg % Light blue for fill color
200 300 50 75 re
B
% Draw a curve filled with gray and with a colored border.
0.5 0.1 0.2 RG
0.7 g
300 300 m
300 400 400 400 400 300 c
b
% Restore the graphic state to what it was at the beginning of this stream
Q
endstream
endobj
6 0 obj
<< /Length 166 >>
stream
% A text block that shows "Hello World"
% No color is set, so this defaults to black in DeviceGray colorspace
BT
/F1 24 Tf
100 100 Td
(Hello World) Tj
ET
endstream
endobj
7 0 obj
<<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
/FirstChar 33
/LastChar 126
/Widths 8 0 R
/FontDescriptor 9 0 R
>>
endobj
8 0 obj
[ 278 355 556 556 889 667 222 333 333 389 584 278 333 278 278 556
556 556 556 556 556 556 556 556 556 278 278 584 584 584 556 1015
667 667 722 722 667 611 778 722 278 500 667 556 833 722 778 667
778 722 667 611 722 667 944 667 667 611 278 278 278 469 556 222
556 556 500 556 556 278 556 556 222 222 500 222 833 556 556 556
556 333 500 278 556 500 722 500 500 500 334 260 334 584 ]
endobj
% This FontDescriptor contains only the required entries for PDF 2.0
% for unembedded standard 14 fonts that contain Latin characters
9 0 obj
<<
/Type /FontDescriptor
/FontName /Helvetica
/Flags 32
/FontBBox [ -166 -225 1000 931 ]
/ItalicAngle 0
/Ascent 718
/Descent -207
/CapHeight 718
/StemV 88
/MissingWidth 0
>>
endobj
% The object cross-reference table. The first entry
% denotes the start of PDF data in this file.
xref
0 10
0000000000 65535 f
0000000012 00000 n
0000000092 00000 n
0000002543 00000 n
0000002615 00000 n
0000002778 00000 n
0000003583 00000 n
0000003807 00000 n
0000003968 00000 n
0000004520 00000 n
trailer
<<
/Size 10
/Root 1 0 R
/ID [ <31c7a8a269e4c59bc3cd7df0dabbf388><31c7a8a269e4c59bc3cd7df0dabbf388> ]
>>
startxref
4847
%%EOF

View File

@@ -10,13 +10,13 @@ import (
"os"
"time"
"crypto"
"bitbucket.org/digitorus/pdf"
"bitbucket.org/digitorus/pdfsign/revocation"
"github.com/digitorus/pkcs7"
"github.com/digitorus/timestamp"
"log"
"golang.org/x/crypto/ocsp"
"crypto"
)
type Response struct {
@@ -187,10 +187,10 @@ func Verify(file *os.File) (apiResp *Response, err error) {
signer.ValidSignature = true
signer.TrustedIssuer = false
}
log.Println("Invalid sig")
//log.Println("Invalid sig")
apiResp.Error = fmt.Sprintln("Failed to verify signature:", err)
} else {
log.Println("Valid sig")
//log.Println("Valid sig")
signer.ValidSignature = true
signer.TrustedIssuer = true
}