From db69b6a4c0dc5e9e57bd8516ffd4d370fb39c8ce Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Sat, 8 Jul 2017 14:55:31 +0200 Subject: [PATCH] Append instead of replace --- sign.go | 2 ++ sign/pdfbyterange.go | 7 +++-- sign/pdfcatalog.go | 7 +++-- sign/pdfsignature.go | 7 ++--- sign/pdftrailer.go | 4 +-- sign/pdfvisualsignature.go | 44 +++++++++++++++++++++++++++++++ sign/pdfxref.go | 15 ++++++++--- sign/sign.go | 54 +++++++++++++++++++++++++++++++++----- verify/verify.go | 13 ++++++++- 9 files changed, 127 insertions(+), 26 deletions(-) create mode 100644 sign/pdfvisualsignature.go diff --git a/sign.go b/sign.go index 3aec357..698bfb6 100644 --- a/sign.go +++ b/sign.go @@ -98,6 +98,8 @@ func main() { ContactInfo: "Geen", Date: time.Now().Local(), }, + CertType: 2, + Approval: false, }, Signer: pkey, Certificate: cert, diff --git a/sign/pdfbyterange.go b/sign/pdfbyterange.go index 3e0b58d..5bc3550 100644 --- a/sign/pdfbyterange.go +++ b/sign/pdfbyterange.go @@ -10,8 +10,7 @@ func (context *SignContext) updateByteRange() error { // @todo: find out of this is safe. output_file_stat, _ := context.OutputFile.Stat() - // Don't count last newline as file length. - output_file_size := output_file_stat.Size() - 1 + output_file_size := output_file_stat.Size() // Calculate ByteRange values to replace them. context.ByteRangeValues = make([]int64, 4) @@ -20,10 +19,10 @@ func (context *SignContext) updateByteRange() error { context.ByteRangeValues[0] = int64(0) // Signature ByteRange part 1 length always stops at the actual signature start byte. - context.ByteRangeValues[1] = context.SignatureContentsStartByte + context.ByteRangeValues[1] = context.SignatureContentsStartByte - 1 // Signature ByteRange part 2 start byte directly starts after the actual signature. - context.ByteRangeValues[2] = context.ByteRangeValues[1] + int64(signatureMaxLength) + context.ByteRangeValues[2] = context.ByteRangeValues[1] + 1 + int64(signatureMaxLength) + 1 // Signature ByteRange part 2 length is everything else of the file. context.ByteRangeValues[3] = output_file_size - context.ByteRangeValues[2] diff --git a/sign/pdfcatalog.go b/sign/pdfcatalog.go index f027527..4f7a42f 100644 --- a/sign/pdfcatalog.go +++ b/sign/pdfcatalog.go @@ -30,7 +30,7 @@ func (context *SignContext) createCatalog() (catalog string, err error) { 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.SignData.ObjectId)) + " 0 R]" + catalog += " /Fields [" + strconv.Itoa(int(context.VisualSignData.ObjectId)) + " 0 R]" if !context.SignData.Signature.Approval { catalog += " /NeedAppearances false" @@ -44,12 +44,11 @@ func (context *SignContext) createCatalog() (catalog string, err error) { catalog += " >>" - // @todo: what do these do? if !context.SignData.Signature.Approval { if context.SignData.Signature.CertType > 0 { - //catalog += " /Perms << /DocMDP " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R >>"; + catalog += " /Perms << /DocMDP " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R >>"; } else { - //catalog += " /Perms << /UR3 " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R >>"; + catalog += " /Perms << /UR3 " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R >>"; } } diff --git a/sign/pdfsignature.go b/sign/pdfsignature.go index 0f19ff4..9317333 100644 --- a/sign/pdfsignature.go +++ b/sign/pdfsignature.go @@ -29,7 +29,7 @@ func (context *SignContext) createSignaturePlaceholder() (signature string, byte // Create a placeholder for the actual signature content, we wil replace it later. signature += " /Contents<" + strings.Repeat("0", int(signatureMaxLength)) + ">" - if context.SignData.Signature.Approval { + if !context.SignData.Signature.Approval { signature += " /Reference [" // array of signature reference dictionaries signature += " << /Type /SigRef" if context.SignData.Signature.CertType > 0 { @@ -77,9 +77,6 @@ func (context *SignContext) createSignature() ([]byte, error) { io.Copy(sign_buf, context.OutputFile) file_content := sign_buf.Bytes() - // Remove trailing newline. - file_content = file_content[:len(file_content)-1] - // Collect the parts to sign. sign_content := make([]byte, 0) sign_content = append(sign_content, file_content[context.ByteRangeValues[0]:(context.ByteRangeValues[0]+context.ByteRangeValues[1])]...) @@ -111,7 +108,7 @@ func (context *SignContext) replaceSignature() error { dst := make([]byte, hex.EncodedLen(len(signature))) hex.Encode(dst, signature) - context.OutputFile.WriteAt(dst, context.ByteRangeValues[0]+context.ByteRangeValues[1]) + context.OutputFile.WriteAt(dst, context.ByteRangeValues[0] + context.ByteRangeValues[1] + 1) return nil } diff --git a/sign/pdftrailer.go b/sign/pdftrailer.go index 7332feb..ec06c18 100644 --- a/sign/pdftrailer.go +++ b/sign/pdftrailer.go @@ -19,7 +19,7 @@ func (context *SignContext) writeTrailer() error { new_root := "Root " + strconv.FormatInt(int64(context.CatalogData.ObjectId), 10) + " 0 R" size_string := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount, 10) - new_size := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount+2, 10) + new_size := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount+3, 10) trailer_string := string(trailer_buf) trailer_string = strings.Replace(trailer_string, root_string, new_root, -1) @@ -36,7 +36,7 @@ func (context *SignContext) writeTrailer() error { } // Write PDF ending. - if _, err := context.OutputFile.Write([]byte("%%EOF\n")); err != nil { + if _, err := context.OutputFile.Write([]byte("%%EOF")); err != nil { return err } diff --git a/sign/pdfvisualsignature.go b/sign/pdfvisualsignature.go new file mode 100644 index 0000000..b55296a --- /dev/null +++ b/sign/pdfvisualsignature.go @@ -0,0 +1,44 @@ +package sign + +import ( + "errors" + "strconv" +) + +func (context *SignContext) createVisualSignature() (visual_signature string, err error) { + visual_signature = strconv.Itoa(int(context.VisualSignData.ObjectId)) + " 0 obj\n" + visual_signature += "<< /Type /Annot" + visual_signature += " /Subtype /Widget" + visual_signature += " /Rect [0 0 0 0]" + + root := context.PDFReader.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 { + 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" + + page := root.Key("Pages").Key("Kids").Index(0).GetPtr() + visual_signature += " /P " + strconv.Itoa(int(page.GetID())) + " " + strconv.Itoa(int(page.GetGen())) + " R" + + visual_signature += " /F 4" + visual_signature += " /FT /Sig" + visual_signature += " /T " + pdfString("Signature") + visual_signature += " /Ff 0" + visual_signature += " /V " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R" + + visual_signature += " >>" + visual_signature += "\nendobj\n" + + return visual_signature, nil +} diff --git a/sign/pdfxref.go b/sign/pdfxref.go index cf2c66f..7c8fd1e 100644 --- a/sign/pdfxref.go +++ b/sign/pdfxref.go @@ -22,7 +22,7 @@ func (context *SignContext) writeXref() error { func (context *SignContext) writeXrefTable() error { xref_size := "xref\n0 " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount, 10) - new_xref_size := "xref\n0 " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount+2, 10) + new_xref_size := "xref\n0 " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount+3, 10) if _, err := context.OutputFile.Write([]byte(new_xref_size)); err != nil { return err @@ -34,7 +34,16 @@ func (context *SignContext) writeXrefTable() error { } // Create the new catalog xref line. - catalog_object_start_position := strconv.FormatInt(context.PDFReader.XrefInformation.StartPos, 10) + visual_signature_object_start_position := strconv.FormatInt(context.Filesize, 10) + 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 { + return err + } + + // Create the new catalog xref line. + catalog_object_start_position := strconv.FormatInt(context.Filesize+context.VisualSignData.Length, 10) catalog_xref_line := leftPad(catalog_object_start_position, "0", 10-len(catalog_object_start_position)) + " 00000 n\n" // Write the new catalog xref line. @@ -43,7 +52,7 @@ func (context *SignContext) writeXrefTable() error { } // Create the new signature xref line. - signature_object_start_position := strconv.FormatInt(context.PDFReader.XrefInformation.StartPos+context.CatalogData.Length, 10) + signature_object_start_position := strconv.FormatInt(context.Filesize+context.VisualSignData.Length+context.CatalogData.Length, 10) signature_xref_line := leftPad(signature_object_start_position, "0", 10-len(signature_object_start_position)) + " 00000 n\n" // Write the new signature xref line. diff --git a/sign/sign.go b/sign/sign.go index 51b4c8a..fba1e71 100644 --- a/sign/sign.go +++ b/sign/sign.go @@ -7,6 +7,7 @@ import ( "time" "bitbucket.org/digitorus/pdf" + "io" ) type CatalogData struct { @@ -23,6 +24,11 @@ type SignData struct { CertificateChain []*x509.Certificate } +type VisualSignData struct { + ObjectId uint32 + Length int64 +} + type SignDataSignature struct { Approval bool CertType uint32 @@ -38,10 +44,12 @@ type SignDataSignatureInfo struct { } type SignContext struct { + Filesize int64 InputFile *os.File OutputFile *os.File SignData SignData CatalogData CatalogData + VisualSignData VisualSignData PDFReader *pdf.Reader NewXrefStart int64 ByteRangeStartByte int64 @@ -73,15 +81,20 @@ func SignFile(input string, output string, sign_data SignData) error { return err } - sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 1 + sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 2 + // We do size+1 because we insert a newline. context := SignContext{ + Filesize: size + 1, PDFReader: rdr, InputFile: input_file, OutputFile: output_file, - CatalogData: CatalogData{ + VisualSignData: VisualSignData{ ObjectId: uint32(rdr.XrefInformation.ItemCount), }, + CatalogData: CatalogData{ + ObjectId: uint32(rdr.XrefInformation.ItemCount) + 1, + }, SignData: sign_data, } @@ -94,8 +107,30 @@ func SignFile(input string, output string, sign_data SignData) error { } func (context *SignContext) SignPDF() error { - // Write the PDF file to the output up til the xref. - if err := writePartFromSourceFileToTargetFile(context.InputFile, context.OutputFile, 0, context.PDFReader.XrefInformation.StartPos); err != nil { + // Copy old file into new file. + if _, err := io.Copy(context.OutputFile, context.InputFile); err != nil { + return err + } + + err := context.OutputFile.Sync() + if err != nil { + return err + } + + // File always needs an empty line after %%EOF. + if _, err := context.OutputFile.Write([]byte("\n")); err != nil { + return err + } + + visual_signature, err := context.createVisualSignature() + if err != nil { + return err + } + + context.VisualSignData.Length = int64(len(visual_signature)) + + // Write the new catalog object. + if _, err := context.OutputFile.Write([]byte(visual_signature)); err != nil { return err } @@ -115,8 +150,8 @@ func (context *SignContext) SignPDF() error { signature_object, byte_range_start_byte, signature_contents_start_byte := context.createSignaturePlaceholder() // Positions are relative to old start position of xref table. - byte_range_start_byte += context.PDFReader.XrefInformation.StartPos + int64(len(catalog)) - signature_contents_start_byte += context.PDFReader.XrefInformation.StartPos + int64(len(catalog)) + byte_range_start_byte += context.Filesize + int64(len(catalog)) + int64(len(visual_signature)) + signature_contents_start_byte += context.Filesize + int64(len(catalog)) + int64(len(visual_signature)) context.ByteRangeStartByte = byte_range_start_byte context.SignatureContentsStartByte = signature_contents_start_byte @@ -127,7 +162,7 @@ func (context *SignContext) SignPDF() error { } // Calculate the new start position of the xref table. - context.NewXrefStart = context.PDFReader.XrefInformation.StartPos + int64(len(signature_object)) + int64(len(catalog)) + context.NewXrefStart = context.Filesize + int64(len(signature_object)) + int64(len(catalog)) + int64(len(visual_signature)) if err := context.writeXref(); err != nil { return err @@ -145,5 +180,10 @@ func (context *SignContext) SignPDF() error { return err } + err = context.OutputFile.Sync() + if err != nil { + return err + } + return nil } diff --git a/verify/verify.go b/verify/verify.go index 5533e17..a993865 100644 --- a/verify/verify.go +++ b/verify/verify.go @@ -14,6 +14,7 @@ import ( "github.com/digitorus/pkcs7" "github.com/digitorus/timestamp" "golang.org/x/crypto/ocsp" + "go/src/log" ) type RevocationInfoArchival struct { @@ -70,9 +71,11 @@ func Verify(file *os.File) (apiResp *Response, err error) { finfo, _ := file.Stat() size := finfo.Size() + file.Seek(0, 0) + rdr, err := pdf.NewReader(file, size) if err != nil { - return nil, fmt.Errorf("Failed to open file") + return nil, fmt.Errorf("Failed to open file: %v", err) } // AcroForm will contain a SigFlags value if the form contains a digital signature @@ -129,6 +132,12 @@ func Verify(file *os.File) (apiResp *Response, err error) { apiResp.Error = fmt.Sprintln("Failed to get ByteRange:", i, err) } + log.Println(v.Key("ByteRange").Index(i-1).Int64()) + log.Println(v.Key("ByteRange").Index(i).Int64()) + log.Println(string(content[0:60])) + log.Println(string(content[len(content)-60:len(content)])) + log.Println(len(content)) + p7.Content = append(p7.Content, content...) } @@ -180,8 +189,10 @@ func Verify(file *os.File) (apiResp *Response, err error) { signer.ValidSignature = true signer.TrustedIssuer = false } + log.Println("Invalid sig") apiResp.Error = fmt.Sprintln("Failed to verify signature:", err) } else { + log.Println("Valid sig") signer.ValidSignature = true signer.TrustedIssuer = true }