-
-
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?
Changes from 10 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,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 | ||
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; | ||
} | ||
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 think you should rename the function starting with Or prefix them with 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. 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 commentThe reason will be displayed to describe this comment to others. Learn more. use 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 commentThe 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. |
||
?> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 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 = 11742; | ||
protected $signature_max_length = 20742; | ||
|
||
/** | ||
* Data for digital signature appearance. | ||
|
@@ -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 commentThe 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); | ||
} | ||
|
@@ -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')) { | ||
|
@@ -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; | ||
} | ||
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 indent this with spaces and move all this code into a protected function you would call here to fetch the signature ? 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 ? 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 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. 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. Please do not close this pull request 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. ok. 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 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 commentThe reason will be displayed to describe this comment to others. Learn more. okay, up to you. 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. wait for awhile. i'm updating code to classes. 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. okay, let me know 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 have done, please review and make some corrections if needed. |
||
return $signature; | ||
// 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.
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.