0

I would like to detect signed PDFs in PHP and verify if the signature is valid. From this document I have written this PHP code below.

What it does is:

  1. Extract the PKCS7 code (it works because I can get the details from Openssl)
  2. Compute the SHA256 hash of the document.

At the end I has a PKCS7 file and a SHA256.

Now, I would like to verify my signature against my PKCS7 file. How can I do this? I initially looked to the digest_enc_alg/sha256WithRSAEncryption/enc_digest, but it seems it is not what I am looking about.

class VerifyPDF
{
    public static function getByteRange($filename)
    {
        $content = file_get_contents('foo.pdf');
        if (!preg_match_all('/ByteRange\[\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*\]/', $content, $matches))
        {
            throw new \Exception('Unable to get certificate');
        }

        return [
            intval($matches[1][0]), // Offset of the first part (usually 0)
            intval($matches[2][0]), // Size of the first part
            intval($matches[3][0]), // Offset to the second part
            intval($matches[4][0])  // Size of the second part
        ];
    }

    public static function get_pkcs7($filename)
    {
        [$sof, $sos, $eos, $eof] = self::getByteRange($filename);

        if (!$fp = fopen($filename, 'rb')) {
            throw new \Exception("Unable to open $filename");
        }

        $signature = stream_get_contents($fp, $eos - $sos - 2, $sos + 1);
        fclose($fp);

        file_put_contents('out.pkcs7', hex2bin($signature));
    }

    public function compute_hash($filename)
    {
        [$a, $b, $c, $d] = self::getByteRange($filename);

        if (!$fp = fopen($filename, 'rb')) {
            throw new \Exception("Unable to open $filename");
        }

        var_dump([$a, $b, $c, $d]);

        $i = stream_get_contents($fp, $b, $a);
        $j = stream_get_contents($fp, $d, $c);

        if (strlen($i) != $b || strlen($j) != $d) {
            throw new \Exception('Invalid chunks');
        }

        fclose($fp);

        return hash('sha256', $i . $j);
    }
}

The HASH I get is:

5036ae43aba11ce626f6f9b1d5246ba0700e217655b9ff927e31fbefadfa2182

So inspired from this I did the following:

#!/bin/bash
PKCS7='out.pkcs7'

# Extract Digest (SHA256)
OFFSET=$(openssl asn1parse -inform der -in $PKCS7 | perl -ne 'print $1 + $2 if /(\d+):d=\d\s+hl=(\d).*?256 prim.*HEX DUMP/m')
dd if=$PKCS7 of=signed-sha256.bin bs=1 skip=$OFFSET count=256

# Extract Public key 
openssl pkcs7 -print_certs -inform der -in $PKCS7 | tac | sed '/-----BEGIN/q' | tac > client.pem
openssl x509 -in client.pem -pubkey -noout > client.pub.pem

# Verify the signature
openssl rsautl -verify -pubin -inkey client.pub.pem < signed-sha256.bin > verified.bin

# Get Hash and compare with the computed hash from the PDF
openssl asn1parse -inform der -in verified.bin | grep -Po '\[HEX DUMP\]:\K\w+$'

Which gives me this:

C8581962753927BB57B66B1D0D0F4B33A29EF3E03DA12D2329DB72763AC7EDB6

So unfortunately by two hashes do not match...

Am I missing something?

  • Is this just doing a regular expression search over the file? What do you intend to do if someone supplies a PDF with content matching your regex in a PDF comment? I suspect you would be better served with a PDF parser. – sarnold Jan 25 at 3:06
  • This is exactly how a PDF parser will proceed :) – nowox Jan 25 at 6:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.