Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement TimeStamping feature #617

Open
wants to merge 44 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
28c747b
Update tcpdf.php
hidasw May 14, 2023
ff9e5a8
Create asn1_function_tcpdf.php
hidasw May 14, 2023
ff20f9f
Create asn1_parser_tcpdf.php
hidasw May 14, 2023
e998624
Create functionLog_tcpdf.php
hidasw May 14, 2023
ffce574
Create Local Root CA.crt
hidasw May 14, 2023
15bf870
Create tcpdf test.pem
hidasw May 14, 2023
a295943
Create index.php
hidasw May 14, 2023
abfaeda
Update tcpdf.php
hidasw May 15, 2023
01fb957
Update index.php
hidasw May 15, 2023
1656d3c
reformatting and cleanup
hidasw May 16, 2023
80f70d3
Update tcpdf_asn1.min.php
hidasw May 17, 2023
3ab497a
Update tcpdf.php
hidasw May 17, 2023
c8e4c7b
major update
hidasw May 25, 2023
c16b58c
Update tcpdf.php
hidasw Jun 4, 2023
f1de348
Update include/tcpdf_cmssignature.php
hidasw Dec 22, 2023
1499b2b
Remove type on properties.
evamtinez Apr 16, 2024
df74f95
Curl close was moved after curl_getinfo.
evamtinez Apr 16, 2024
4a384cb
Fix indentation
evamtinez Apr 16, 2024
fe6c482
Merge pull request #2 from evamtinez/main
hidasw Apr 18, 2024
3a741b6
Add TSA parameter
hidasw Apr 19, 2024
fe247a1
update to support LTV and TSA at once
hidasw Apr 22, 2024
050eec1
Merge branch 'main' into main
hidasw Apr 22, 2024
92f3539
significant improvement on signing process
hidasw Apr 24, 2024
3ecc5ab
Merge branch 'main' of https://github.com/hidasw/TCPDF
hidasw Apr 24, 2024
959052d
combines several functions
hidasw Apr 24, 2024
9950510
Update example_052.php
hidasw Apr 24, 2024
e1297c2
Update tcpdf.php
hidasw May 14, 2024
a7b5ebc
Update tcpdf_cmssignature.php
hidasw May 14, 2024
46ecd34
Update example_052.php
hidasw May 14, 2024
6ab1ed7
Delete RootCATest.pem.crl
hidasw May 14, 2024
f166179
Delete RootCATest.der.crl
hidasw May 14, 2024
f7dc852
Delete Root CA Test.crt
hidasw May 14, 2024
7a766b6
Delete Root CA Test OCSP Signer.pem
hidasw May 14, 2024
2543e57
Delete PDF User.pem
hidasw May 14, 2024
0913f4f
Create PDF Signing CA.crt
hidasw May 14, 2024
58228d7
Create ocspTest.bat
hidasw May 14, 2024
5ea1de8
Create longChain.pfx
hidasw May 14, 2024
d2f6c24
Create longChain.pem
hidasw May 14, 2024
ca7a5e3
Create long.pfx
hidasw May 14, 2024
67ce271
Create b6ce4782.0
hidasw May 14, 2024
42beeea
Create 7262ea48.0
hidasw May 14, 2024
0884ea0
Create 71ab782e.0
hidasw May 14, 2024
5d1c8ec
Create 5a0ce691.0
hidasw May 14, 2024
cbb1aac
Create 26a91b3f.0
hidasw May 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions include/tcpdf_asn1.min.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php
// ASN.1 Parser start 21:31 Sore Kamis 26 Maret 2009
// ASN.1 Parser at 22:10 Sore Kamis 26 Maret 2009 Telah jadi utk standar asn.1
//
// 06:40 Esuk Jumat 27 Maret 2009 ASN.1 Parser kesulitan dlm memecahkan explicit > 9

// 11:18 Esuk Jumat 27 Maret 2009 parse explicit:xx mulai dipecahkan. kemungkinan tlh jadi
// 17:51 Sore Jumat 27 Maret 2009 memecahkan explicit sampai 2097151 (65536 * 32) kurang 1

// 20:04 Sore Jumat 27 Maret 2009 ASN.1 Parser tlh jadi. Congratulation....
// 12:15 Sore 16/05/2023
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all this file seems quite old, what is it source ?
what is the license of this code ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, it useless. just progress mark. i'm forget to remove some comment line.

function asn1_first($hex) {
$asn1_Id = substr($hex, 0, 2);
$header = substr($hex, 2, 2);
if($asn1_Id == 'bf') {
if(hexdec($header) > 128) {
$headerLength = hexdec(substr($hex, 6, 2));
$reduced = 8; // the string reduced by id & headerLength
$expNum = (128*(hexdec($header)-128))+hexdec(substr($hex, 4, 2));
$header2 = substr($hex, 4, 2);
if(hexdec($header2) >= 128) {
$headerLength = hexdec(substr($hex, 8, 2));
$reduced = 10;
$expNum = (16384*(hexdec($header)-128))+(128*(hexdec($header2)-128))+hexdec(substr($hex, 6, 2));
}
} else {
$headerLength = hexdec(substr($hex, 4, 2));
$reduced = 6;
$expNum = hexdec(substr($hex, 2, 2));
}
$asn1_Id = "EXP:"."$expNum";
} else {
//echo "$header==";
if($header == '83') {
$headerLength = hexdec(substr($hex, 4, 6));
$reduced = 10;
} elseif ($header == '82') {
$headerLength = hexdec(substr($hex, 4, 4));
$reduced = 8;
} elseif ($header == '81') {
$headerLength = hexdec(substr($hex, 4, 2));
$reduced = 6;
} else {
$l=0;
$l = hexdec(substr($hex, 2, 2));
$headerLength = $l;
$reduced = 4;
//echo "$headerLength --".substr($hex, 2, 2)."--<br>";

}
}
$str_remains = substr($hex, $reduced+($headerLength*2));
$content = substr($hex, $reduced, $headerLength*2);
$return['res'] = array($asn1_Id, $content); // array 0=>iD(sequence be 30, integer be 02, etc) 1=>contents of id
$return['rem'] = $str_remains; // the remain string returned
if($str_remains == '' && $content == '') { // if remains string was empty & contents also empty, function return FALSE
$return = false;
}
return $return;
}

function asn1parse($hex) {
//$return =false;
while(asn1_first($hex) != false) { // while asn1_first() still return string
$r = asn1_first($hex);
$return[] = array($r['res'][0],$r['res'][1]);
$hex = $r['rem']; // $hex now be result of asn1_first()
}
if(!is_array(@$return)) {
return false;
}
return $return;
}

// change at 22:37 Sore 04/09/2009
// change at 12:15 Sore 16/05/2023
function asn1_header($str) {
$len = strlen($str)/2;
$ret = dechex($len);
if(strlen($ret)%2 != 0) {
$ret = "0$ret";
}

$headerLength = strlen($ret)/2;
if($len > 127) {
$ret = "8".$headerLength.$ret;
}
return $ret;
}

function SEQ($hex) {
$ret = "30".asn1_header($hex).$hex;
return $ret;
}
function OCT($hex) {
$ret = "04".asn1_header($hex).$hex;
return $ret;
}
function INT($int) {
if(strlen($int)%2 != 0) {
$int = "0$int";
}
$int = "$int";
$ret = "02".asn1_header($int).$int;
return $ret;
}
function SET($hex) {
$ret = "31".asn1_header($hex).$hex;
return $ret;
}
//function EXPLICIT($num="0", $hex) {
function EXPLICIT($num, $hex) {
$ret = "a$num".asn1_header($hex).$hex;
return $ret;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should rename the function starting with asn1_ so it had less changes to collide with existing functions at the user level

Or prefix them with tcpdf_

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

altough renaming that, the procedural style function too exposed indeed, plan to built a class for that but dont know how to do it. cause its so impractice when repeatedly write "new" or "$this->".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use static

class Foo {
public static function doFoo() {}
}

Foo::doFoo();

Should be way easier

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so, but I need to simplify and compacting the script first.

?>
131 changes: 128 additions & 3 deletions tcpdf.php
Original file line number Diff line number Diff line change
Expand Up @@ -1274,7 +1274,8 @@ class TCPDF {
* @protected
* @since 4.6.005 (2009-04-24)
*/
protected $signature_max_length = 11742;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i''m increasing this size to provide enough space to place tsa response data. and sometimes need more, depend on tsa response data size.

// protected $signature_max_length = 11742;
protected $signature_max_length = 20742;

/**
* Data for digital signature appearance.
Expand Down Expand Up @@ -7692,12 +7693,14 @@ public function Output($name='doc.pdf', $dest='I') {
$signature = $tmparr[1];
// decode signature
$signature = base64_decode(trim($signature));
// add TSA timestamp to signature
$signature = $this->applyTSA($signature);
// convert signature to hex
$signature = current(unpack('H*', $signature));
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should before $this->applyTSA($signature)

// add TSA timestamp to signature
$signature = $this->applyTSA($signature);

$signature = str_pad($signature, $this->signature_max_length, '0');
// Add signature to the document

$this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
$this->bufferlen = strlen($this->buffer);
}
Expand Down Expand Up @@ -13642,6 +13645,8 @@ protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1,
* @author Richard Stockinger
* @since 6.0.090 (2014-06-16)
*/
// other options suggested to be implement: reqPolicy, nonce, certReq, extensions
// Also option to abort signing if timestamping failed and LTV enable (embed crl and or ocsp revocation info)
public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') {
$this->tsa_data = array();
if (!function_exists('curl_init')) {
Expand Down Expand Up @@ -13675,7 +13680,127 @@ protected function applyTSA($signature) {
return $signature;
}
//@TODO: implement this feature
// start timestamping
// by Hida (16 Mei 2023)

// Include minimum asn.1 fuctional script
require_once(dirname(__FILE__).'/include/tcpdf_asn1.min.php');

// Parse TCPDF's pkcs#7 Signature structure to get sequence of signed hash
$pkcs7 = asn1parse($signature);
$pkcs7ContentInfo = asn1parse($pkcs7[0][1]);

$pkcs7content = asn1parse($pkcs7ContentInfo[1][1]);

$pkcs7SignedData = asn1parse($pkcs7content[0][1]);

$pkcs7signerInfos = asn1parse($pkcs7SignedData[4][1]);

$SignerInfo = asn1parse($pkcs7signerInfos[0][1]);

$pkcs7EncryptedDigest = $SignerInfo[5][1];

// Create timestamp request

// Create hash of encrypted contents TCPDF signature
// $this->setTimeStamp() have no options for change tsa req hash alg yet, so sha1 selected
$hash = hash('sha1', hex2bin($pkcs7EncryptedDigest));

// Build timestamp request data
$tsReqData = seq(
int(1).
seq(
seq(
"06052B0E03021A". // Obj_sha1
"0500" // Null
).
oct($hash)
).
int(hash('crc32', rand())). // Add random nonce request
'0101ff' // set certReq true to tell TSA server to include SigningCertificate
);

$raw_data = hex2bin($tsReqData);

//Send request to TSA Server with Curl
if(extension_loaded('curl')) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->tsa_data['tsa_host']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/timestamp-query',
'User-Agent: TCPDF'
)
);
curl_setopt($ch, CURLOPT_POSTFIELDS, $raw_data);


// can't send tsRequest, Timestamp failed!
if(!$tsResponse = curl_exec($ch)) {
return $signature;
}

// parse timestamp response data
$hexTsaResponse = bin2hex($tsResponse);
if(!$parseTimeStampResp = asn1parse($hexTsaResponse)) { // bad TSA Reponse
return $signature;
}

// verify tsa response PKIStatusInfo and TimeStampToken exists
if(!$TimeStampResp = asn1parse($parseTimeStampResp[0][1])) {
return $signature;
}

// Select timeStampToken only. must ignore response status data (in first sequence if exist, select 2nd sequence)
if(count($TimeStampResp) > 1) {
$TSTInfo = $TimeStampResp[1][1]; // TSTInfo
} else if (count($TimeStampResp) == 1) {
$TSTInfo = $TimeStampResp[0][1]; // TSTInfo
} else { // TimeStampResp not containts 1 or 2 fields
return $signature;
}

// Add timestamp in TCPDF Signature
// Create timestamp pkcs#7 data
$TimeStampToken = seq(
"060B2A864886F70D010910020E". // OBJ_id_smime_aa_timeStampToken
set(
seq(
$TSTInfo // TSTInfo
)
)
);

$time = seq(
$pkcs7signerInfos[0][1].
explicit(1,
$TimeStampToken
)
);

$pkcs7contentSignedData=seq(
int(1). // version
set($pkcs7SignedData[1][1]). // digestAlgorithms
seq($pkcs7SignedData[2][1]). // contentInfo
explicit(0,
$pkcs7SignedData[3][1]
). // certificates [0] IMPLICIT ExtendedCertificatesAndCertificates
set(
$time
)
);
$pkcs7ContentInfo = seq(
"06092A864886F70D010702". // ContentType OBJ_pkcs7_signed
explicit(0,($pkcs7contentSignedData)) // content
).
// "0000"; // sometime needed for backward compatibility
"";

$signature = $pkcs7ContentInfo;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you indent this with spaces and move all this code into a protected function you would call here to fetch the signature ?
That would avoid adding that much code into this function

Also, I think you could keep the actual value of signature_max_length and only increase it (if the value < what you need) when calling the signature process ?
Would that keep other pdf documents the same as before (the ones without a signature)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think signature_max_length value have to be reserved before signing process, thats why it not set to 0. will padded by zero after signing. this value affect only signed file size to several kilobytes. Tried to lower this value around 14k and still done with apple tsa.
want move to object oriented but not at the moment. maybe to closed this pull request while i'll periodically update script.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not close this pull request
If you invite me on your repository I may clean up the implementation for you

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I start pushing work or do you have more to push first?

Copy link
Author

@hidasw hidasw May 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, up to you.
i'm currently improving asn.1 parser and have been stuck on recursion for last few days. cz i'm working alone.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait for awhile. i'm updating code to classes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, let me know

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have done, please review and make some corrections if needed.
I've minimized the script, considering most of the general purpose asn.1 encoders/decoders use complex libraries.

return $signature;
// End timestamping
}

/**
Expand Down