diff --git a/README.adoc b/README.adoc index 6e7acd3..595ac4b 100644 --- a/README.adoc +++ b/README.adoc @@ -9,8 +9,6 @@ the Cairo library. As such, it currently comes with some restrictions: overwritten * the document may not employ cross-reference streams, or must constitute a hybrid-reference file at least - * the signature may take at most 4 kilobytes as a compile-time limit, - which should be enough space even for one intermediate certificate The signature is attached to the first page and has no appearance. @@ -37,6 +35,10 @@ Usage $ ./pdf-simple-sign document.pdf document.signed.pdf KeyAndCerts.p12 password +If the signature doesn't fit within the default reservation of 4 kibibytes, +you might need to adjust it using the `-r` option, or throw out any unnecessary +intermediate certificates. + Contributing and Support ------------------------ Use https://git.janouch.name/p/pdf-simple-sign to report bugs, request features, diff --git a/cmd/pdf-simple-sign/main.go b/cmd/pdf-simple-sign/main.go index 3257a66..5141a12 100644 --- a/cmd/pdf-simple-sign/main.go +++ b/cmd/pdf-simple-sign/main.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2018, Přemysl Eric Janouch +// Copyright (c) 2018 - 2020, Přemysl Eric Janouch // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted. @@ -20,8 +20,9 @@ import ( "flag" "fmt" "io/ioutil" - "janouch.name/pdf-simple-sign/pdf" "os" + + "janouch.name/pdf-simple-sign/pdf" ) // #include @@ -39,10 +40,13 @@ func die(status int, format string, args ...interface{}) { } func usage() { - die(1, "Usage: %s [-h] INPUT-FILENAME OUTPUT-FILENAME "+ + die(1, "Usage: %s [-h] [-r RESERVATION] INPUT-FILENAME OUTPUT-FILENAME "+ "PKCS12-PATH PKCS12-PASS", os.Args[0]) } +var reservation = flag.Int( + "r", 4096, "signature reservation as a number of bytes") + func main() { flag.Usage = usage flag.Parse() @@ -51,7 +55,7 @@ func main() { } inputPath, outputPath := flag.Arg(0), flag.Arg(1) - pdfDocument, err := ioutil.ReadFile(inputPath) + doc, err := ioutil.ReadFile(inputPath) if err != nil { die(1, "%s", err) } @@ -63,10 +67,10 @@ func main() { if err != nil { die(3, "%s", err) } - if pdfDocument, err = pdf.Sign(pdfDocument, key, certs); err != nil { + if doc, err = pdf.Sign(doc, key, certs, *reservation); err != nil { die(4, "error: %s", err) } - if err = ioutil.WriteFile(outputPath, pdfDocument, 0666); err != nil { + if err = ioutil.WriteFile(outputPath, doc, 0666); err != nil { die(5, "%s", err) } } diff --git a/pdf-simple-sign.cpp b/pdf-simple-sign.cpp index 6d01211..34acc9f 100644 --- a/pdf-simple-sign.cpp +++ b/pdf-simple-sign.cpp @@ -40,6 +40,7 @@ // ------------------------------------------------------------------------------------------------- using uint = unsigned int; +using ushort = unsigned short; static std::string concatenate(const std::vector& v, const std::string& delim) { std::string res; @@ -831,7 +832,7 @@ error: /// https://www.adobe.com/devnet-docs/acrobatetk/tools/DigSig/Acrobat_DigitalSignatures_in_PDF.pdf /// https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf /// https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PPKAppearances.pdf -static std::string pdf_sign(std::string& document) { +static std::string pdf_sign(std::string& document, ushort reservation) { pdf_updater pdf(document); auto err = pdf.initialize(); if (!err.empty()) @@ -855,7 +856,7 @@ static std::string pdf_sign(std::string& document) { pdf.document.append((byterange_len = 32 /* fine for a gigabyte */), ' '); pdf.document.append("\n /Contents <"); sign_off = pdf.document.size(); - pdf.document.append((sign_len = 8192 /* certificate, digest, encrypted digest, ... */), '0'); + pdf.document.append((sign_len = reservation * 2), '0'); pdf.document.append("> >>"); // We actually need to exclude the hexstring quotes from signing @@ -945,15 +946,18 @@ static void die(int status, const char* format, ...) { int main(int argc, char* argv[]) { auto invocation_name = argv[0]; auto usage = [=]{ - die(1, "Usage: %s [-h] INPUT-FILENAME OUTPUT-FILENAME PKCS12-PATH PKCS12-PASS", + die(1, "Usage: %s [-h] [-r RESERVATION] INPUT-FILENAME OUTPUT-FILENAME PKCS12-PATH PKCS12-PASS", invocation_name); }; static struct option opts[] = { {"help", no_argument, 0, 'h'}, + {"reservation", required_argument, 0, 'r'}, {nullptr, 0, 0, 0}, }; + // Reserved space in bytes for the certificate, digest, encrypted digest, ... + long reservation = 4096; while (1) { int option_index = 0; auto c = getopt_long(argc, const_cast(argv), @@ -961,9 +965,16 @@ int main(int argc, char* argv[]) { if (c == -1) break; + char* end = nullptr; switch (c) { - case 'h': usage(); break; - default: usage(); + case 'r': + errno = 0, reservation = strtol(optarg, &end, 10); + if (errno || *end || reservation <= 0 || reservation > USHRT_MAX) + die(1, "%s: must be a positive number", optarg); + break; + case 'h': + default: + usage(); } } @@ -990,7 +1001,7 @@ int main(int argc, char* argv[]) { die(1, "%s: %s", input_path, strerror(errno)); } - auto err = pdf_sign(pdf_document); + auto err = pdf_sign(pdf_document, ushort(reservation)); if (!err.empty()) { die(2, "Error: %s", err.c_str()); } diff --git a/pdf/pdf.go b/pdf/pdf.go index a6f1ae8..f871c70 100644 --- a/pdf/pdf.go +++ b/pdf/pdf.go @@ -1115,12 +1115,14 @@ func FillInSignature(document []byte, signOff, signLen int, // There must be at least one certificate, matching the private key. // The certificates must form a chain. // +// A good default for the reservation is around 4096 (the value is in bytes). +// // The presumption here is that the document is valid and that it doesn't // employ cross-reference streams from PDF 1.5, or at least constitutes // a hybrid-reference file. The results with PDF 2.0 (2017) are currently // unknown as the standard costs money. -func Sign(document []byte, - key crypto.PrivateKey, certs []*x509.Certificate) ([]byte, error) { +func Sign(document []byte, key crypto.PrivateKey, certs []*x509.Certificate, + reservation int) ([]byte, error) { pdf, err := NewUpdater(document) if err != nil { return nil, err @@ -1152,7 +1154,7 @@ func Sign(document []byte, buf.WriteString("\n /Contents <") signOff = buf.Len() - signLen = 8192 // cert, digest, encrypted digest, ... + signLen = reservation * 2 // cert, digest, encrypted digest, ... buf.Write(bytes.Repeat([]byte{'0'}, signLen)) buf.WriteString("> >>")