After reading this Stack Overflow question (and other pages, referenced below, in the comments) I came up with a PHP code that, given a digitally signed PDF file, informs who signed it:
<?php
function der2pem($der_data) {
// https://www.php.net/manual/en/ref.openssl.php
$pem = chunk_split(base64_encode($der_data), 64, "\n");
$pem = "-----BEGIN CERTIFICATE-----\n".$pem."-----END CERTIFICATE-----\n";
return $pem;
}
function extract_pkcs7_signatures($path_to_pdf) {
// https://stackoverflow.com/q/46430367
$content = file_get_contents($path_to_pdf);
$regexp = '/ByteRange\ \[\s*(\d+) (\d+) (\d+)/';
$result = [];
preg_match_all($regexp, $content, $result);
$signatures = null;
if (isset($result[2]) && isset($result[3]) && isset($result[2][0]) && isset($result[3][0])) {
$start = $result[2][0];
$end = $result[3][0];
if ($stream = fopen($path_to_pdf, 'rb')) {
$signatures = stream_get_contents($stream, $end - $start - 2, $start + 1);
fclose($stream);
$signatures = hex2bin($signatures);
}
}
return $signatures;
}
function who_signed($path_to_pdf) {
// https://www.php.net/manual/en/openssl.certparams.php
// https://www.php.net/manual/en/function.openssl-pkcs7-read.php
// https://www.php.net/manual/en/function.openssl-x509-parse.php
$signers = [];
$signatures = extract_pkcs7_signatures($path_to_pdf);
if (!empty($signatures)) {
$pem = der2pem($signatures);
$certificates = array();
$result = openssl_pkcs7_read($pem, $certificates);
if ($result) {
foreach ($certificates as $certificate) {
$certificate_data = openssl_x509_parse($certificate);
$signers[] = $certificate_data['subject']['CN'];
}
}
}
return $signers;
}
$path_to_pdf = 'test.pdf';
// In case you want to test the extract_pkcs7_signatures() function:
/*
$signatures = extract_pkcs7_signatures($path_to_pdf);
$path_to_pkcs7 = pathinfo($path_to_pdf, PATHINFO_FILENAME) . '.pkcs7';
file_put_contents($path_to_pkcs7, $signatures);
echo shell_exec("openssl pkcs7 -inform DER -in $path_to_pkcs7 -print_certs -text");
exit;
*/
var_dump(who_signed($path_to_pdf));
?>
This is just command line PHP, you don't need to run any previous Composer commands to be able to run this script.
For some test1.pdf, signed by just one person (let's call her ALICE), this script returns:
array(4) {
[0]=>
string(23) "CERTIFICATE AUTHORITY 1"
[1]=>
string(23) "CERTIFICATE AUTHORITY 2"
[2]=>
string(5) "ALICE"
[3]=>
string(5) "ALICE"
}
For some test2.pdf, signed by two people (let's call them BOB and CAROL), this script returns:
array(4) {
[0]=>
string(23) "CERTIFICATE AUTHORITY 1"
[1]=>
string(3) "BOB"
[2]=>
string(23) "CERTIFICATE AUTHORITY 2"
[3]=>
string(23) "CERTIFICATE AUTHORITY 3"
}
The problem with this script is that, comparing its outputs to the ones provided by pdfsig, they are wrong.
For the same test1.pdf, pdfsig returns:
Digital Signature Info of: test1.pdf
Signature #1:
- Signer Certificate Common Name: ALICE
...
For the same test2.pdf, pdfsig returns:
Digital Signature Info of: test2.pdf
Signature #1:
- Signer Certificate Common Name: BOB
...
Signature #2:
- Signer Certificate Common Name: CAROL
...
What am I doing wrong? I mean, what do I need to do to correctly identify the person (or the people) who signed a PDF file?