Giter Club home page Giter Club logo

sapp's Introduction

SAPP - Simple and Agnostic PDF Document Parser

SAPP stands for Simple and Agnostic PDF Parser and it makes what is name says: parsing PDF files. It also enables other cool features such as rebuilding documents (to make the content more clear or compact) or digitally signing documents.

SAPP is agnostic because it does not care of composing PDF documents (e.g. adding pages, updating pages, etc.). Instead, its aim is to be somehow a backend to parse an existing PDF document and to manipulate the objects included on it, or to create new ones.

The way of working with SAPP can be seen in the function to sign the document: it is an independent function that adds and manipulates the PDF objects contained in the document.

Some of the features of SAPP:

  1. Supports 1.4 PDF documents
  2. Supports many features of 1.5 PDF and later documents (including cross-reference streams)
  3. Works using incremental versions
  4. Works for rebuilding documents to flatten versions (older version are dropped)
  5. Signature of documents using the Acrobat workflow (and obtain the green checkmark).
  6. Others.

1. Why SAPP

I created SAPP because I wanted to programmatically sign documents, including multiple signatures.

I tried tcpdf along with FPDI, but the results are not those expected. When importing a document using FPDI, the result is a new document with the pages of the original one, not the original document. So if the original document was signed, those signatures were lost.

I read about SetaPDF-Signer, but it cannot be used for free. I also inspected some CLI tools such as PortableSigner, but (1) it was Java based (and I really don't like java) and (2) it depends on iText and other libraries which don't seem to be free at this moment.

At the end I got the PDF 1.7 definition, and I learned about incremental PDF documents and its utility to include multiple signature in documents.

2. Using SAPP

2.1 Using composer and packagist

To use SAPP in your projects, you just need to use composer:

$ composer require ddn/sapp:dev-main

Then, a vendor folder will be created, and then you can simply include the main file and use the classes:

use ddn\sapp\PDFDoc;

require_once('vendor/autoload.php');

$obj = PDFDoc::from_string(file_get_contents("/path/to/my/pdf/file.pdf"));
echo $obj->to_pdf_file_s(true);

2.2 Getting the source code

Altenatively you can clone the repository and use composer:

$ git clone https://github.com/dealfonso/sapp
$ cd sapp
$ composer dump-autoload
$ php pdfrebuild.php examples/testdoc.pdf > testdoc-rebuilt.pdf

Then you will be ready to include the main file and use the classes.

3. Examples

In the root folder of the source code you can find two simple examples:

  1. pdfrebuild.php: this example gets a PDF file, loads it and rebuilds it to make every PDF object to be in order, and also reducing the amount of text to define the document.
  2. pdfsign.php: this example gets a PDF file and digitally signs it using a pkcs12 (pfx) certificate.
  3. pdfsigni.php: this example gets a PDF file and digitally signs it using a pkcs12 (pfx) certificate, and adds an image that makes visible the signature in the document.
  4. pdfsignx.php: alternate example that gets a PDF file and digitally signs it using a pkcs12 (pfx) certificate, and adds an image that makes visible the signature in the document.
  5. pdfcompare.php: this example compares two PDF files and checks the differences between them (object by object, field by field).

3.1. Rebuild PDF files with pdfrebuild.php

Once cloned the repository and generated the autoload content, it is possible to run the example:

$ php pdfrebuild.php examples/testdoc.pdf > testdoc-rebuilt.pdf

The result is a more ordered PDF document which is (problably) smaller. (e.g. rebuilding examples/testdoc.pdf provides a 50961 bytes document while the original was 51269 bytes).

$ ls -l examples/testdoc.pdf
-rw-r--r--@ 1 calfonso  staff  51269  5 nov 14:01 examples/testdoc.pdf
$ ls -l testdoc-rebuilt.pdf
-rw-r--r--@ 1 calfonso  staff  50961  5 nov 14:22 testdoc-rebuilt.pdf

And the xref table looks significantly better ordered:

$ cat examples/testdoc.pdf
...
xref
0 39
0000000000 65535 f
0000049875 00000 n
0000002955 00000 n
0000005964 00000 n
0000000022 00000 n
0000002935 00000 n
0000003059 00000 n
0000005928 00000 n
...
$ cat testdoc-rebuilt.pdf
...
xref
0 8
0000000000 65535 f
0000000009 00000 n
0000000454 00000 n
0000000550 00000 n
0000000623 00000 n
0000003532 00000 n
0000003552 00000 n
0000003669 00000 n
...

The code:

use ddn\sapp\PDFDoc;

require_once('vendor/autoload.php');

if ($argc !== 2)
    fwrite(STDERR, sprintf("usage: %s <filename>", $argv[0]));
else {
    if (!file_exists($argv[1]))
        fwrite(STDERR, "failed to open file " . $argv[1]);
    else {
        $obj = PDFDoc::from_string(file_get_contents($argv[1]));

        if ($obj === false)
            fwrite(STDERR, "failed to parse file " . $argv[1]);
        else
            echo $obj->to_pdf_file_s(true);
    }
}

3.2. Sign PDF files with pdfsign.php

To sign a PDF document, it is possible to use the script pdfsign.php:

$ php pdfsign.php examples/testdoc.pdf caralla.p12 > testdoc-signed.pdf

And now the document is signed. And if you wanted to add a second signature, it is as easy as signing the resulting document again:

$ php pdfsign.php testdoc-signed.pdf user.p12 > testdoc-resigned.pdf

The code: (full working example)

use ddn\sapp\PDFDoc;

require_once('vendor/autoload.php');

if ($argc !== 3)
    fwrite(STDERR, sprintf("usage: %s <filename> <certfile>", $argv[0]));
else {
    if (!file_exists($argv[1]))
        fwrite(STDERR, "failed to open file " . $argv[1]);
    else {
        // Silently prompt for the password
        fwrite(STDERR, "Password: ");
        system('stty -echo');
        $password = trim(fgets(STDIN));
        system('stty echo');
        fwrite(STDERR, "\n");

        $file_content = file_get_contents($argv[1]);
        $obj = PDFDoc::from_string($file_content);
        
        if ($obj === false)
            fwrite(STDERR, "failed to parse file " . $argv[1]);
        else {
            if (!$obj->set_signature_certificate($argv[2], $password)) {
                fwrite(STDERR, "the certificate is not valid");
            } else {
                $docsigned = $obj->to_pdf_file_s();
                if ($docsigned === false)
                    fwrite(STDERR, "could not sign the document");
                else
                    echo $docsigned;
            }
        }
    }
}

3.3. Sign PDF files with an image, using pdfsignx.php

To sign a PDF document that contains an image associated to the signature, it is possible to use the script pdfsignx.php:

$ php pdfsignx.php examples/testdoc.pdf "https://www.google.es/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png" caralla.p12 > testdoc-signed.pdf

And now the document is signed, and a cool image appears. If you wanted to add a second signature, it is as easy as signing the resulting document again.

The code: (full working example)

use ddn\sapp\PDFDoc;

require_once('vendor/autoload.php');

if ($argc !== 4)
    fwrite(STDERR, sprintf("usage: %s <filename> <image> <certfile>", $argv[0]));
else {
    if (!file_exists($argv[1]))
        fwrite(STDERR, "failed to open file " . $argv[1]);
    else {
        fwrite(STDERR, "Password: ");
        system('stty -echo');
        $password = trim(fgets(STDIN));
        system('stty echo');
        fwrite(STDERR, "\n");

        $file_content = file_get_contents($argv[1]);
        $obj = PDFDoc::from_string($file_content);
        
        if ($obj === false)
            fwrite(STDERR, "failed to parse file " . $argv[1]);
        else {
            $signedDoc = $obj->sign_document($argv[3], $password, 0, $argv[2]);
            if ($signedDoc === false) {
                fwrite(STDERR, "failed to sign the document");
            } else {
                $docsigned = $signedDoc->to_pdf_file_s();
                if ($docsigned === false)
                    fwrite(STDERR, "could not sign the document");
                else
                    echo $docsigned;
            }
        }
    }
}

3.4. Sign PDF files with an image, using pdfsigni.php

To sign a PDF document that contains an image associated to the signature, it is possible to use the script pdfsigni.php:

$ php pdfsigni.php examples/testdoc.pdf "https://www.google.es/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png" caralla.p12 > testdoc-signed.pdf

And now the document is signed, and a cool image appears. If you wanted to add a second signature, it is as easy as signing the resulting document again.

The main difference with the previous code is that in this case, the signature is considered a stage of editing the document, but it will not be signed until the document is generated. So it is possible to edit the document (i.e. add text or images) and defer the signature until the document is finally generated.

The code:

* the code related to the position in which the signature and the image appear has been ommited, but can be seen in file pdfsigni.php.

...
$obj->set_signature_appearance(0, [ $p_x, $p_y, $p_x + $i_w, $p_y + $i_h ], $image);
if (!$obj->set_signature_certificate($argv[3], $password)) {
    fwrite(STDERR, "the certificate is not valid");
} else {
    $docsigned = $obj->to_pdf_file_s();
    if ($docsigned === false)
        fwrite(STDERR, "could not sign the document");
    else
        echo $docsigned;
}
...

3.5. Decompress the streams of any object with pdfdeflate.php

This is an example of how to manipulate the objects in a document. The example walks over any object in the document, obtains its stream deflated, removes the filter (i.e. compression method), and restores it to the document. So that the objects are not compressed anymore (e.g. you can read in plain text the commands used to draw the elements).

The code:

foreach ($obj->get_object_iterator() as $oid => $object) {
    if ($object === false)
        continue;
    if ($object["Filter"] == "/FlateDecode") {
        /* Getting the stream with raw flag set to "false" will process the stream prior to returning it */
        $stream = $object->get_stream(false);
        if ($stream !== false) {
            unset($object["Filter"]);
            /* Setting the stream with raw flag set to "false" will process the stream, although in this case it is not important, because we removed the filter.*/
            $object->set_stream($stream, false);
            $obj->add_object($object);
        }
    }
}
echo $obj->to_pdf_file_s(true);

4. Limitations

At this time, the main limitations are:

  • Basic support for non-zero generation pdf objects: they are uncommon, but according to the definition of the PDF structure, they are possible. If you find one non-zero generation object please send me the document and I'll try to support it.
  • Not dealing with encrypted documents.
  • Other limitations, for sure :)

5. Future work

My idea is to provide support for other cool features:

  1. Support for TSA timestamping
  2. Include document protection (i.e. lock documents)
  3. Document encryption (http://www.fpdf.org/en/script/script37.php)
  4. Try to provide support to write some text

6. Attributions

  1. The mechanism for calculating the signature hash is heavily inspired in tcpdf.
  2. Reading jpg and png files has been taken from fpdf.

sapp's People

Contributors

aphoe avatar cesarreyes3 avatar dealfonso avatar erikn69 avatar hidasw avatar malamalca avatar marclaporte avatar mikocevar avatar parallels999 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sapp's Issues

Can it save changes incrementally?

Good day, I am reaching out to ask if it is possible to add images to pdf and save the changes incrementally. Is that possible with this library?
Your response will be highly appreciated. Thank you!

Option to not load whole PDF to memory

Hi, I am in need to signing pretty big PDFs ranging from 10MB to - 1GB (thousands of pages) and currently it's not really possible, since they all have to be loaded to memory. Any possibility to make it stream the PDF content and modify/write on the go? Right now I have to split the PDF and sign each page individually. Client then receives huge zip with thousands of small PDFs... since joining them back again invalidates the signature

Some signed documents can't be signed again

Hi, I have some signed PDFs that can not be signed again with this tool.

Error info at C:\\xampp7\\htdocs\\webapp\\htdocs\\custom\\vendor\\ddn\\sapp\\src\\PDFUtilFnc.php:637: object is not valid: 18
Warning info at C:\\xampp7\\htdocs\\webapp\\htdocs\\custom\\vendor\\ddn\\sapp\\src\\PDFDoc.php:972: root object does not exist, so cannot get information about pages
Error info at C:\\xampp7\\htdocs\\webapp\\htdocs\\custom\\vendor\\ddn\\sapp\\src\\PDFUtilFnc.php:637: object is not valid: 18
Error info at C:\\xampp7\\htdocs\\webapp\\htdocs\\custom\\vendor\\ddn\\sapp\\src\\PDFDoc.php:546: invalid root object
Error info at C:\\xampp7\\htdocs\\webapp\\htdocs\\custom\\vendor\\ddn\\sapp\\src\\PDFUtilFnc.php:637: object is not valid: 18
Error info at C:\\xampp7\\htdocs\\webapp\\htdocs\\custom\\vendor\\ddn\\sapp\\src\\PDFDoc.php:367: invalid root object
Error info at C:\\xampp7\\htdocs\\webapp\\htdocs\\custom\\vendor\\ddn\\sapp\\src\\PDFDoc.php:714: could not generate the signed document

Here's the PDF's cat, I deleted everything between >>stream XXXXXX endstream

%PDF-1.3
1 0 obj
[/PDF /Text /ImageB /ImageC /ImageI]
endobj
8 0 obj
<< /Length 2924 /Filter /FlateDecode 
endobj
2 0 obj
<< /Type /Page /Parent 9 0 R /MediaBox [0 0 595.276 841.89] /Contents 8 0 R /Resources << /ProcSet 1 0 R /XObject << /Im7 7 0 R >> /Font << /F3 3 0 R /F4 4 0 R /F5 5 0 R /F6 6 0 R >> >> >>
endobj
7 0 obj
<< /Type /XObject /Subtype /Image /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /FlateDecode  /Width 176 /Height 63 /Length 4356 
endobj
10 0 obj
<< /Filter /FlateDecode  /Length 76495 /Length1 350844 
endobj
11 0 obj
<< /Filter /FlateDecode  /Length 246 /Length1 402 
endobj
12 0 obj
<< /Filter /FlateDecode  /Length 100604 /Length1 393196 
endobj
13 0 obj
<< /Filter /FlateDecode  /Length 279 /Length1 492 
endobj
3 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>
endobj
4 0 obj
<< /Type /Font /Subtype /Type0 /BaseFont /ABCDEE+Arial,Bold /Encoding /Identity-H /DescendantFonts [14 0 R] /ToUnicode 11 0 R >>
endobj
14 0 obj
<< /Type /Font /Subtype /CIDFontType2 /BaseFont /ABCDEE+Arial,Bold /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /W [3 [277.832] 29 [333.008] 36 [722.168] 38 [722.168] 39 [722.168] 54 [666.992] 68 [556.152] 70 [556.152] 71 [610.84] 72 [556.152] 76 [277.832] 79 [277.832] 81 [610.84] 82 [610.84] 83 [610.84] 85 [389.16] 86 [556.152] 87 [333.008] 89 [556.152] 121 [610.84] ] /FontDescriptor 15 0 R >>
endobj
15 0 obj
<< /Type /FontDescriptor /Ascent 728 /CapHeight 0 /Descent -210 /Flags 32 /FontBBox [ -628 -376 2000 1056 ] /FontName /ABCDEE+Arial,Bold /ItalicAngle 0 /StemV 0  /FontFile2 10 0 R >>
endobj
5 0 obj
<< /Type /Font /Subtype /Type0 /BaseFont /ABCDEE+Arial /Encoding /Identity-H /DescendantFonts [16 0 R] /ToUnicode 13 0 R >>
endobj
16 0 obj
<< /Type /Font /Subtype /CIDFontType2 /BaseFont /ABCDEE+Arial /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /W [3 [277.832] 11 [333.008] 12 [333.008] 15 [277.832] 16 [333.008] 17 [277.832] 18 [277.832] 19 [556.152] 20 [556.152] 21 [556.152] 22 [556.152] 23 [556.152] 24 [556.152] 25 [556.152] 26 [556.152] 27 [556.152] 28 [556.152] 36 [666.992] 37 [666.992] 38 [722.168] 39 [722.168] 40 [666.992] 41 [610.84] 43 [722.168] 44 [277.832] 45 [500] 47 [556.152] 48 [833.008] 49 [722.168] 50 [777.832] 51 [666.992] 53 [722.168] 54 [666.992] 55 [610.84] 56 [722.168] 57 [666.992] 68 [556.152] 69 [556.152] 70 [500] 71 [556.152] 72 [556.152] 74 [556.152] 75 [556.152] 76 [222.168] 77 [222.168] 79 [222.168] 80 [833.008] 81 [556.152] 82 [556.152] 83 [556.152] 84 [556.152] 85 [333.008] 86 [500] 87 [277.832] 88 [556.152] 89 [500] 91 [500] 92 [500] 93 [500] 105 [556.152] 116 [277.832] 121 [556.152] 157 [370.117] 158 [365.234] 172 [666.992] 257 [333.008] ] /FontDescriptor 17 0 R >>
endobj
17 0 obj
<< /Type /FontDescriptor /Ascent 728 /CapHeight 0 /Descent -210 /Flags 32 /FontBBox [ -665 -325 2000 1040 ] /FontName /ABCDEE+Arial /ItalicAngle 0 /StemV 0  /FontFile2 12 0 R >>
endobj
6 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>
endobj
9 0 obj
<< /Type /Pages /Kids [ 2 0 R ] /Count 1 >>
endobj
18 0 obj
<< /Type /Catalog /Pages 9 0 R >>
endobj
19 0 obj
<< /Title <>
/Author <>
/Subject <>
/Creator (Microsoft Reporting Services 15.0.0.0)
/Producer (Microsoft Reporting Services PDF Rendering Extension 15.0.0.0)
/CreationDate (D:20240530144215Z00'00')
>>
endobj
xref
0 20
0000000000 65535 f
0000000010 00000 n
0000003067 00000 n
0000185813 00000 n
0000185918 00000 n
0000186713 00000 n
0000188073 00000 n
0000003274 00000 n
0000000065 00000 n
0000188173 00000 n
0000007806 00000 n
0000084399 00000 n
0000084738 00000 n
0000185441 00000 n
0000186065 00000 n
0000186511 00000 n
0000186855 00000 n
0000187876 00000 n
0000188235 00000 n
0000188288 00000 n
trailer << /Size 20 /Root 18 0 R /Info 19 0 R >>
startxref
188515
%%EOF
21 0 obj<</F 132/V 20 0 R/T(Signature1)/Type/Annot/Rect[0 0 0 0]/FT/Sig/P 2 0 R/Subtype/Widget>>
endobj
20 0 obj<</Location(C/ Direccion)/Reason(EMPRESA, S.L.)/Contents <30821c5f06092a864886f70 [OMITTED] c6297fca64f326ea79b71eefb0d063875e1290a537d9336f299e8e5878ac0a6cee7c5974ea531efc1ba6f9276ffc0b8a0ceaafb6a7942bbd6ccf33a79b8106a4100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000>/Type/Sig/ByteRange [0 189214 203878 3959 ]                                                         /SubFilter/adbe.pkcs7.sha1/Filter/Adobe.PPKMS/M(D:20240530144215+00'00')/ContactInfo(Cristina)/Name(EMPRESA S.L.)>>
endobj
22 0 obj <</Type/Metadata/Subtype/XML/Length 2907>>stream
<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d' ?>
<x:xmpmeta xmlns:x='adobe:ns:meta/'>
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
<rdf:Description rdf:about='' xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:creator><rdf:Seq><rdf:li></rdf:li></rdf:Seq></dc:creator><dc:title></dc:title><dc:format>application/pdf</dc:format><dc:subject><rdf:Bag><rdf:li></rdf:li></rdf:Bag></dc:subject></rdf:Description>
<rdf:Description rdf:about='' xmlns:pdf="http://ns.adobe.com/pdf/1.3/"><pdf:Producer>Microsoft Reporting Services PDF Rendering Extension 15.0.0.0</pdf:Producer></rdf:Description>
<rdf:Description rdf:about='' xmlns:xmp="http://ns.adobe.com/xap/1.0/"><xmp:CreateDate>2024-05-30&apos;T&apos;14:42:15.000&apos;+00:00&apos;</xmp:CreateDate><xmp:CreatorTool>Microsoft Reporting Services 15.0.0.0</xmp:CreatorTool></rdf:Description>
</rdf:RDF></x:xmpmeta>
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
                                                                                                   
<?xpacket ends='w' ?>
endstream
endobj
18 0 obj<</Type/Catalog/Metadata 22 0 R/Pages 9 0 R/AcroForm<</SigFlags 3/Fields[21 0 R]>>>>
endobj
2 0 obj<</Type/Page/Contents 8 0 R/Resources<</XObject<</Im7 7 0 R>>/ProcSet 1 0 R/Font<</F4 4 0 R/F3 3 0 R/F6 6 0 R/F5 5 0 R>>>>/Parent 9 0 R/MediaBox[0 0 595.276 841.89]/Annots[21 0 R]>>
endobj
19 0 obj<</Title()/Creator(Microsoft Reporting Services 15.0.0.0)/Producer(Microsoft Reporting Services PDF Rendering Extension 15.0.0.0)/CreationDate(D:20240530144215Z00'00')/Author()/Subject()>>
endobj
xref
0 1
0000000000 65535 f 
2 1
0000207199 00000 n 
18 5
0000207099 00000 n 
0000207395 00000 n 
0000189106 00000 n 
0000189002 00000 n 
0000204116 00000 n 
trailer
<</Size 23/Info 19 0 R/Prev 188515/Root 18 0 R>>
startxref
207599
%%EOF

The PDF is an invoice so I can't upload it here.

error in the second signature

Hola , excelente proyecto ,
ayudame con algo, no se si lo estoy haciendo bien .

Intento tener una segunda firma en el documento que genere

php pdfsign.php testdoc-signed.pdf fq.p12 > testdoc-resigned.pdf

y me sale este error

Hello, excellent project,
help me with something, I don't know if I'm doing it right.

I try to have a second signature in the document that it generates

php pdfsign.php testdoc-signed.pdf fq.p12> testdoc-resigned.pdf

Error info at C:\laragon\www\sapp\src\PDFUtilFnc.php:377: PDF version string not found
failed to parse file testdoc-signed.pdf

error happened

Dear MR Alfonso
Excuse me again , How can I solve the problem like this in picture? and because of what it can be happened

thanks before for your attention

image

Client signing [enhancement]

Hi,

it would be great to have an ability to implement signing of pdfs on a client side.

  1. sapp should provide a hash of the pdf document to be signed [SAPP -> getPDFHash()]
  2. user signs the hash in browser via external code (javascript/php)
  3. the developer has a way to pass signature and algorithm to SAPP which generates ASN1/pkcs7 signature block manually and incorporates it in a pdf file

Would it be possible with SAPP?

On windows i get `openssl_pkcs7_sign(): Error getting private key`

On linux everything works, but on windows not
After debugging i found the problem here, $t_decpkey is null,
and openssl_error_string() is error:2006D080:BIO routines:BIO_new_file:no such file

sapp/src/PDFDoc.php

Lines 296 to 301 in 3d7f18a

$t_pkey = openssl_pkey_get_private($certificate["pkey"], $certpass);
if ($t_pkey === false)
return p_error("invalid private key");
openssl_pkey_export($t_pkey, $t_decpkey);
$certificate["pkey"] = $t_decpkey;

image
any ideas??

Got success message but didn't get the signed PDF

Hi!
I'm trying to sign a PDF document as a test, but my new PDF file don't have any signature.

This is my code:

<?php

require_once('vendor/autoload.php');

use ddn\sapp\PDFDoc;

$parameters = array(null, 'Google.pdf', 'certificate.pfx', 'mysignedfile.pdf');

if (!file_exists($parameters[1]))
    fwrite(STDERR, "failed to open file " . $parameters[1]);
else {
    $file_content = file_get_contents($parameters[1]);
    $obj = PDFDoc::from_string($file_content);
    
    if ($obj === false)
        fwrite(STDERR, "failed to parse file " . $parameters[1]);
    else {
        $obj->set_signature_certificate($parameters[3], "pfx_password");
        $docsigned = $obj->to_pdf_file_s();
        if ($docsigned === false)
            fwrite(STDERR, "could not sign the document");
        else
            try {
                $folder = 'downloads';

                if(!is_dir($folder)){
                   mkdir($folder,0775,true);
                } 
                
                if(file_put_contents('downloads/new_file.pdf', $docsigned)){
                    echo "Success";   
                } else {
                    echo "Error";
                }

                
            } catch(Exception $e){
                echo $e->getMessage();
            }
    }
}

When I run it using the URL I get the "Success" message and a new file is created, inside folder downloads, named "new_file.pdf". When I open this document using Adobe Acrobat for MacOS it looks like doesn't have any digital signature.

Where am I wrong?

Another weird situation, if I provide a wrong password for the PFX file I still get the success message.

Get signatures count method

I'm trying to add multiple signature images incrementally, there is a way to count previous signatures (for avoid overlapping images recalculating position on base of signature count)
Like

sapp/src/PDFDoc.php

Lines 89 to 91 in 01acefd

public function get_page_count() {
return count($this->_pages_info);
}

For example

public function get_signature_count() { 
     return count($this->_signatures); 
} 

[enhancement] Make it macroable

Support Closure Macros for common tasks

It could be really helpful

Examples:
https://github.com/spatie/macroable/blob/main/src/Macroable.php
https://github.com/illuminate/macroable/blob/master/Traits/Macroable.php
https://github.com/fawno/FPDF/blob/master/src/Traits/PDFMacroableTrait.php
mike42/escpos-php@dd16a6d

Code:

declare(strict_types=1);

use BadMethodCallException;
use Closure;

namespace ddn\sapp;

trait MacroableTrait {
    /**
     * The registered string macros.
     *
     * @var array
     */
    protected static $macros = [];

    /**
     * Register a custom macro.
     *
     * @param  string  $name
     * @param  callable  $macro
     * @return void
     */
    public static function macro($name, $macro)
    {
        static::$macros[$name] = $macro;
    }

    /**
     * Dynamically handle calls to the class.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     *
     * @throws \BadMethodCallException
     */
    public function __call($method, $parameters)
    {
        if (! isset(static::$macros[$name])) {
            throw new BadMethodCallException(sprintf(
                'Method %s::%s does not exist.', static::class, $method
            ));
        }

        $macro = static::$macros[$method];

        if ($macro instanceof Closure) {
            $macro = $macro->bindTo($this, static::class);
        }

        return $macro(...$parameters);
    }
}

Now we can customize class adding our code, for example:

PDFDoc::macro('set_signature_appearance_calculating', function($image, $page = 0) {
    $position = [ ];
    $image = $argv[2];
    $imagesize = @getimagesize($image);
    if ($imagesize === false) {
        fwrite(STDERR, "failed to open the image $image");
        return;
    }
    $pagesize = $this->get_page_size($page);
    if ($pagesize === false)
        return p_error("failed to get page size");

    $pagesize = explode(" ", $pagesize[0]->val());
    // Calculate the position of the image according to its size and the size of the page;
    //   the idea is to keep the aspect ratio and center the image in the page with a size
    //   of 1/3 of the size of the page.
    $p_x = intval("". $pagesize[0]);
    $p_y = intval("". $pagesize[1]);
    $p_w = intval("". $pagesize[2]) - $p_x;
    $p_h = intval("". $pagesize[3]) - $p_y;
    $i_w = $imagesize[0];
    $i_h = $imagesize[1];

    $ratio_x = $p_w / $i_w;
    $ratio_y = $p_h / $i_h;
    $ratio = min($ratio_x, $ratio_y);

    $i_w = ($i_w * $ratio) / 3;
    $i_h = ($i_h * $ratio) / 3;
    $p_x = $p_w / 3;
    $p_y = $p_h / 3;

    // Set the image appearance
    $this->set_signature_appearance($page, [ $p_x, $p_y, $p_x + $i_w, $p_y + $i_h ], $image);
});

Finally, we can reuse set_signature_appearance_calculating anywhere

$obj = PDFDoc::from_string($pdfPath);
$image= file_get_contents($imagePath);
// Set the image appearance and the certificate file
$obj->set_signature_appearance_calculating($image, $obj->get_page_count()-1);
$obj->set_signature_certificate($cert, $password);
$docsigned = $obj->to_pdf_file_s();
if ($docsigned)
    echo $docsigned;

Signature not valid, error on format or information within the signature

Hello, I've been trying to digitally sign PDFs generated with the mPDF library , I can successfully generate the PDF and sign it with the pdfsign.php example, but when I check the document with different programs (one of them being Adove Reader) I get a "Signature not valid, Contents illegal data". I've checked the signature itself, using a program to sign a document with it and works correctly.

Not signed PDF
Example of signed PDF

Thanks in advance.

This project is very good,But I have some questions

This is a great project. Thank you for your contribution
But I found this problem when I used it:
The signature is successful, and I can see the picture of my signature in the browser. But when I opened the PDF with Adobe Acrobat, I found only the signature, not the image.

What should I do?

Thanks.

Script change

Hi,

Can I hire you to adapt the script to my needs?

Sign pdf generated by Mpdf php lib

Hi!,

I'm trying to sign a pdf generated with Mpdf php library:

The set_signature_certificate method increases the filesize but the file doesn't show any certificate, I've been checking with "libreOffice Draw". Also, I've tried to rebuild the pdf with sapp before set signature and sign the pdf using terminal pdfsign.php script without any luck.

The only thing that works is to convert using pdftk after generate it with mpdf, and then sign with pdfsign.php script, but this is not a good workflow for me, ideally it should work only with php.

I'm attaching several example pdf, hope this helps to find the issue:

simple_pdf_not_serversigned.pdf
simple_pdf_not_serversigned_pdftk.pdf
simple_pdf_not_serversigned_pdftk_terminalsigned_OKSIGNED.pdf
simple_pdf_not_serversigned_terminalsigned.pdf
simple_pdf_rebuilded_and_serversigned.pdf
simple_pdf_rebuilded_and_serversigned_pdftk_INVALIDSIGNED.pdf
simple_pdf_serversigned.pdf
simple_pdf_serversigned_pdftk_INVALIDSIGNED.pdf

Many Thanks!

Falha ao carregar o documento PDF

Fiz o processo de clone do repositório e composer e executei o comando:

php pdfrebuild.php examples/testdoc.pdf > testdoc-rebuilt.pdf

Bem, o código parece esta executando corretamente e no retorno recebo "generating xref using classic xref...trailer", mas o arquivo consta como não sendo possível abrir, tentei executar os outros comando mas continua acontecendo o mesmo problema, mesmo utilizando um certificado próprio.

documento com problema: testdoc-rebuilt.pdf

Issue with Digital Signature Validation

I have recently encountered an issue with the digital signature validation of file.pdf. Upon checking the signature information, I found that there is a "Digest Mismatch" error, which indicates that the computed digest of the signed ranges does not match the actual digest of the file.

Signature Info of: file.pdf
Signature #1:

  • Signer Certificate Common Name: test
  • Signer full Distinguished Name: CN=test,OU=Test,O=test,L=Kochi,ST=Kerala,C=IN
  • Signing Time: Mar 15 2023 18:24:49
  • Signing Hash Algorithm: SHA-256
  • Signature Type: adbe.pkcs7.detached
  • Signed Ranges: [0 - 49517], [61261 - 61606]
  • Not total document signed
  • Signature Validation: Digest Mismatch.
    I believe this is abnormal and could indicate that the file has been tampered with or corrupted. I would appreciate it if someone could look into this issue and provide guidance on how to resolve it.

Write some content

Hello, is it possible to write some content after the set_signature_appearance?
I'm putting the signature image, but I wanted to write a text next to it.

Posição da assinatura

é possivel inserir a assinatura em uma posição da pagina?
$x
$y

tentei da forma abaixo mais nao funcionou para duas assinaturas, elas ficam em cima uma da outra.
$obj->set_signature_appearance($page,[120,60, $position_sig['y'], $position_sig['x']]);

Certificate chain not included in signature.

Trying to signing pdf with pkcs12 (CA chain included). But in the signed pdf result, theres no chain certificate shown, just single signer certificate shown.
Is there additional options or parameters to include chain certificates?

Can't add multiple signature appearance at once.

Hi, It would be great if I can add multiple signature appearance at once.

The problem is, if I add a signature, save the pdf and add another one, The pdf signature is now invalid because after signing, modification is not allowed. That's the whole point of signing a document with a digital signature.

"Headers already sent" issue with Yii2 framework

Hi!,

Just a solution for "Headers already sent" issue with Yii2 framework, it only needs an exit just after the "echo":

header("Content-type:application/pdf");
header("Content-Disposition:attachment;filename=$pdf_filename");
echo $obj->to_pdf_file_s(true);
exit();

Sign PDF version 1.7 files by creating a copy with mpdf library

First of all this library is AWESOME and works perfectly! Thanks a lot for this contribution as other alternatives in the web, as you described in the readme, suck.
We managed to sign PDF version 1.7 by using mpdf library and create a copy of it and then feed that to your library to sign it, in case this tip is useful to someone.

Thanks again for sharing your efforts,
Best
Francisco

Call to a member function set_signature_certificate() on bool

Hey guys :)

could you help me with this problem?

I am running the following code outside CLI in Laravel 9 with PHP 8.1.9 but I am getting:

"Call to a member function set_signature_certificate() on bool" in browser and
"Error info at /Users/martinkravec/git/angel/vendor/ddn/sapp/src/PDFUtilFnc.php:377: PDF version string not found
" in stderr.log.

use ddn\sapp\PDFDoc;

if (!defined('STDERR')) define('STDERR', fopen(__DIR__ . '/../../../storage/logs/stderr.log', 'wb'));

require_once(__DIR__ . '/../../../vendor/autoload.php');

$fileContent = file_get_contents('./file.pdf');
$obj = PDFDoc::from_string($fileContent);
$certificatePath = __DIR__ . '/../../../storage/app/test_certificate.pfx';

$password = '';
$obj->set_signature_certificate($certificatePath, $password);

When I do cat ./file.pdf, I get file content starting with:

HTTP/1.0 200 OK
Cache-Control:       no-cache, private
Content-Disposition: inline; filename="document.pdf"
Content-Type:        application/pdf
Date:                Sun, 28 Aug 2022 09:37:03 GMT

%PDF-1.7
1 0 obj
<< /Type /Catalog
/Outlines 2 0 R
/Pages 3 0 R >>
endobj
2 0 obj
<< /Type /Outlines /Count 0 >>
endobj
3 0 obj
<< /Type /Pages
/Kids [6 0 R
19 0 R
]
/Count 2
/Resources <<
/ProcSet 4 0 R
/Font << 
/F1 8 0 R
/F2 13 0 R
>>
/XObject << 
/I1 18 0 R
>>
>>
/MediaBox [0.000 0.000 595.280 841.890]
 >>
endobj
4 0 obj
[/PDF /Text /ImageC ]
endobj
5 0 obj
<<
/Producer (���d�o�m�p�d�f� �2�.�0�.�0� �+� �C�P�D�F)
/CreationDate (D:20220828113702+02'00')
/ModDate (D:20220828113702+02'00')
/Title (���D���v�k�y�.�P�D�F)
>>
endobj
6 0 obj
<< /Type /Page
/MediaBox [0.000 0.000 595.280 841.890]
/Parent 3 0 R
/Contents 7 0 R
>>
endobj
7 0 obj
<< /Filter /FlateDecode
/Length 919 >>
stream

Error on read some PDFs

Hello Carlos!

First, congratulations for the project, very easy to use!

However, I am having a problem reading some PDFs. Most of them get the error "failed to parse file"

One of the examples follows attaching :
CAPÍTULO 22.pdf

Error opening a pdf in sapp - PDFDoc.php:755: generating xref using classic xref...trailer

testdoc-signed.pdf

PS C:\wamp64\www\PROJETOSITE\2022\assinaturaDigital\assinaturaSAPP> php pdfsigni.php examples/testdoc.pdf "https://www.google.es/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png" JOSIVAN.pfx > testdoc-signed.pdf
Password: 'stty' não é reconhecido como um comando interno
ou externo, um programa operável ou um arquivo em lotes.
805493
'stty' não é reconhecido como um comando interno
ou externo, um programa operável ou um arquivo em lotes.

Debug info at C:\wamp64\www\PROJETOSITE\2022\assinaturaDigital\assinaturaSAPP\src\PDFDoc.php:755: generating xref using classic xref...trailer

PDF version

Hello Mr Alfonso
Thanks for creating this library that made me learn a lot, so my question is in what version this library will run perfectly? because i'd try with pdf 1.3 or 1.4 , it running well, but with pdf 1.7 it didn't work, always "failed to parse file "

thank you for your attention and explanation

Clue for signature appearance

Hello again mr alfonso,
Now i'm trying to sign pdf doc with signature with image (pdfsigni.php). In this case I want to put image signatures based on my needs, mostly in bottom of page, it could be left, middle or right bottom. would you give some clues or something to put the image dinamically? what parameters range values for that i can throw into set_signature_appearance function?

Thanks before

Composer install warning => "Class ddn\sapp\helpers\UUID located in ./vendor/ddn/sapp/src/helpers/uuid.php does not comply with psr-4 autoloading standard. Skipping."

Hi!,

I've found a warning message when I install this project using composer, but all works well ...it says:

Class ddn\sapp\helpers\UUID located in ./vendor/ddn/sapp/src/helpers/uuid.php does not comply with psr-4 autoloading standard. Skipping.

Not sure if its good or bad :D .. what I do to use it is use ddn\sapp\PDFDoc; at the beginning of php file

Thanks!

Parsing previously signed PDF throws "Invalid token" exception in Parser

admittedly, I do not fully understand the underlying object structure of PDF, so feel free to take my "issue" with a grain of salt.
I created a signed (with image) PDF, based on the pdfsigni.php example. It worked and I was able to generate a signed PDF. Very cool, I might add!

However, when I attempt to run the same script on the newly generated PDF, it dies out on line 350 of the PDFObjectParser (which is the default switch case when there is an invalid token.) For what its worth, my "Invalid token" (eg $this->_tt) is "5." I don't really know what that means though, LOL

Any thoughts on what silly and obvious thing I am missing here? Thanks!

Issues with Examples

PS C:\xampp\htdocs\signer\sapp> php pdfrebuild.php examples/testdoc.pdf > testdoc-rebuilt.pdf

PHP Fatal error:  Uncaught Exception: Invalid token: pos: 50143, c:  , n: \, t: \000, tt: simple, b: \000Q\000u\000a\000r\000t\000z\000 \000P\000D\000F
 in C:\xampp\htdocs\signer\sapp\src\PDFObjectParser.php:351
Stack trace:
#0 C:\xampp\htdocs\signer\sapp\src\PDFObjectParser.php(391): ddn\sapp\PDFObjectParser->_parse_obj()
#1 C:\xampp\htdocs\signer\sapp\src\PDFObjectParser.php(150): ddn\sapp\PDFObjectParser->_parse_value()
#2 C:\xampp\htdocs\signer\sapp\src\PDFUtilFnc.php(631): ddn\sapp\PDFObjectParser->parse(Object(ddn\sapp\helpers\StreamReader))
#3 C:\xampp\htdocs\signer\sapp\src\PDFUtilFnc.php(484): ddn\sapp\PDFUtilFnc::object_from_string('%PDF-1.3\n%\xC4\xE5\xF2\xE5\xEB...', 1, 49883, NULL)
#4 C:\xampp\htdocs\signer\sapp\src\PDFUtilFnc.php(542): ddn\sapp\PDFUtilFnc::find_object_at_pos('%PDF-1.3\n%\xC4\xE5\xF2\xE5\xEB...', 1, 49875, Array)
#5 C:\xampp\htdocs\signer\sapp\src\PDFDoc.php(246): ddn\sapp\PDFUtilFnc::find_object('%PDF-1.3\n%\xC4\xE5\xF2\xE5\xEB...', Array, 1)
#6 C:\xampp\htdocs\signer\sapp\src\PDFDoc.php(555): ddn\sapp\PDFDoc->get_object(1)
#7 C:\xampp\htdocs\signer\sapp\src\PDFDoc.php(675): ddn\sapp\PDFDoc->update_mod_date()
#8 C:\xampp\htdocs\signer\sapp\src\PDFDoc.php(812): ddn\sapp\PDFDoc->to_pdf_file_b(true)
#9 C:\xampp\htdocs\signer\sapp\pdfrebuild.php(37): ddn\sapp\PDFDoc->to_pdf_file_s(true)
#10 {main}
  thrown in C:\xampp\htdocs\signer\sapp\src\PDFObjectParser.php on line 351

I am using windows with a fresh install using these instructions:

$ git clone https://github.com/dealfonso/sapp
$ cd sapp
$ composer dump-autoload
$ php pdfrebuild.php examples/testdoc.pdf > testdoc-rebuilt.pdf

PHP Fatal error: Uncaught Error: Call to a member function val() on int in ddn/sapp/src/PDFDoc.php:459

I'm trying pdfsigni.php
And i'm getting

PHP Fatal error:  Uncaught Error: Call to a member function val() on int in ddn\sapp\src\PDFDoc.php:459
Stack trace:
#0 ddn\sapp\src\PDFDoc.php(690): ddn\sapp\PDFDoc->_generate_signature_in_document()
#1 ddn\sapp\src\PDFDoc.php(823): ddn\sapp\PDFDoc->to_pdf_file_b(false)
#2 ddn\sapp\pdfsigni.php(78): ddn\sapp\PDFDoc->to_pdf_file_s()
#3 {main}
  thrown in ddn\sapp\src\PDFDoc.php on line 459

sapp/src/PDFDoc.php

Lines 458 to 460 in 01acefd

$result = _add_image([$this, "create_object"], $imagefilename, $bbox[0], $bbox[1], $bbox[2], $bbox[3], $page_rotation->val());
if ($result === false)

ddn\sapp\src\PDFDoc.php on line 459 is $page_rotation->val()

I'm doing something wrong? i did not change anything

Btw, thanks for the amazing work

Extracting all signatures

How can I extract all signatures from a PDF (to validate them one by one) ?

Please help !

Thanks !

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.