-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Implement TimeStamping feature #617
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
base: main
Are you sure you want to change the base?
Changes from 12 commits
28c747b
ff9e5a8
ff20f9f
e998624
ffce574
15bf870
a295943
abfaeda
01fb957
1656d3c
80f70d3
3ab497a
c8e4c7b
c16b58c
f1de348
1499b2b
df74f95
4a384cb
fe6c482
3a741b6
fe247a1
050eec1
92f3539
3ecc5ab
959052d
9950510
e1297c2
a7b5ebc
46ecd34
6ab1ed7
f166179
f7dc852
7a766b6
2543e57
0913f4f
58228d7
5ea1de8
d2f6c24
ca7a5e3
67ce271
42beeea
0884ea0
5d1c8ec
cbb1aac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| <?php | ||
| /** | ||
| * @file | ||
| * This is a PHP function for parsing and creating ASN.1 syntax for handle minimum timeStamp request and response. | ||
| * @author Hida | ||
| * @version 1.0.1 | ||
| * Last Update : 17/05/2023 | ||
| */ | ||
| 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; | ||
| } | ||
|
|
||
| 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; | ||
| } | ||
| ?> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1274,7 +1274,7 @@ class TCPDF { | |
| * @protected | ||
| * @since 4.6.005 (2009-04-24) | ||
| */ | ||
| protected $signature_max_length = 11742; | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 = 16742; | ||
|
|
||
| /** | ||
| * Data for digital signature appearance. | ||
|
|
@@ -7692,12 +7692,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)); | ||
|
||
| // 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); | ||
| } | ||
|
|
@@ -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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but it for signing, which already implemented here
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @williamdes Wouldn't be possible to use the default
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the feedback, @hidasw what do you think ?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @williamdes |
||
| public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') { | ||
| $this->tsa_data = array(); | ||
| if (!function_exists('curl_init')) { | ||
|
|
@@ -13674,7 +13678,50 @@ protected function applyTSA($signature) { | |
| if (!$this->tsa_timestamp) { | ||
| return $signature; | ||
| } | ||
| //@TODO: implement this feature | ||
| // * @author Hida | ||
| 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 | ||
| $hash = hash('sha1', hex2bin($pkcs7EncryptedDigest)); | ||
| $tsReqData = seq(int(1).seq(seq("06052B0E03021A"."0500").oct($hash)).int(hash('crc32', rand())).'0101ff'); | ||
| $binarytsReqData = hex2bin($tsReqData); | ||
|
|
||
| //Send request to TSA Server | ||
| $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, $binarytsReqData); | ||
| if(!$tsResponse = curl_exec($ch)) { return $signature; } // can't send tsRequest, Timestamp failed! | ||
| $hexTsaResponse = bin2hex($tsResponse); // parse timestamp response data | ||
| if(!$parseTimeStampResp = asn1parse($hexTsaResponse)) { return $signature; } // bad TSA Reponse | ||
| if(!$TimeStampResp = asn1parse($parseTimeStampResp[0][1])) { return $signature; } // verify tsa response PKIStatusInfo and TimeStampToken exists | ||
|
|
||
| // 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; | ||
| } | ||
|
|
||
| // Create timestamp pkcs#7 data | ||
| $TimeStampToken = seq("060B2A864886F70D010910020E".set(seq($TSTInfo))); | ||
| $time = seq($pkcs7signerInfos[0][1].explicit(1,$TimeStampToken)); | ||
| $pkcs7contentSignedData=seq(int(1).set($pkcs7SignedData[1][1]).seq($pkcs7SignedData[2][1]).explicit(0,$pkcs7SignedData[3][1]).set($time)); | ||
| $pkcs7ContentInfo = seq("06092A864886F70D010702".explicit(0,($pkcs7contentSignedData))); | ||
|
|
||
| $signature = $pkcs7ContentInfo; | ||
| return $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 you should rename the function starting with
asn1_so it had less changes to collide with existing functions at the user levelOr 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
staticShould 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.