-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
base: main
Are you sure you want to change the base?
Conversation
tcpdf.php
Outdated
@@ -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)); |
There was a problem hiding this comment.
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)
@@ -1274,7 +1274,8 @@ class TCPDF { | |||
* @protected | |||
* @since 4.6.005 (2009-04-24) | |||
*/ | |||
protected $signature_max_length = 11742; |
There was a problem hiding this comment.
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.
add Content-Type and User-Agent header for tsa request. Some server only accept application/timestamp-query
include/functionLog_tcpdf.php
Outdated
@@ -0,0 +1,49 @@ | |||
<?php | |||
// create 11:16 AM 10/17/2011 | |||
function tsaLog($str, $type = 'i', $nl=true) { // 11:16 AM 10/17/2011 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this file and function should be removed from this pull-request
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes it for debug only
tcpdf.php
Outdated
// start timestamping | ||
// by Hida since 5.9.128 (2011-10-06) | ||
if($this->tsa_timestamp) { | ||
//Include asn1 fuction script | ||
require_once(dirname(__FILE__).'/include/asn1_parser_tcpdf.php'); | ||
require_once(dirname(__FILE__).'/include/asn1_function_tcpdf.php'); | ||
require_once(dirname(__FILE__).'/include/functionLog_tcpdf.php'); | ||
|
||
$tsaLog = __FILE__." line:(".__LINE__."). Perform timestamping...\n"; | ||
//Parse TCPDF Signature structure to get signed hash sequence | ||
$p = asn1parse($signature); | ||
$p1 = asn1parse($p[0][1]); | ||
$p2 = asn1parse($p1[1][1]); | ||
$p3 = asn1parse($p2[0][1]); | ||
$p2 = asn1parse($p3[4][1]); | ||
$pa1 = asn1parse($p2[0][1]); | ||
$pa2 = asn1parse($pa1[3][1]); | ||
|
||
//Create timestamp request | ||
|
||
//Create hash of encrypted contents TCPDF signature | ||
$hash = hash('sha1', hex2bin($pa1[5][1])); | ||
//Build timestamp request data | ||
$tsReqData = seq( | ||
int(1). | ||
seq( | ||
seq( | ||
"06052B0E03021A". // Obj_sha1 | ||
"0500" // Null | ||
). | ||
oct($hash) | ||
). | ||
int(hash('crc32', rand())). | ||
'0101ff' | ||
); | ||
$raw_data = hex2bin($tsReqData); | ||
|
||
//Send request to TSA Server with Curl | ||
if(extension_loaded('curl')) { | ||
$tsaLog .= __FILE__." line:(".__LINE__."). Curl was already Loaded\n".__FILE__." line:(".__LINE__."). Curl is sending tsRequest to \"".$this->tsa_data['tsa_host']."\" ...\n"; | ||
$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, [ | ||
'Content-Type: application/timestamp-query', | ||
'User-Agent: TCPDF' | ||
curl_setopt($ch, CURLOPT_POSTFIELDS, $raw_data); | ||
$tsResponse = curl_exec($ch); | ||
if($tsResponse != false) { | ||
$tsaLog .= __FILE__." line:(".__LINE__."). tsRequest is sent.\n"; | ||
} else { | ||
tsaLog("$tsaLog".__FILE__." line:(".__LINE__."). can't send tsRequest, Timestamp failed!\n",'w'); | ||
} | ||
//parse ts response | ||
$hexTs = bin2hex($tsResponse); | ||
$tsparse = asn1parse($hexTs); | ||
|
||
$tsparse0 = asn1parse($tsparse[0][1]); | ||
if(count($tsparse0) > 1) { //Remove response status data, only take timeStampToken | ||
$timeStamp = seq($tsparse0[1][1]); | ||
} else { | ||
$timeStamp = seq($tsparse0[0][1]); | ||
} | ||
|
||
//Add timestamp to TCPDF Signature | ||
$timeStamp = seq("060B2A864886F70D010910020E".set($timeStamp)); | ||
$pkcs7 = int($pa1[0][1]).seq($pa1[1][1]).seq($pa1[2][1]).explicit(0, $pa1[3][1]).seq($pa1[4][1]).oct($pa1[5][1]); | ||
$time = seq($pkcs7.explicit(1,$timeStamp)); | ||
$aa=seq(int(1). set($p3[1][1]).seq($p3[2][1]).explicit(0, $p3[3][1]).set($time)); | ||
$hdaSignature = seq("06092A864886F70D010702".explicit(0,($aa)))."0000"; | ||
|
||
$signature = $hdaSignature; | ||
// $tsaLog .= $signature; | ||
tsaLog("$tsaLog".__FILE__." line:(".__LINE__."). Timestamp Success.\n"); | ||
} else { | ||
$tsaLog .= __FILE__." line:(".__LINE__."). Curl was not loaded, trying to load it...\n"; | ||
if(@dl('php_curl.dll')) { | ||
$tsaLog .= __FILE__." line:(".__LINE__."). Curl successfully Loaded.\n"; | ||
} else { | ||
tsaLog("$tsaLog\n".__FILE__." line:(".__LINE__."). Curl failed to load. Timestamping failed!", 'w'); | ||
} | ||
} | ||
} | ||
// end timestamping |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you do some code cleanup and formatting please ?
It's really hard to read
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try to rewrite with more readable soon..
also merge asn.1 function to one file, remove useless log function file. tested working trusted timestamp http://timestamp.apple.com/ts01
tcpdf.php
Outdated
|
||
// 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; | ||
} |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay, let me know
There was a problem hiding this comment.
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.
include/tcpdf_asn1.min.php
Outdated
// 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 |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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.
include/tcpdf_asn1.min.php
Outdated
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; | ||
} |
There was a problem hiding this comment.
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_
There was a problem hiding this comment.
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->".
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
migrate procedural to class
@@ -13642,6 +13644,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 | |||
// and option to abort signing if timestamping failed and LTV enable (embed crl and or ocsp revocation info) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicolaasuni what minimal set of changes would be accepted ?
Some of it could go into a new library ?
Would everything be accepted ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but it for signing, which already implemented here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@williamdes Wouldn't be possible to use the default openssl_*
PHP functions (as in the current version) instead or re-implementing the cryptography function from scratch?
I will look into port this functionality into tc-lib-pdf but I am not keen on rewriting crypto libs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback, @hidasw what do you think ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We using openssl for cryptographic functions (encrypt/decrypt). For ocsp/ts/crl operations, php openssl does not have those functions natively, even for asn.1 parsing. So it requires its own independent function or using a library like phpseclib.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you at least copy the functions from phpseclib so we can know they are safely implemented ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@williamdes
Unfortunately in phpseclib there is no function for ocsp, ts and crl. There is asn.1 function but it looks too complicated with namespace and many files included. You can compare with my script.
If you are looking for a library for ocsp, crl and ts functions, it is definitely related to the asn.1 function contained in the library. It will be difficult to separate them.
tcpdf.php
Outdated
|
||
/** | ||
* Applying TSA for a timestamp. | ||
* @param string $signature Digital signature as hex string | ||
* @return hex string Timestamped digital signature | ||
* @protected | ||
* @author M Hida | ||
* @since 6.6.2 (2023-05-25) | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This documentation is misleading since the function already existed in prior versions
could you update this to reflect that it already existed ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably remove the previous comment, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks, that's better
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice !
When I find time I will do some polishing of the code indentation
Hi @hidasw, I have noticed that the code does not validate the TSA URL at any point, and passing an invalid URL does not display any error. It simply does not apply the timestamp to the signature. I wanted to ask if this is a functionality planned to be added to the pull request. Thanks |
@evamtinez |
I'm using PHP 7.2. I can see the log file correctly, but it would be helpful to get an error if the timestamp is invalid. For instance, if I set a timestamp to something like tsa.example.org (which doesn’t exist), the document appears to be signed correctly, and the process returns OK, but the PDF file doesn’t have the timestamp. I’m handling this error in the class I'm using in my project, but I'm unsure if it needs to be directly integrated into the TCPDF library. In my implementation, before setting the timestamp, I call a validation function to check if the TSA is valid. However, this validation could be directly implemented in the setTimestamp function. What do you think? Thanks |
@evamtinez I think so. As a note, for the current version, if the validation process fails on one of certificate chains, then this certificate chain up to the root CA is not embedded in the pdf. (to be fixed in the next update). |
$time = $params[0]; //yymmddhhiiss | ||
$oldTz = date_default_timezone_get(); | ||
date_default_timezone_set("UTC"); | ||
$time = date("ymdHis", $time); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @hidasw ,
The signature time is not being created properly because in this line you are passing a date in the format 'ymdHis' as the second parameter of the date() function instead of a timestamp.
I found this bug while working on a project that uses PHP 7.2.
Feel free to ask if you need any further assistance!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
plan to use strtotime or date class, but need to manage while converting 4 digits year to 2 digits. if year is 70, some app treat it as 1970 and other as 2070. also for time zone difference.
I am testing this fork and after I added the curl_setopt($ch, CURLOPT_USERPWD, 'username:password'); in the sendReq function (tcpdf_cmssignature.php) it successfuly requested the specified TSA, but I don't think the TSR was added correctly (I am testing the final pdf file with pdfsig utility though). However, I would like to ask you if you have idea how to add ONLY the the timestamp signature, not the ID signiture /certificate? |
whats the mean "but I don't think the TSR was added correctly", is there a problem with tsresponse? |
I am sorry for the late reply, I am struggling with this topic. Everything about PDF format and cryptography is new to me. What I am looking for perfectly demonstrates this: https://stackoverflow.com/questions/65807104/can-you-add-a-timestamped-no-tamper-proof-to-a-pdf-without-signing-it My situation is: I have a working TSA, I am able to create a valid TSQ of a whole PDF file, send it to the TSA and I receive the valid TSR. As I am generating my PDF files in PHP (currently with DomPdf, but I believe I can switch to TCPDF) I am trying to get to the state the TSR is embedded into the PDF. Currently I am able to embed the timestamp into the signiture, but as I have only a self generated certificate the acrobat says the validity of the signiture is unknown. Therefore my question is how to embed ONLY the RFC3161, i.e. /Type /DocTimeStamp object into the PDF. I do appologize for chaotic and unprecise expressions but hopefully it's understandable what I mean. |
Please, can I ask @rsvitak i see you use postsignum TSA. Please can you help me with URL to create on this. I cant go throw autenticatin process. Many thx... |
@KruzstofWren I had to update the list of changes I made to the @hidasw code in my comment above, thank you for tour post. This is neccessary to add to the sendReq function in the include/tcpdf_cmssignature.pdf to make the code send the autentication data to the server: |
@rsvitak |
Many thx for reaction. But still, in logs i have this fail... info : Timestamping process start...info: sending TSA query to "https://tsa.postsignum.cz:444/TSS/HttpTspServer/"... Something more i need edit? |
I can't test it since tsa uses authentication.
parse the response with openssl: Let me know whats wrong. |
I have stored my modifications to https://github.com/rsvitak/TCPDF to avoid missunderstandings with description of my modifications in my post #617 (comment). |
All is working great now. Only mistake is on my side....right URL for https://www.postsignum.cz is: https://www3.postsignum.cz/TSS/TSS_user/ |
@KruzstofWren I am not very sure in this topic, but the difference could be in the authority that proves the "stamp"? Maybe if you could paste the "TCPDF" commands used to create the documents, or if you can provide the final PDF files, it would be easier to tell more... |
This two type of commands $pdf->setTimeStamp('https://www3.postsignum.cz/TSS/TSS_user/', USER, PASS); And final PDF are diferent with show type of certification.... SIGN or CERTIFICATE in pannel of certification in Adobe Reader |
Hi, |
Also add some file for parsing asn.1 data, and logging (debug only).
should merged with main tcpdf.php but its not meet oop standar yet.
still use procedural style.