diff --git a/examples/data/cert/PDF User.pem b/examples/data/cert/PDF User.pem new file mode 100644 index 00000000..f9ea4de3 --- /dev/null +++ b/examples/data/cert/PDF User.pem @@ -0,0 +1,42 @@ +-----BEGIN CERTIFICATE----- +MIIEbjCCAlagAwIBAgIBNTANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDEwxSb290 +IENBIFRlc3QwIhgPMjAyNDA0MjAwMzMzNDVaGA8yMDM0MDQyMDAzMzM0NVowJTEQ +MA4GA1UEChMHVGVzdGluZzERMA8GA1UEAxMIUERGIFVzZXIwgZ8wDQYJKoZIhvcN +AQEBBQADgY0AMIGJAoGBAKH1qYqwFNlpd6ok7KyvXWxH1sp7GjJDwlLsWmpvfxJL +HFmfOtlVFcvtgFEg4DvX2U09k10vCe3IebXyBBuNDwQsqjsMqQOFDEZjQ7jO4iML +P9OOt5yuPCQFNCvzOmghUZi03QdE061wTKKQ4FKBefeXKm46ai5lQBzZcaH8WaRz +AgMBAAGjggE1MIIBMTAdBgNVHQ4EFgQU5DDcS3grVKgqyrOSqbwXnQ3S2V4wHwYD +VR0jBBgwFoAUiTCzgJSQfaYOamTcu/ocq5KNU4EwYgYIKwYBBQUHAQEEVjBUMC4G +CCsGAQUFBzAChiJodHRwOi8vbG9jYWxob3N0L2NhL1Jvb3RDQVRlc3QuY3J0MCIG +CCsGAQUFBzABhhZodHRwOi8vbG9jYWxob3N0L29jc3AvMDQGA1UdHwQtMCswKaAn +oCWGI2h0dHA6Ly9sb2NhbGhvc3QvY3JsL1Jvb3RDQVRlc3QuY3JsMAsGA1UdDwQE +AwIHgDA9BgNVHSUENjA0BggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggr +BgEFBQcDBAYKKwYBBAGCNwoDBDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4IC +AQClD/VJ1I73rCe6uWIRkivyDMZWGSOD3hg5VAi7bJ4mrVIc+p1kR5BaK4ZYTKO+ +iHQ+ybb+9FhqvLvkok1am2eXCmDp1KcPi6iGJbAzGYi+wd+u7SKl921kVTtv/oqz +ccILoLq0e3lB7VD3MRY0aHtwyLGLPgWcpJXopkNOUnPgTJKGLN/xrZ2OwRizim54 +HzF+EzFpZpVgKN5MEGfXVMLKuRr8mtuIYNJQiKT0WdprCKTv5xdOEmoZY7DdVTKB +O8Qf006/3XSsytNq79Mr034KwYSxwtz8goMP6F1qSXVnbBM0+BnEk3O+8iN0iy7A +BZhV4Cd8Il5AjIHjOLToySTNUFnSzTZZjNqtqKl0XgRIyBCHZEh6kbzkrvR+MMOv +qgisq0EtlHMLzWztIKxzc7lGV5xv8qhhPVYw6b/WY6D2IzabL6gluYuNqntsAvC2 +BO7FhcyTFgO3eO/PUJvLetNtZEfuUkjPUP17cEm26MwmDlbLCK4l/iJBClg/Zo5X +W0lRADLV/HvspXYCBVXJdGTROmRfYNua1MnFjyuj0UdnWlh05NoIX0xY/OhjumGZ +GlZgeH1FmmN3ird2dbpJ7JISVH7FFlte+nUnL/5/Mtso5s8NaaKlEordowwLEAcj +x+0xKj8xzKstu4KYxl1WgjDHXGvDcsIGQLcgHZiG8dxFBA== +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKH1qYqwFNlpd6ok +7KyvXWxH1sp7GjJDwlLsWmpvfxJLHFmfOtlVFcvtgFEg4DvX2U09k10vCe3IebXy +BBuNDwQsqjsMqQOFDEZjQ7jO4iMLP9OOt5yuPCQFNCvzOmghUZi03QdE061wTKKQ +4FKBefeXKm46ai5lQBzZcaH8WaRzAgMBAAECgYBlByogMiB/UlPsFaZxPd8H+XQZ +0FqR+kSQl5D7Ddv1XFct7K63/WRgViGR+fEGQ2nwzbVMDliiNEj/3x0C1qYDVU+u +DgZ6mpj6SxcGlZPZDL/tBWnscMx+MdxtLgebpTRc33WZebT4Z0BpGwAyVVTP+UVY +DM1saWxx4M220zm9WQJBANUt4mh0w0/jCeAFarj0RPbxAP9ISehzRi5nGLGURy/k +BhCxlyoD2bNBaKfY4LrKTeW6DWP28F1URxmooX5+DFcCQQDCffWm//ow3i+hkyu1 +lzV86OibYIIXMSrytQ9VYc+7Osyl5SJL51pgxFy/+Rb0MrOBWbnwnDOC+NIT+Z9+ +D5dFAkEAxn1TqwjU9mQiRLkmdpHSSM97qzZGwq3acchCoM28PqYk05RXeKJfKF3F +sUrpbGKDh1vlrec2RN/817JAtANvPwJBAIuFnMIOg8amvXilgQelz6Mp2iQMmYZV +qT+vj65qmoDv73ta4r2c3ALrGrZE1Kid7pPucCshgvqD0QCvZDEkshkCQBvgmKkw +gCp4t7beLYuKQA2ZoxAUUhUpfgm+ZrmYVNuucu/NRGFnCE0/RPK0hcHuJ+quA+vX +LvO+htmWg69RL1Q= +-----END PRIVATE KEY----- diff --git a/examples/data/cert/Root CA Test OCSP Signer.pem b/examples/data/cert/Root CA Test OCSP Signer.pem new file mode 100644 index 00000000..dfb9951d --- /dev/null +++ b/examples/data/cert/Root CA Test OCSP Signer.pem @@ -0,0 +1,39 @@ +-----BEGIN CERTIFICATE----- +MIIDwDCCAaigAwIBAgIBmTANBgkqhkiG9w0BAQ0FADAXMRUwEwYDVQQDEwxSb290 +IENBIFRlc3QwIhgPMjAyNDA0MjAwMzM5MjNaGA8yMDc0MDQyMDAzMzkyM1owIzEh +MB8GA1UEAxMYUm9vdCBDQSBUZXN0IE9DU1AgU2lnbmVyMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDZ3Y7XgXxECy6i/kGNK2w3v/qn7yl8x0mUO4eI3rdFh2Cu +hWWnmn3NVDwU84FF4yDPAk1Q5S8PujOXE71d2y1pv1JxDLOJU1mmkqAaz9PAabx9 +B4ipM5quWzYYy2+pIcBUMgcdK1RsdDBHmtl275aKa01FEwCGoF+i7MMbsQnQ8QID +AQABo4GKMIGHMB0GA1UdDgQWBBQwRbuGabUZgZinsgNT0NQdZIVH6TAfBgNVHSME +GDAWgBSJMLOAlJB9pg5qZNy7+hyrko1TgTAPBgkrBgEFBQcwAQUEAgUAMA4GA1Ud +DwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCTAMBgNVHRMBAf8EAjAA +MA0GCSqGSIb3DQEBDQUAA4ICAQAaFbhL/2WYtZwFCpLI7xsxxX0k8l3MgVP+6TRv +brM0QGu6PGda9wfxnlkW0S/vx7HPFD7ncczPzxkaekDbfgDXbuvezryCpGNz+8Qt +wCVoqEKmWE3YsBxEb1/v9lXoPQx1zxc5gup6JTC0/EWdZcKhdskj5PX46S4M+akc +TbMKJ/p46Au+2zTevlreZTJVT11/zjaiUAJLyRPE2BUWWYfhIB3yapUJ0XcCFTT3 +SGIIa5Nec+jRcuDoyZZdIQAhqrSkiVDBJ39oz4eXQ8Q0dQYcLIyhdjuBrnrOi90m +BK0Xgvgu5wc5fxmPinKMbcuUg0v3jJ4EF5fg+XEohPDI1Wr0kywjfmSYSNhhID0T +i1J/L6O0AP0EV2xc5E8HolpbV1/qWg9InI4BlyFMY1VRcd52Sv9eblxPE8wpjdwv +FPTwhJ/4Cz/AzOPQVrHSbzdJLJIzHrbCxhOMJg3z595INy8RaoLM+maya1lS44ZA +0y34dO5DSqip7+HUFPUPH3IFSN2z02VUT4UVy4qLav9UWdwujXihUebSD5lgPF8n +HCFfXnsx61yxiU1dzJ7KtjE00R5/MhtFfC8B8H281nLm0t83+uZs+fRlNh2aGkMJ +/cBiM6NGnNAD6Wi0W/q7GgVLJL0Vv94e4nNP0ZUE6UZ5N7YEHJNdotURGen4hy0s +n2FL0g== +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANndjteBfEQLLqL+ +QY0rbDe/+qfvKXzHSZQ7h4jet0WHYK6FZaeafc1UPBTzgUXjIM8CTVDlLw+6M5cT +vV3bLWm/UnEMs4lTWaaSoBrP08BpvH0HiKkzmq5bNhjLb6khwFQyBx0rVGx0MEea +2XbvloprTUUTAIagX6LswxuxCdDxAgMBAAECgYEAn0Vo0sWXugrOulvUQkb3Yz5X +GYQvOUhb0yE98WKYax0QIiXlbgT0aTJmhg6KeDQWXR/atAQIRqAibRUCQGYmKKpi ++pImH/5O7bGakm9wkKqHp3ETPH1iHdXNaRdQ0kqlqFPojGA22ACprd45sNsWz9+E +v8IRG4CSuDuBhmOCtAECQQDug6x2zUmSHdALnkKvo0SlFOzhARtYk/+iei0rxxne +bTmD6BRALff2293555HFWrkAKi+U53yOPbqyF80YUwSxAkEA6dZbBLml8+H7D8V4 +g4PPjLhEJlKlzlaH3bKBlUsDI6vqEzSPrSVlWtSDk3NCi2xz0baX49bUBTNb9QxM +TzegQQJAVHLCiX294sIzeymZqt4/28NA65mcuQwNotVnUOy5uAssJgvxv5eHCBxo +x6a58gphHjHRjwM3EpXAmHXc5BPgwQJBAOAvkrPFCUX4sis8t33fMW1IfI4BTtW0 +QC5cpb/sWuYoPBKNofHumG2ssTj8mB/zqla1KIpXvI3/33nggIiyXAECQQCMW6oi +Ja+VPOIiHxsrrTtMu12kE3j5GYn+JbkFsoZqP7nNJwzVS/LNRjvPwWzgfGCM9A0B +HR7T1oYpUMTBvIJH +-----END PRIVATE KEY----- diff --git a/examples/data/cert/Root CA Test.crt b/examples/data/cert/Root CA Test.crt new file mode 100644 index 00000000..d85ad291 --- /dev/null +++ b/examples/data/cert/Root CA Test.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE7DCCAtSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDEwxSb290 +IENBIFRlc3QwIhgPMjAyNDA0MTkxMzM5NDhaGA8yMDY0MDQxOTEzMzk0OFowFzEV +MBMGA1UEAxMMUm9vdCBDQSBUZXN0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEA4TNbja25gwf5Np8vo4KlIVQwOHEqJe4iv4iN6wupNT9jx6HHES/w8ZXG +/WgXbEUVsX9qsRyEe3DJwLtLXJN7gMdV9PFW8JXqUktI4cNAeh0HqXu1aj4KNI8y +OhPIa0GbWXpZVbCT+Eh9yYcC2iHSR2/sjCzVnmzfyT8ueseEEFFsoQQDe+o4nUeY +2Pk359SFpoPkgzDKPjpeMwrtAJhUWngEWW2JpaXoeWV3X88+WKCc+S3dY3kyo/oO +O+o1TcPOgb5S6hsqjfBgcMsnRKX/awo3i7Tv2wIxmsoCEFEu2ExJXONiMCFpzPhh +g/sHVADwPZd88Azr8JAvPpDXvTnrAsZvtMzwRoF1/Odf69/JpMiMWwmZ13Nc8onb +kXm8kCb/2J5e0dP/G3pCWoICm3Fso/OclHjdcji9dz/tZTkOcKr7HGvz8D/GQJ7c +3sKqeu8SMi6xJWupbVA/Q9HNBBdaRbfub0mvvaav1tdxGXPdC7UGJ0m5UDue8Kai +HG2FovwWCA3N6z4ZVkcaNeXqZTF/AwnL5zjA/NYroBgrsj8axuvYC2Z7yIIrJH+k +RiffFfqFO7IVdrrAqEqZ1sY5tms2NGkOYNU0QBgWUdxLhu46Qn5cNe5vTUn5wPTj +bOOs2IQkXBigCvwTD8QCuvnctvR3YOLT4Qt6ggfehosZLpKxfB0CAwEAAaM/MD0w +CwYDVR0PBAQDAgGGMB0GA1UdDgQWBBSJMLOAlJB9pg5qZNy7+hyrko1TgTAPBgNV +HRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAyM1IlSiyJ5J2PfiBRk6sg +bkn62jZhQbO5Lznp5pj26liRPt+BhXqUDUAyz27jMjsi8JNMlFA7AX/GMnR5TFrP +j6HLIoJvH/oiyOw/wNTImqxBa1W3MZvAC1hp2CwkdS5oe4SL4biALfIiYkHPWsmZ +BwnWueXLwUnroKjCLzeG4NmY0kSO6MXFSl1fnp14TuqzHvLizQt7qIuKd+D502Wd +B6T7B3zxUXEJEa643fAN4qHQd/PUhkkC76V1eZ3st7gR+8fs37QABJwK5WWAcv6l +yc4tRuLfkV8sAHuH1AxGbQsT+eCM38qEw2ftTMkdDfvjeBvDf4y9ELAIhsw6o1Sa +nEzFLhRRENpEzgXJoASTNEbNqy7sVjYzxLt1+q0xpPOdn5pF0J1xbkOirTd9YzUG +NIcF6QF5nVHYgA13/0pvhV2Zjlu4q70+hNrbkr+pyJ/8XQwScjWsz0l3PQNpdTvC +pAnuVuQ0qpVBGZ5US9Jp4LhTKi1PIUM7Pfer22TuGZZqihwdgUWlqpWgfY8G4t5z +b0LPPLZGR5kRZIeUq2IvlqgLIERKsxaUrWR6LVIZgvhEfxibN36B88SzcY1eAjXo +MvWHBl2/zTvumujSqvHxoABnZ0Wgman+kH1JiDEbSbf0FNgGQQLM1W1FTFEi3o4J +9Kq9zlhM4WiI5uds/Mz5bA== +-----END CERTIFICATE----- diff --git a/examples/data/cert/RootCATest.der.crl b/examples/data/cert/RootCATest.der.crl new file mode 100644 index 00000000..758e2fbc Binary files /dev/null and b/examples/data/cert/RootCATest.der.crl differ diff --git a/examples/data/cert/RootCATest.pem.crl b/examples/data/cert/RootCATest.pem.crl new file mode 100644 index 00000000..ca91d4d7 --- /dev/null +++ b/examples/data/cert/RootCATest.pem.crl @@ -0,0 +1,17 @@ +-----BEGIN X509 CRL----- +MIICxzCBsAIBATANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDEwxSb290IENBIFRl +c3QXDTI0MDQyMjIwMTUwMFoXDTQ0MDQxNzIwMTUwMFowFDASAgE2Fw0yNDA0MjIy +MDE0MTdaoE8wTTA/BgNVHSMEODA2gBSJMLOAlJB9pg5qZNy7+hyrko1TgaEbpBkw +FzEVMBMGA1UEAxMMUm9vdCBDQSBUZXN0ggEBMAoGA1UdFAQDAgENMA0GCSqGSIb3 +DQEBCwUAA4ICAQCiVOeVvHpOR3bXJLLS6U5AmGGw0lXqz6UyxjlLi/uabu7nrxh9 +AEhYzGk7I0XsC0LQlFPBjhwAzA8iOV+EBL8KW+tq+xan3BOz0fXtC5WY/j7Vb/8G +SnoASb4u6MsERtgDXOaGZiFdGBe28iUi+spn0TrV3nn6RRvX9EjwAmJwrwRCNDoX +mkcUEcJLbhtH56/VvLCSFvbo/mMrV7wnd8S+fN/vbMh6b5dPgaJ0jMgSq2TrVZJX +tS61olA4ocfc9Se0FPfstUXKD2S58gvSbyAzxYKXNHTy+RASmMdKOeGdcIaN4p8M +X0qlMSBi4BBpvgzrogUCYCxkUiDzO0EAZ0hwWiLE4bPLZQDMrgR3eFO/pj/I6Umb +PnOlVodE0HUHX8aH4AUemhV+4hpezmTin6ysick4QxZxSBkzKK4E/q6b4ii3qkpt +kzzp3C5tfJGUvI2JE+NjDVPYL0lr3pCbWjNtQvQ6pugvzbqUEreZkr+oX3tCoAaX +Lqt4tN0d9JBh+/Hd5uIfUyPRwUlPIT0AUi/4MQqPOPfbemLOxoLZY1Zp+3D/F40E +xD64gmPmnOeKWggHiN2WIZoEF67RoZpVhwm0ehN1LwSw+tKW0J4goTcZqxzQ+hWp +5NhIzkocy5Z/3uRtZcAMs/kzbekfC+1JFXhA56iq+VUNk07DIbJa4Oavtg== +-----END X509 CRL----- diff --git a/examples/example_012.pdf b/examples/example_012.pdf deleted file mode 100644 index eec8ee0c..00000000 Binary files a/examples/example_012.pdf and /dev/null differ diff --git a/examples/example_052.php b/examples/example_052.php index 681acaad..9c07b23d 100644 --- a/examples/example_052.php +++ b/examples/example_052.php @@ -76,8 +76,10 @@ */ // set certificate file -//$certificate = 'file://data/cert/tcpdf.crt'; -$certificate = file_get_contents('data/cert/tcpdf.crt'); +$certificate = file_get_contents("data/cert/PDF User.pem"); +$ca = "data/cert/Root CA Test.crt"; +$extracerts = getcwd()."/$ca"; +$crl = "data/cert/RootCATest.der.crl"; // set additional information $info = array( @@ -87,11 +89,18 @@ 'ContactInfo' => 'http://www.tcpdf.org', ); +// set LTV setLtv($ocspURI=null, $crlURIorFILE=null, $issuerURIorFILE=null) +// set null to skip ocsp or crl +// set to empty or false will continue lookup in certificate attributes (Authority Info Access(AIA) and CRL Distribution Points(CDP)) +// skip both will result no LTV. Note that issuerURIorFILE cannot skipped. It will use given address/file location or lookup in cert AIA attributes if null. +// dont give any parameter to lookup all address in cert attributes. +$pdf->setLtv(null, $crl, $ca); + // Set TSA server address $pdf->setTimeStamp('http://timestamp.apple.com/ts01'); // set document signature -$pdf->setSignature($certificate, $certificate, 'tcpdfdemo', '', 2, $info); +$pdf->setSignature($certificate, $certificate, 'tcpdfdemo', $extracerts, 2, $info); // set font. 'helvetica' MUST be used to avoid a PHP notice from PHP 7.4+ $pdf->setFont('helvetica', '', 12); @@ -121,7 +130,12 @@ //Close and output PDF document $pdf->Output('example_052.pdf', 'D'); +//$r=$pdf->Output('example_052.pdf', 'S'); +//$h = fopen('../example_052.pdf','w'); +//fwrite($h, $r); +//fclose($h); //============================================================+ // END OF FILE //============================================================+ +?> \ No newline at end of file diff --git a/include/tcpdf_cmssignature.php b/include/tcpdf_cmssignature.php index 304b5267..84138454 100644 --- a/include/tcpdf_cmssignature.php +++ b/include/tcpdf_cmssignature.php @@ -3,543 +3,1893 @@ // File name : tcpdf_cmssignature.php // Version : 1.0.1 // Begin : 2023-05-25 -// Last Update : 2023-05-25 +// Last Update : 2024-04-21 // Author : Hida - https://github.com/hidasw // License : GNU GPLv3 */ /** - * @class tcpdf_cms_signature + * @class tcpdf_cmssignature * Manage CMS Signature for TCPDF. - * @version 1.0.001 + * @version 1.1 * @author M Hida */ -class tcpdf_cms_signature { - /** - * Catch error. - * @public - */ - public $errorMsg; - - /** - * result value of pkcs7 EncryptedDigest. - * @public - */ - public $pkcs7_EncryptedDigest; - - /** - * private array parsed of pkcs7 data. - * @private - */ - private $pkcs7_dataArray; - - /** - * futher implementation - * @public - */ - public function __construct() { - - } - - /** - * Create common tsa query with SHA1 digest, nonce and cert req extension - * @param string $binaryData raw/binary data of tsa query - * @return string binary tsa query - * @public - */ - public function tsa_query($binaryData) { - $hash = hash('sha1', $binaryData); - $tsReqData = tcpdf_asn1::seq( - tcpdf_asn1::int(1). - tcpdf_asn1::seq( - tcpdf_asn1::seq("06052B0E03021A"."0500"). // object OBJ_sha1 - tcpdf_asn1::oct($hash)). - tcpdf_asn1::int(hash('crc32', rand())). // tsa nonce - '0101ff' // req return cert - ); - return hex2bin($tsReqData); - } - - /** - * function to get response header of specific name - * @param string $headerkeyName header name eg Content-Type - * @param string $headerData text of header - * @return string value of header name - * @protected - */ - protected function getResponseHeader($headerkeyName, $headerData) { - $headers = explode("\n", $headerData); - foreach ($headers as $key => $r) { - // Match the header name up to ':', compare lower case - if (stripos($r, $headerkeyName . ':') === 0) { - list($headername, $headervalue) = explode(":", $r, 2); - return trim($headervalue); - } - } - } - - /** - * send tsa query with curl - * @param string $binarytsReqData binary ts query - * @param string $tsa_host='', $tsa_username='', $tsa_password='' - * @return string tsa response - * @public - */ - public function tsa_send($binarytsReqData, $tsa_host='', $tsa_username='', $tsa_password='') { - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $tsa_host); - if(isset($tsa_username) && isset($tsa_password)) { - curl_setopt($ch, CURLOPT_USERPWD, $tsa_username . ":" . $tsa_password); - } - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_HEADER, 1); - curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/timestamp-query','User-Agent: TCPDF')); - curl_setopt($ch, CURLOPT_POSTFIELDS, $binarytsReqData); - $tsResponse = curl_exec($ch); - - if($tsResponse) { - $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); - curl_close($ch); - $header = substr($tsResponse, 0, $header_size); - $body = substr($tsResponse, $header_size); - - // Get the HTTP response code - $headers = explode("\n", $header); - foreach ($headers as $key => $r) { - if (stripos($r, 'HTTP/') === 0) { - list(,$code, $status) = explode(' ', $r, 3); - break; - } - } - if($code != '200') { - $this->errorMsg = "tsa Response error! Code: $code, Status: $status."; - return false; - } - $contentTypeHeader = $this->getResponseHeader("Content-Type", $header); - if($contentTypeHeader != 'application/timestamp-reply') { - $this->errorMsg = "tsa response content type not application/timestamp-reply, but: $contentTypeHeader."; - return false; - } - return $body; // binary response - } - } - - /** - * parse tsa response to array - * @param string $binaryTsaRespData binary tsa response - * @return array asn.1 hex structure of tsa response - * @private - */ - private function tsa_parseResp($binaryTsaRespData) { - $tcpdf_asn1 = new tcpdf_asn1; - if(!@$ar = $tcpdf_asn1->parse_recursive(bin2hex($binaryTsaRespData), 3)) { - $this->errorMsg = "can't parse invalid tsa Response."; - return false; - } - - $curr = $ar; - foreach($curr as $key=>$value) { - if($value['type'] == '30') { - $curr['TimeStampResp']=$curr[$key]; - unset($curr[$key]); - - } - } - $ar=$curr; - - $curr = $ar['TimeStampResp']; - foreach($curr as $key=>$value) { - if(is_numeric($key)) { - if($value['type'] == '30' && !array_key_exists('status', $curr)) { - $curr['status']=$curr[$key]; - unset($curr[$key]); - } else if($value['type'] == '30') { - $curr['timeStampToken']=$curr[$key]; - unset($curr[$key]); - } - } - } - $ar['TimeStampResp']=$curr; - - $curr = $ar['TimeStampResp']['timeStampToken']; - foreach($curr as $key=>$value) { - if(is_numeric($key)) { - if($value['type'] == '06') { - $curr['contentType']=$curr[$key]; - unset($curr[$key]); - } - if($value['type'] == 'a0') { - $curr['content']=$curr[$key]; - unset($curr[$key]); - } - } - } - $ar['TimeStampResp']['timeStampToken'] = $curr; - - $curr = $ar['TimeStampResp']['timeStampToken']['content']; - foreach($curr as $key=>$value) { - if(is_numeric($key)) { - if($value['type'] == '30') { - $curr['TSTInfo']=$curr[$key]; - unset($curr[$key]); - } - } - } - $ar['TimeStampResp']['timeStampToken']['content'] = $curr; - - if(@$ar['TimeStampResp']['timeStampToken']['content']['hexdump'] != '') { - return $ar; - } else { - $this->errorMsg = "TimeStampResp data not valid."; - return false; - } - } - - /** - * append tsa data to pkcs7 signature - * @param string $binaryTsaResp binary tsa response - * @return string hex pkcs7 with tsa. - * @public - */ - public function pkcs7_appendTsa($binaryTsaResp) { - if(!@$tsaResp = $this->tsa_parseResp($binaryTsaResp)) { - $this->errorMsg = "can't parse tsa response"; - return false; - } - $TSTInfo = $tsaResp['TimeStampResp']['timeStampToken']['hexdump']; - - $TimeStampToken = tcpdf_asn1::seq( - "060B2A864886F70D010910020E". - tcpdf_asn1::set( - $TSTInfo - ) - ); - - $time = tcpdf_asn1::seq( - $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['signerinfos']['signerinfo']['value_hex']. // ocsp & crl is here - tcpdf_asn1::explicit(1,$TimeStampToken) - ); - $pkcs7contentSignedData=tcpdf_asn1::seq( - $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['version']['hexdump']. - $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['digestAlgorithms']['hexdump']. - $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['contentInfo']['hexdump']. - $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['certificates']['hexdump']. - tcpdf_asn1::set($time) - ); - $pkcs7ContentInfo = tcpdf_asn1::seq( - "06092A864886F70D010702". - tcpdf_asn1::explicit(0,$pkcs7contentSignedData) - ); - return $pkcs7ContentInfo; - } - - /** - * insert pkcs7 signature to parsed - * @param string $hex hex pkcs7 - * @return array pkcs7 form - * @set pkcs7_dataArray array pkcs7 form - * @set pkcs7_EncryptedDigest string hex pkcs7 EncryptedDigest value - * @public - */ - public function pkcs7_data($hex) { - $tcpdf_asn1 = new tcpdf_asn1; - if(!@$ar = $tcpdf_asn1->parse_recursive($hex, 5)) { - $this->errorMsg = "can't parse pkcs7 data."; - return false; - } - - $curr = $ar; - foreach($curr as $key=>$value) { - if($value['type'] == '30') { - $curr['ContentInfo']=$curr[$key]; - unset($curr[$key]); - } - } - $ar=$curr; - - $curr = $ar['ContentInfo']; - foreach($curr as $key=>$value) { - if(is_numeric($key)) { - if($value['type'] == '06') { - $curr['ContentType']=$curr[$key]; - unset($curr[$key]); - } else if($value['type'] == 'a0') { - $curr['content']=$curr[$key]; - unset($curr[$key]); - } - } - } - $ar['ContentInfo'] = $curr; - - $curr = $ar['ContentInfo']['content']; - foreach($curr as $key=>$value) { - if(is_numeric($key)) { - if($value['type'] == '30') { - $curr['SignedData']=$curr[$key]; - unset($curr[$key]); - } - } - } - $ar['ContentInfo']['content'] = $curr; - - $curr = $ar['ContentInfo']['content']['SignedData']; - foreach($curr as $key=>$value) { - if(is_numeric($key)) { - if($value['type'] == '02') { - $curr['version']=$curr[$key]; - unset($curr[$key]); - } else if($value['type'] == '31' && !array_key_exists('digestAlgorithms', $curr)) { - $curr['digestAlgorithms']=$curr[$key]; - unset($curr[$key]); - }else if($value['type'] == '30') { - $curr['contentInfo']=$curr[$key]; - unset($curr[$key]); - }else if($value['type'] == 'a0') { - $curr['certificates']=$curr[$key]; - unset($curr[$key]); - }else if($value['type'] == 'a1') { - $curr['crls']=$curr[$key]; - unset($curr[$key]); - }else if($value['type'] == '31') { - $curr['signerinfos']=$curr[$key]; - unset($curr[$key]); - } - } - } - $ar['ContentInfo']['content']['SignedData']=$curr; - - $curr = $ar['ContentInfo']['content']['SignedData']['signerinfos']; - foreach($curr as $key=>$value) { - if(is_numeric($key)) { - if($value['type'] == '30') { - $curr['signerinfo']=$curr[$key]; - unset($curr[$key]); - } - } - } - $ar['ContentInfo']['content']['SignedData']['signerinfos'] = $curr; - - $curr=$ar['ContentInfo']['content']['SignedData']['signerinfos']['signerinfo']; - foreach($curr as $key=>$value) { - if(is_numeric($key)) { - if($value['type'] == '02') { - $curr['version']=$curr[$key]; - unset($curr[$key]); - } else if($value['type'] == '30' && !array_key_exists('issuerAndSerialNumber', $curr)) { - $curr['issuerAndSerialNumber']=$curr[$key]; - unset($curr[$key]); - } else if($value['type'] == '30'&& !array_key_exists('digestAlgorithm', $curr)) { - $curr['digestAlgorithm']=$curr[$key]; - unset($curr[$key]); - } else if($value['type'] == 'a0') { - $curr['authenticatedAttributes']=$curr[$key]; - unset($curr[$key]); - } else if($value['type'] == '30') { - $curr['digestEncryptionAlgorithm']=$curr[$key]; - unset($curr[$key]); - } else if($value['type'] == '04') { - $curr['encryptedDigest']=$curr[$key]; - unset($curr[$key]); - } - } - } - $ar['ContentInfo']['content']['SignedData']['signerinfos']['signerinfo']=$curr; - - $this->pkcs7_dataArray = $ar; - $this->pkcs7_EncryptedDigest=hex2bin($ar['ContentInfo']['content']['SignedData']['signerinfos']['signerinfo']['encryptedDigest']['value_hex']); - return $ar; - } +class tcpdf_cmssignature { + + /** + * string to logged + */ + public $log; + + /** + * write log to file + */ + public $writeLog = true; + + /** + * log file + */ + public $logFile = '../log.txt'; + + + /** + * filter log + */ + public $logFilter = false; + + /** + * result value of pkcs7 EncryptedDigest. + * @public + */ + public $pkcs7_EncryptedDigest; + + /** + * private array parsed of pkcs7 data. + * @private + */ + private $pkcs7_dataArray; + + /** + * logging at end + * @public + */ + public function __destruct() { + if($this->writeLog) { + $logTime = date("Y-m-d H:i:s"); + $logs = "========== START LOG ==========\n"; + $logs .= "$logTime\n"; + $arrLog = explode("\n", $this->log); + $newlines = ''; + foreach($arrLog as $line) { + $head = trim(substr($line, 0, strpos($line, ":"))); + $newhead = str_pad($head, 10, " "); + $ct = rtrim(substr($line, strpos($line, ":")+1)); + $newline = "$newhead:$ct\n"; + if($this->logFilter) { + if(strtolower($head) != strtolower($this->logFilter)) { + $newline = ""; + } + } + $newlines .= $newline; + } + $logs .= $newlines; + $logs .= "========== END LOG ==========\n\n"; + if(@$h = fopen($this->logFile, 'w')) { + fwrite($h, $logs); + fclose($h); + } + } + } + + /** + * function to get response header of specific name + * @param string $headerkeyName header name eg Content-Type + * @param string $headerData text of header + * @return string value of header name + * @protected + */ + protected function getResponseHeader($headerkeyName, $headerData) { + $headers = explode("\n", $headerData); + foreach ($headers as $key => $r) { + // Match the header name up to ':', compare lower case + if (stripos($r, $headerkeyName . ':') === 0) { + list($headername, $headervalue) = explode(":", $r, 2); + return trim($headervalue); + } + } + } + + /** + * send tsa/ocsp query with curl + * @param string $binarytsReqData binary ts query + * @param string $tsa_host='', $tsa_username='', $tsa_password='' + * @return string tsa response + * @public + */ + public function sendReq($reqData) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $reqData['uri']); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: {$reqData['req_contentType']}",'User-Agent: TCPDF')); + curl_setopt($ch, CURLOPT_POSTFIELDS, $reqData['data']); + $tsResponse = curl_exec($ch); + + if($tsResponse) { + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + curl_close($ch); + $header = substr($tsResponse, 0, $header_size); + $body = substr($tsResponse, $header_size); + + // Get the HTTP response code + $headers = explode("\n", $header); + foreach ($headers as $key => $r) { + if (stripos($r, 'HTTP/') === 0) { + list(,$code, $status) = explode(' ', $r, 3); + break; + } + } + if($code != '200') { + $this->log .= "error: sendReq end. Response error! Code=\"$code\", Status=\"".trim($status)."\"\n"; + return false; + } + $contentTypeHeader = $this->getResponseHeader("Content-Type", $header); + if($contentTypeHeader != $reqData['resp_contentType']) { + $this->log = "error: sendReq end. response content type not {$reqData['resp_contentType']}, but: $contentTypeHeader.\n"; + return false; + } + return $body; // binary response + } + } + + /** + * parse tsa response to array + * @param string $binaryTsaRespData binary tsa response to parse + * @return array asn.1 hex structure of tsa response + * @private + */ + private function tsa_parseResp($binaryTsaRespData) { + if(!@$ar = asn1::parse(bin2hex($binaryTsaRespData), 3)) { + $this->log = "can't parse invalid tsa Response."; + return false; + } + + $curr = $ar; + foreach($curr as $key=>$value) { + if($value['type'] == '30') { + $curr['TimeStampResp']=$curr[$key]; + unset($curr[$key]); + } + } + $ar=$curr; + + $curr = $ar['TimeStampResp']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '30' && !array_key_exists('status', $curr)) { + $curr['status']=$curr[$key]; + unset($curr[$key]); + } else if($value['type'] == '30') { + $curr['timeStampToken']=$curr[$key]; + unset($curr[$key]); + } + } + } + $ar['TimeStampResp']=$curr; + + $curr = $ar['TimeStampResp']['timeStampToken']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '06') { + $curr['contentType']=$curr[$key]; + unset($curr[$key]); + } + if($value['type'] == 'a0') { + $curr['content']=$curr[$key]; + unset($curr[$key]); + } + } + } + $ar['TimeStampResp']['timeStampToken'] = $curr; + + $curr = $ar['TimeStampResp']['timeStampToken']['content']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '30') { + $curr['TSTInfo']=$curr[$key]; + unset($curr[$key]); + } + } + } + $ar['TimeStampResp']['timeStampToken']['content'] = $curr; + + if(@$ar['TimeStampResp']['timeStampToken']['content']['hexdump'] != '') { + return $ar; + } else { + $this->log = "TimeStampResp data not valid."; + return false; + } + } + + /** + * append tsa data to pkcs7 signature + * @return string hex pkcs7 with tsa. + * @public + */ + public function pkcs7_appendTsa() { + $tsaQuery = x509::tsa_query($this->pkcs7_EncryptedDigest); + $reqData = array( + 'data'=>$tsaQuery, + 'uri'=>$this->tsa_data['tsa_host'], + 'req_contentType'=>'application/timestamp-query', + 'resp_contentType'=>'application/timestamp-reply' + ); + $this->log .= "info: sending TSA query to \"".$this->tsa_data['tsa_host']."\"..."; + if(!$binaryTsaResp = self::sendReq($reqData)) { + $this->log .= "error: Can't send TSA Request to: \"".$this->tsa_data['tsa_host']."\"\n"; + } else { + $this->log .= "OK\n"; + } + if(!$tsaResp = $this->tsa_parseResp($binaryTsaResp)) { + $this->log .= "can't parse tsa resdponse:".($binaryTsaResp); + return false; + } + $TSTInfo = $tsaResp['TimeStampResp']['timeStampToken']['hexdump']; + $TimeStampToken = asn1::seq( + "060B2A864886F70D010910020E". + asn1::set( + $TSTInfo + ) + ); + $time = asn1::seq( + $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['signerinfos']['signerinfo']['value_hex']. // ocsp & crl is here + asn1::expl(1,$TimeStampToken) + ); + $pkcs7contentSignedData=asn1::seq( + $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['version']['hexdump']. + $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['digestAlgorithms']['hexdump']. + $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['contentInfo']['hexdump']. + $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['certificates']['hexdump']. + asn1::set($time) + ); + $pkcs7ContentInfo = asn1::seq( + "06092A864886F70D010702". + asn1::expl(0,$pkcs7contentSignedData) + ); + return $pkcs7ContentInfo; + } + + /** + * append LTV to pkcs7 signature + * @return string hex pkcs7 with ltv embeded. + * @public + */ + public function pkcs7_appendLtv() { + $this->log .= "info: cms append Ltv start.\n"; + $issuerURIorFILE = $this->signature_ltv_data['issuerURIorFILE']; + $ocspURI = $this->signature_ltv_data['ocspURI']; + $crlURIorFILE = $this->signature_ltv_data['crlURIorFILE']; + + $x509 = new x509; + $certSigner_parse = $x509::readcert($this->signature_data['signcert'], 'oid'); + + $this->log .= "info: getting OCSP address...\n"; + if($ocspURI!==null && empty($ocspURI)) { + $this->log .= "info: OCSP address is not set. try getting from certificate ocsp aia attribute..."; + $ocspURI = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.1'][0]; + $this->log .= (empty($ocspURI))?"FAILED\n":"OK. ocsp=\"$ocspURI\"\n"; + } else { + $this->log .= "info: OCSP address is set manually to \"$ocspURI\"\n"; + } + + $this->log .= "info: getting CRL address...\n"; + if($crlURIorFILE!==null && empty($crlURIorFILE)) { + $this->log .= "info: CRL uri or file is not set. try getting from certificate crl cdp attribute..."; + $crlURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['2.5.29.31']['value'][0]; + $this->log .= (empty($crlURIorFILE))?"FAILED\n":"OK. crl=\"$crlURIorFILE\"\n"; + } else { + $this->log .= "info: CRL uri or file is set manually to \"$crlURIorFILE\"\n"; + } + + if(!empty($ocspURI) || !empty($crlURIorFILE)) { + $this->log .= "info: getting Issuer address...\n"; + if(!empty($issuerURIorFILE)) { + $issuer = $issuerURIorFILE; + $this->log .= "info: issuer location manually specified ($issuer)\n"; + } else { + $this->log .= "info: issuer location not specified use aia attribute from certificate..."; + if($issuer = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.2'][0]) { + $this->log .= "OK. issuer=\"$issuer\"\n"; + } else { + $this->log .= "FAIL! cant get issuer address\n"; + return false; + } + } + + $this->log .= "info: getting issuer from \"$issuer\"..."; + if($issuerCert = @file_get_contents($issuer)) { + $this->log .= "OK. size ".round(strlen($issuerCert)/1024,2)."Kb\n"; + $this->log .= "info: reading issuer certificate..."; + if($issuer_certDER = x509::get_cert($issuerCert)) { + $this->log .= "OK\n"; + $this->log .= "info: check if issuer is cert issuer..."; + $certIssuer_parse = $x509::readcert($issuer_certDER, 'oid'); + $certSigner_signatureField = $certSigner_parse['signatureValue']; + if(openssl_public_decrypt(hex2bin($certSigner_signatureField), $decrypted, $x509::x509_der2pem($issuer_certDER), OPENSSL_PKCS1_PADDING)) { + $this->log .= "OK.\n"; + } else { + $this->log .= "FAILED! CA is not issuer.\n"; + $this->log .= "error: ltv terminated. returning default signature.\n"; + return false; + } + } else { + $this->log .= "FAILED!\n"; + $this->log .= "error: ltv terminated. returning default signature.\n"; + return false; + } + } else { + $this->log .= "FAILED.\n"; + $this->log .= "error: ltv terminated. returning default signature.\n"; + } + } else { + $this->log .= "info: no ocsp/crl address info, ltv terminated!. returning default signature\n"; + return false; + } + + $appendOCSP = false; + if(!empty($ocspURI)) { + $this->log .= "info: OCSP start.\n"; + $ocspReq_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; + $ocspReq_issuerNameHash = $certIssuer_parse['tbsCertificate']['subject']['sha1']; + $ocspReq_issuerKeyHash = $certIssuer_parse['tbsCertificate']['subjectPublicKeyInfo']['sha1']; + $ocspRequestorSubjName = $certSigner_parse['tbsCertificate']['subject']['hexdump']; + $this->log .= "info: OCSP create request..."; + if($ocspReq = $x509::ocsp_request($ocspReq_serialNumber, $ocspReq_issuerNameHash, $ocspReq_issuerKeyHash, $this->signature_data['signcert'], $this->signature_data['privkey'], $ocspRequestorSubjName)) { + $this->log .= "OK.\n"; + $ocspBinReq = pack("H*", $ocspReq); + $reqData = array( + 'data'=>$ocspBinReq, + 'uri'=>$ocspURI, + 'req_contentType'=>'application/ocsp-request', + 'resp_contentType'=>'application/ocsp-response' + ); + $this->log .= "info: OCSP send request to \"$ocspURI\"..."; + if($ocspResp = self::sendReq($reqData)) { + $this->log .= "OK.\n"; + $this->log .= "info: OCSP parse response..."; + if($ocsp_parse = $x509::ocsp_response_parse($ocspResp, $return)) { + $this->log .= "OK.\n"; + $this->log .= "info: OCSP check cert validity..."; + $certStatus = $ocsp_parse['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses'][0]['certStatus']; + if($certStatus == 'valid') { + $this->log .= "VALID.\n"; + $ocspRespHex = $ocsp_parse['hexdump']; + $appendOCSP = asn1::expl(1, + asn1::seq( + $ocspRespHex + ) + ); + } else { + $this->log .= "invalid! status:\"$certStatus\"\n"; + } + } else { + $this->log .= "FAILED!. Ocsp server status \"$return\"\n"; + } + } else { + $this->log .= "error: OCSP send request FAILED!\n"; + } + } else { + $this->log .= "FAILED!\n"; + } + } + + $appendCRL = false; + if(!$appendOCSP) { // CRL not included if OCSP already valid + if(!empty($crlURIorFILE)) { + $this->log .= "info: getting crl from \"$crlURIorFILE\"..."; + if($crl = @file_get_contents($crlURIorFILE)) { + $this->log .= "OK. crl size ".round(strlen($crl)/1024,2)."Kb\n"; + $this->log .= "info: reading crl..."; + if($crlread=$x509->crl_read($crl)) { + $this->log .= "OK\n"; + $this->log .= "info: checking if crl issued by CA..."; + $crl_signatureField = $crlread['parse']['signature']; + if(openssl_public_decrypt(hex2bin($crl_signatureField), $decrypted, $x509::x509_der2pem($issuer_certDER), OPENSSL_PKCS1_PADDING)) { + $this->log .= "OK\n"; + $crl_parse=$crlread['parse']; + $crlCertValid=true; + $this->log .= "info: CRL check cert validity..."; + if(array_key_exists('revokedCertificates', $crl_parse['TBSCertList'])) { + $certSigner_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; + if(array_key_exists($certSigner_serialNumber, $crl_parse['TBSCertList']['revokedCertificates']['lists'])) { + $crlCertValid=false; + $this->log .= "FAILED! Certificate Revoked!\n"; + } + } + if($crlCertValid == true) { + $this->log .= "VALID\n"; + $crlHex = current(unpack('H*', $crlread['der'])); + $appendCRL = asn1::expl(0, + asn1::seq( + $crlHex + ) + ); + } + } else { + $this->log .= "FAILED\n"; + } + } else { + $this->log .= "FAILED!\n"; + } + } else { + $this->log .= "FAILED!\n"; + } + } + } + if($appendOCSP || $appendCRL) { + $appendLTV = asn1::seq("06092A864886F72F010108". // adbe-revocationInfoArchival (1.2.840.113583.1.1.8) + asn1::set( + asn1::seq( + $appendOCSP. + $appendCRL + ) + ) + ); + $authenticatedAttributes= $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['signerinfos']['signerinfo']['authenticatedAttributes']['value_hex']. + $appendLTV; + $tohash = asn1::set($authenticatedAttributes); + $hash = hash('sha256',hex2bin($tohash)); + $toencrypt = asn1::seq( + "300d06096086480165030402010500". // OBJ_sha256 and OBJ_null + asn1::oct( + $hash + ) + ); + openssl_private_encrypt(hex2bin($toencrypt), $encryptedDigest, $this->signature_data['privkey'],OPENSSL_PKCS1_PADDING); + $hexencryptedDigest = bin2hex($encryptedDigest); + + $signerinfos = asn1::seq( + $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['signerinfos']['signerinfo']['version']['hexdump']. + $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['signerinfos']['signerinfo']['issuerAndSerialNumber']['hexdump']. + $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['signerinfos']['signerinfo']['digestAlgorithm']['hexdump']. + asn1::expl(0,$authenticatedAttributes). + $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['signerinfos']['signerinfo']['digestEncryptionAlgorithm']['hexdump']. + asn1::oct($hexencryptedDigest) + ); + $pkcs7contentSignedData=asn1::seq( + $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['version']['hexdump']. + $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['digestAlgorithms']['hexdump']. + $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['contentInfo']['hexdump']. + $this->pkcs7_dataArray['ContentInfo']['content']['SignedData']['certificates']['hexdump']. + asn1::set($signerinfos) + ); + $pkcs7ContentInfo = asn1::seq( + "06092A864886F70D010702". // Hexadecimal form of pkcs7-signedData + asn1::expl(0,$pkcs7contentSignedData) + ); + return $pkcs7ContentInfo; + } else { + $this->log .= "error: nothing to do, no OCSP or CRL\n"; + } + } + + /** + * parsing pkcs7 signature data + * @param string $hex hex pkcs7 + * @return array pkcs7 form + * @set pkcs7_dataArray array pkcs7 form + * @set pkcs7_EncryptedDigest string hex pkcs7 EncryptedDigest value + * @public + */ + public function pkcs7_data($hex) { + // $tcpdf_asn1 = new tcpdf_asn1; + // if(!@$ar = $tcpdf_asn1->parse_recursive($hex, 6)) { + if(!@$ar = asn1::parse($hex, 6)) { + $this->errorMsg = "can't parse pkcs7 data."; + return false; + } + + $curr = $ar; + foreach($curr as $key=>$value) { + if($value['type'] == '30') { + $curr['ContentInfo']=$curr[$key]; + unset($curr[$key]); + } + } + $ar=$curr; + + $curr = $ar['ContentInfo']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '06') { + $curr['ContentType']=$curr[$key]; + unset($curr[$key]); + } else if($value['type'] == 'a0') { + $curr['content']=$curr[$key]; + unset($curr[$key]); + } + } + } + $ar['ContentInfo'] = $curr; + + $curr = $ar['ContentInfo']['content']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '30') { + $curr['SignedData']=$curr[$key]; + unset($curr[$key]); + } + } + } + $ar['ContentInfo']['content'] = $curr; + + $curr = $ar['ContentInfo']['content']['SignedData']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '02') { + $curr['version']=$curr[$key]; + unset($curr[$key]); + } else if($value['type'] == '31' && !array_key_exists('digestAlgorithms', $curr)) { + $curr['digestAlgorithms']=$curr[$key]; + unset($curr[$key]); + }else if($value['type'] == '30') { + $curr['contentInfo']=$curr[$key]; + unset($curr[$key]); + }else if($value['type'] == 'a0') { + $curr['certificates']=$curr[$key]; + unset($curr[$key]); + }else if($value['type'] == 'a1') { + $curr['crls']=$curr[$key]; + unset($curr[$key]); + }else if($value['type'] == '31') { + $curr['signerinfos']=$curr[$key]; + unset($curr[$key]); + } + } + } + $ar['ContentInfo']['content']['SignedData']=$curr; + + $curr = $ar['ContentInfo']['content']['SignedData']['signerinfos']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '30') { + $curr['signerinfo']=$curr[$key]; + unset($curr[$key]); + } + } + } + $ar['ContentInfo']['content']['SignedData']['signerinfos'] = $curr; + + $curr=$ar['ContentInfo']['content']['SignedData']['signerinfos']['signerinfo']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '02') { + $curr['version']=$curr[$key]; + unset($curr[$key]); + } else if($value['type'] == '30' && !array_key_exists('issuerAndSerialNumber', $curr)) { + $curr['issuerAndSerialNumber']=$curr[$key]; + unset($curr[$key]); + } else if($value['type'] == '30'&& !array_key_exists('digestAlgorithm', $curr)) { + $curr['digestAlgorithm']=$curr[$key]; + unset($curr[$key]); + } else if($value['type'] == 'a0') { + $curr['authenticatedAttributes']=$curr[$key]; + unset($curr[$key]); + } else if($value['type'] == '30') { + $curr['digestEncryptionAlgorithm']=$curr[$key]; + unset($curr[$key]); + } else if($value['type'] == '04') { + $curr['encryptedDigest']=$curr[$key]; + unset($curr[$key]); + } else if($value['type'] == 'a1') { + $curr['explicit1']=$curr[$key]; + unset($curr[$key]); + } + } + } + $ar['ContentInfo']['content']['SignedData']['signerinfos']['signerinfo']=$curr; + + + + $curr=$ar['ContentInfo']['content']['SignedData']['signerinfos']['signerinfo']['authenticatedAttributes']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '02') { + $curr['a']=$curr[$key]; + unset($curr[$key]); + } else if($value['type'] == '30' && !array_key_exists('contentType', $curr)) { + $curr['contentType']=$curr[$key]; + unset($curr[$key]); + } else if($value['type'] == '30'&& !array_key_exists('signingTime', $curr)) { + $curr['signingTime']=$curr[$key]; + unset($curr[$key]); + } else if($value['type'] == '30'&& !array_key_exists('md', $curr)) { + $curr['md']=$curr[$key]; + unset($curr[$key]); + } else if($value['type'] == '30'&& !array_key_exists('smimec', $curr)) { + $curr['smimec']=$curr[$key]; + unset($curr[$key]); + } else if($value['type'] == '30'&& !array_key_exists('other0', $curr)) { + $curr['other0']=$curr[$key]; + unset($curr[$key]); + } else if($value['type'] == '30'&& !array_key_exists('other1', $curr)) { + $curr['other1']=$curr[$key]; + unset($curr[$key]); + } else if($value['type'] == '30') { + $curr['zzzz']=$curr[$key]; + unset($curr[$key]); + } + } + } + $ar['ContentInfo']['content']['SignedData']['signerinfos']['signerinfo']['authenticatedAttributes']=$curr; + + $this->pkcs7_dataArray = $ar; + $this->pkcs7_EncryptedDigest=hex2bin($ar['ContentInfo']['content']['SignedData']['signerinfos']['signerinfo']['encryptedDigest']['value_hex']); + return $ar; + } +} + +/** + * @class x509 + * Perform some x509 operation + * @version 1.1 + * @author M Hida + */ +class x509 { + /* + * Create common tsa query with SHA1 digest, nonce and cert req extension + * @param string $binaryData raw/binary data of tsa query + * @return string binary tsa query + * @public + */ + public static function tsa_query($binaryData) { + $hash = hash('sha1', $binaryData); + $tsReqData = asn1::seq( + asn1::int(1). + asn1::seq( + asn1::seq("06052B0E03021A"."0500"). // object OBJ_sha1 + //asn1::seq("0609608648016503040201"."0500"). // object OBJ_sha256 + asn1::oct($hash) + ). + asn1::int(hash('crc32', rand()).'001'). // tsa nonce + '0101ff' // req return cert + ); + return hex2bin($tsReqData); + } + + /** + * Calculate 32 openssl subject hash old and new + * @param string $hex_subjSequence hex subject name sequence + * @return array subject hash old and new + */ + public static function opensslSubjHash($hex_subjSequence){ + $parse = asn1::parse($hex_subjSequence,3); + $hex_subjSequence_new=''; + foreach($parse[0] as $k=>$v) { + if(is_numeric($k)) { + $hex_subjSequence_new .= asn1::set( + asn1::seq( + $v[0][0]['hexdump']. + asn1::utf8(strtolower(hex2bin($v[0][1]['value_hex']))) + ) + ); + } + } + $tohash = pack("H*", $hex_subjSequence_new); + $openssl_subjHash_new = hash('sha1', $tohash); + $openssl_subjHash_new = substr($openssl_subjHash_new, 0, 8); + $openssl_subjHash_new = str_split($openssl_subjHash_new, 2); + $openssl_subjHash_new = array_reverse($openssl_subjHash_new); + $openssl_subjHash_new = implode("", $openssl_subjHash_new); + + $openssl_subjHash_old = hash('md5', hex2bin($hex_subjSequence)); + $openssl_subjHash_old = substr($openssl_subjHash_old, 0, 8); + $openssl_subjHash_old = str_split($openssl_subjHash_old, 2); + $openssl_subjHash_old = array_reverse($openssl_subjHash_old); + $openssl_subjHash_old = implode("", $openssl_subjHash_old); + + return array( + "old"=>$openssl_subjHash_old, + "new"=>$openssl_subjHash_new + ); + } + + /** + * Parsing ocsp response data + * @param string $binaryOcspResp binary ocsp response + * @return array ocsp response structure + */ + public static function ocsp_response_parse($binaryOcspResp, &$status='') { + $hex = current(unpack("H*", $binaryOcspResp)); + $parse = asn1::parse($hex,10); + if($parse[0]['type'] == '30') { + $ocsp = $parse[0]; + } else { + return false; + } + + //OCSPResponseStatus ::= ENUMERATED { + // successful (0), --Response has valid confirmations + // malformedRequest (1), --Illegal confirmation request + // internalError (2), --Internal error in issuer + // tryLater (3), --Try again later + // --(4) is not used + // sigRequired (5), --Must sign the request + // unauthorized (6) --Request unauthorized + //} + foreach($ocsp as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '0a') { + $ocsp['responseStatus']=$value['value_hex']; + unset($ocsp[$key]); + } + if($value['type'] == 'a0') { + $ocsp['responseBytes']=$value; + unset($ocsp[$key]); + } + } else { + //unset($ocsp[$key]); + unset($ocsp['depth']); + unset($ocsp['type']); + unset($ocsp['typeName']); + unset($ocsp['value_hex']); + } + } + if(@$ocsp['responseStatus'] != '00') { + $responseStatus['01']='malformedRequest'; + $responseStatus['02']='internalError'; + $responseStatus['03']='tryLater'; + $responseStatus['05']='sigRequired'; + $responseStatus['06']='unauthorized'; + $status = $responseStatus[$ocsp['responseStatus']]; + return false; + } + + if(!@$curr = $ocsp['responseBytes']) { + return false; + } + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '30') { + $curr['responseType']=self::oidfromhex($value[0]['value_hex']); + $curr['response']=$value[1]; + unset($curr[$key]); + } + } else { + unset($curr['typeName']); + unset($curr['type']); + unset($curr['depth']); + } + } + $ocsp['responseBytes'] = $curr; + + $curr = $ocsp['responseBytes']['response']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '30') { + $curr['BasicOCSPResponse']=$value; + unset($curr[$key]); + } + } else { + unset($curr['typeName']); + unset($curr['type']); + unset($curr['depth']); + } + } + $ocsp['responseBytes']['response'] = $curr; + + $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '30' && !array_key_exists('tbsResponseData', $curr)) { + $curr['tbsResponseData']=$value; + unset($curr[$key]); + continue; + } + if($value['type'] == '30' && !array_key_exists('signatureAlgorithm', $curr)) { + $curr['signatureAlgorithm']=$value[0]['value_hex']; + unset($curr[$key]); + continue; + } + if($value['type'] == '03') { + $curr['signature']=substr($value['value_hex'], 2); + unset($curr[$key]); + } + if($value['type'] == 'a0') { + foreach($value[0] as $certsK=>$certsV) { + if(is_numeric($certsK)) { + $certs[$certsK] = $certsV['value_hex']; + } + } + $curr['certs']=$certs; + unset($curr[$key]); + } + } else { + unset($curr['typeName']); + unset($curr['type']); + unset($curr['depth']); + } + } + $ocsp['responseBytes']['response']['BasicOCSPResponse'] = $curr; + + $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == 'a0') { + $curr['version']=$value[0]['value']; + unset($curr[$key]); + } + if($value['type'] == 'a1' && !array_key_exists('responderID', $curr)) { + $curr['responderID']=$value; + unset($curr[$key]); + } + if($value['type'] == 'a2') { + $curr['responderID']=$value; + unset($curr[$key]); + } + if($value['type'] == '18') { + $curr['producedAt']=$value['value']; + unset($curr[$key]); + } + if($value['type'] == '30') { + $curr['responses']=$value; + unset($curr[$key]); + } + if($value['type'] == 'a1') { + $curr['responseExtensions']=$value; + unset($curr[$key]); + } + } else { + unset($curr['typeName']); + unset($curr['type']); + unset($curr['depth']); + } + } + $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData'] = $curr; + + $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '30') { + $curr['lists']=$value; + unset($curr[$key]); + } + } else { + unset($curr['typeName']); + unset($curr['type']); + unset($curr['depth']); + } + } + $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions'] = $curr; + + $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions']['lists']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '30') { + if($value[0]['value_hex'] == '2b0601050507300102') { // nonce + $curr['nonce']=$value[0]['value_hex']; + } else { + $curr[$value[0]['value_hex']]=$value[1]; + } + unset($curr[$key]); + } + } else { + unset($curr['typeName']); + unset($curr['type']); + unset($curr['depth']); + } + } + $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions']['lists'] = $curr; + + $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses']; + $i=0; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + foreach($value as $SingleResponseK=>$SingleResponseV) { + if(is_numeric($SingleResponseK)) { + if($SingleResponseK == 0) { + foreach($SingleResponseV as $certIDk=>$certIDv) { + if(is_numeric($certIDk)) { + if($certIDv['type'] == '30') { + $certID['hashAlgorithm'] = $certIDv[0]['value_hex']; + } + if($certIDv['type'] == '04' && !array_key_exists('issuerNameHash', $certID)) { + $certID['issuerNameHash'] = $certIDv['value_hex']; + } + if($certIDv['type'] == '04') { + $certID['issuerKeyHash'] = $certIDv['value_hex']; + } + if($certIDv['type'] == '02') { + $certID['serialNumber'] = $certIDv['value_hex']; + } + } + } + $cert['certID'] = $certID; + } + if($SingleResponseK == 1) { + if($SingleResponseV['type'] == '82') { + $certStatus = 'unknown'; + } elseif($SingleResponseV['type'] == '80') { + $certStatus = 'valid'; + } else { + $certStatus = 'revoked'; + } + $cert['certStatus'] = $certStatus; + } + if($SingleResponseK == 2) { + $cert['thisUpdate'] = $SingleResponseV['value']; + } + if($SingleResponseK == 3) { + $cert['nextUpdate'] = $SingleResponseV[0]['value']; + } + if($SingleResponseK == 4) { + $cert['singleExtensions'] = $SingleResponseV; + } + } + } + $curr[$i] = $cert; + } else { + unset($curr[$key]); + unset($curr['typeName']); + unset($curr['type']); + unset($curr['depth']); + } + } + $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses'] = $curr; + + $arrModel = array( + 'responseStatus'=>'', + 'responseBytes'=>array( + 'response'=>'', + 'responseType'=>'' + ) + ); + + $differ=array_diff_key($arrModel,$ocsp); + if(count($differ) == 0) { + $differ=array_diff_key($arrModel['responseBytes'],$ocsp['responseBytes']); + if(count($differ) > 0) { + foreach($differ as $key=>$val) { + } + return false; + } + } else { + foreach($differ as $key=>$val) { + } + return false; + } + return $ocsp; + } + + /** + * Create ocsp request + * @param string $serialNumber serial number to check + * @param string $issuerNameHash sha1 hex form of issuer subject hash + * @param string $issuerKeyHash sha1 hex form of issuer subject public info hash + * @param string $signer_cert cert to sign ocsp request + * @param string $signer_key privkey to sign ocsp request + * @param string $subjectName hex form of asn1 subject + * @return string hex form ocsp request + */ + public static function ocsp_request($serialNumber, $issuerNameHash, $issuerKeyHash, $signer_cert = false, $signer_key = false, $subjectName=false) { + $Request = false; + $hashAlgorithm = asn1::seq( + "06052B0E03021A". // OBJ_sha1 + "0500" + ); + $issuerNameHash = asn1::oct($issuerNameHash); + $issuerKeyHash = asn1::oct($issuerKeyHash); + $serialNumber = asn1::int($serialNumber); + $CertID = asn1::seq($hashAlgorithm.$issuerNameHash.$issuerKeyHash.$serialNumber); + $Request = asn1::seq($CertID); // one request + if($signer_cert) { + $requestorName = asn1::expl("1", asn1::expl("4", $subjectName)); + } else { + $requestorName = false; + } + $requestList = asn1::seq($Request); // add more request into sequence + $rand = microtime (true)*rand(); + $nonce = md5(base64_encode($rand).$rand); + $ReqExts = asn1::seq( + '06092B0601050507300102'. // OBJ_id_pkix_OCSP_Nonce + asn1::oct("0410".$nonce) + ); + $requestExtensions = asn1::expl( "2", asn1::seq( + $ReqExts + ) + ); + $TBSRequest = asn1::seq($requestorName.$requestList.$requestExtensions); + $optionalSignature = ''; + if($signer_cert) { + if(!openssl_sign (hex2bin($TBSRequest), $signature_value, $signer_key)) { + return false; + //die("Ora bisa gawe signature maring request"); + } + $signatureAlgorithm = asn1::seq( + '06092A864886F70D010105'. // OBJ_sha1WithRSAEncryption. + "0500" + ); + $signature = asn1::bit("00".bin2hex($signature_value)); + $signer_cert = x509::x509_pem2der($signer_cert); + $certs = asn1::expl("0", asn1::seq(bin2hex($signer_cert))); + $optionalSignature = asn1::expl("0",asn1::seq($signatureAlgorithm.$signature.$certs)); + } + $OCSPRequest = asn1::seq($TBSRequest.$optionalSignature); + return $OCSPRequest; + } + + /** + * Convert crl from pem to der (binary) + * @param string $crl pem crl to convert + * @return string der crl form + */ + public static function crl_pem2der($crl) { + $begin = '-----BEGIN X509 CRL-----'; + $end = '-----END X509 CRL-----'; + $beginPos = stripos($crl, $begin); + if($beginPos===false) { + return false; + } + $crl = substr($crl, $beginPos+strlen($begin)); + $endPos = stripos($crl, $end); + if($endPos===false) { + return false; + } + $crl = substr($crl, 0, $endPos); + $crl = str_replace("\n", "", $crl); + $crl = str_replace("\r", "", $crl); + $dercrl = base64_decode($crl); + return $dercrl; + } + + /** + * Read crl from pem or der (binary) + * @param string $crl pem or der crl + * @return array der crl and parsed crl + */ + public static function crl_read($crl) { + if(!$crlparse=self::parsecrl($crl)) { // if cant read, thats not crl + return false; + } + if(!$dercrl=self::crl_pem2der($crl)) { // if not pem, thats already der + $dercrl=$crl; + } + $res['der'] = $dercrl; + $res['parse'] = $crlparse; + return $res; + } + + /** + * parsing crl from pem or der (binary) + * @param string $crl pem or der crl + * @param string $oidprint option show obj as hex/oid + * @return array parsed crl + */ + private static function parsecrl($crl, $oidprint = false) { + if($derCrl = self::crl_pem2der($crl)) { + $derCrl = bin2hex($derCrl); + } else { + $derCrl = bin2hex($crl); + } + + $curr = asn1::parse($derCrl, 7); + foreach($curr as $key=>$value) { + if($value['type'] == '30') { + $curr['crl']=$curr[$key]; + unset($curr[$key]); + } + } + $ar=$curr; + if(!array_key_exists('crl', $ar)) { + return false; + } + $curr = $ar['crl']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '30' && !array_key_exists('TBSCertList', $curr)) { + $curr['TBSCertList']=$curr[$key]; + unset($curr[$key]); + } + if($value['type'] == '30') { + $curr['signatureAlgorithm']=self::oidfromhex($value[0]['value_hex']); + unset($curr[$key]); + } + if($value['type'] == '03') { + $curr['signature']=substr($value['value'], 2); + unset($curr[$key]); + } + } else { + unset($curr[$key]); + } + } + $ar['crl'] = $curr; + + $curr = $ar['crl']['TBSCertList']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '02') { + $curr['version']=$curr[$key]['value']; + unset($curr[$key]); + } + if($value['type'] == '30' && !array_key_exists('signature', $curr)) { + $curr['signature']=$value[0]['value_hex']; + unset($curr[$key]); + continue; + } + if($value['type'] == '30' && !array_key_exists('issuer', $curr)) { + $curr['issuer']=$value; + unset($curr[$key]); + continue; + } + if($value['type'] == '17' && !array_key_exists('thisUpdate', $curr)) { + $curr['thisUpdate']=hex2bin($value['value_hex']); + unset($curr[$key]); + continue; + } + if($value['type'] == '17' && !array_key_exists('nextUpdate', $curr)) { + $curr['nextUpdate']=hex2bin($value['value_hex']); + unset($curr[$key]); + continue; + } + if($value['type'] == '30' && !array_key_exists('revokedCertificates', $curr)) { + $curr['revokedCertificates']=$value; + unset($curr[$key]); + continue; + } + if($value['type'] == 'a0') { + $curr['crlExtensions']=$curr[$key]; + unset($curr[$key]); + } + } else { + unset($curr[$key]); + } + } + $ar['crl']['TBSCertList'] = $curr; + + if(array_key_exists('revokedCertificates', $curr)) { + $curr = $ar['crl']['TBSCertList']['revokedCertificates']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '30') { + $serial = $value[0]['value']; + $revoked['time']=hex2bin($value[1]['value_hex']); + $lists[$serial]=$revoked; + unset($curr[$key]); + } + } else { + unset($curr['depth']); + unset($curr['type']); + unset($curr['typeName']); + } + } + $curr['lists'] = $lists; + $ar['crl']['TBSCertList']['revokedCertificates'] = $curr; + } + + if(array_key_exists('crlExtensions', $ar['crl']['TBSCertList'])) { + $curr = $ar['crl']['TBSCertList']['crlExtensions'][0]; + unset($ar['crl']['TBSCertList']['crlExtensions']); + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + $attributes_name = self::oidfromhex($value[0]['value_hex']); + if($oidprint == 'oid') { + $attributes_name = self::oidfromhex($value[0]['value_hex']); + } + if($oidprint == 'hex') { + $attributes_name = $value[0]['value_hex']; + } + $attributes_oid = self::oidfromhex($value[0]['value_hex']); + if($value['type'] == '30') { + $crlExtensionsValue = $value[1][0]; + if($attributes_oid == '2.5.29.20') { // OBJ_crl_number + $crlExtensionsValue = $crlExtensionsValue['value']; + } + if($attributes_oid == '2.5.29.35') { // OBJ_authority_key_identifier + foreach($crlExtensionsValue as $authority_key_identifierValueK=>$authority_key_identifierV) { + if(is_numeric($authority_key_identifierValueK)) { + if($authority_key_identifierV['type'] == '80') { + $authority_key_identifier['keyIdentifier'] = $authority_key_identifierV['value_hex']; + } + if($authority_key_identifierV['type'] == 'a1') { + $authority_key_identifier['authorityCertIssuer'] = $authority_key_identifierV['value_hex']; + } + if($authority_key_identifierV['type'] == '82') { + $authority_key_identifier['authorityCertSerialNumber'] = $authority_key_identifierV['value_hex']; + } + } + } + $crlExtensionsValue = $authority_key_identifier; + } + $attribute_list=$crlExtensionsValue; + } + $ar['crl']['TBSCertList']['crlExtensions'][$attributes_name] = $attribute_list; + } + } + } + + $curr = $ar['crl']['TBSCertList']['issuer']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '31') { + if($oidprint == 'oid') { + $subjOID = self::oidfromhex($curr[$key][0][0]['value_hex']); + } elseif($oidprint == 'hex') { + $subjOID = $curr[$key][0][0]['value_hex']; + } else { + $subjOID = self::oidfromhex($curr[$key][0][0]['value_hex']); + } + $curr[$subjOID][]=hex2bin($curr[$key][0][1]['value_hex']); + unset($curr[$key]); + + } + } else { + unset($curr['depth']); + unset($curr['type']); + unset($curr['typeName']); + if($key == 'hexdump') { + $curr['sha1']=hash('sha1', pack("H*", $value)); + } + } + } + $ar['crl']['TBSCertList']['issuer'] = $curr; + + $arrModel['TBSCertList']['version'] = ''; + $arrModel['TBSCertList']['signature'] = ''; + $arrModel['TBSCertList']['issuer'] = ''; + $arrModel['TBSCertList']['thisUpdate'] = ''; + $arrModel['TBSCertList']['nextUpdate'] = ''; + $arrModel['signatureAlgorithm'] = ''; + $arrModel['signature'] = ''; + + $crl = $ar['crl']; + $differ=array_diff_key($arrModel,$crl); + if(count($differ) == 0) { + $differ=array_diff_key($arrModel['TBSCertList'],$crl['TBSCertList']); + if(count($differ) > 0) { + foreach($differ as $key=>$val) { + } + return false; + } + } else { + foreach($differ as $key=>$val) { + } + return false; + } + return $ar['crl']; + } + + /** + * Convert x509 pem certificate to x509 der + * @param string $pem pem form cert + * @return string der form cert + */ + public static function x509_pem2der($pem) { + $x509_der = false; + if($x509_res = @openssl_x509_read($pem)) { + openssl_x509_export ($x509_res, $x509_pem); + $arr_x509_pem = explode("\n", $x509_pem); + $numarr = count($arr_x509_pem); + $i=0; + $cert_pem = false; + foreach($arr_x509_pem as $val) { + if($i > 0 && $i < ($numarr-2)) { + $cert_pem .= $val; + } + $i++; + } + $x509_der = base64_decode($cert_pem); + } + return $x509_der; + } + + /** + * Convert x509 der certificate to x509 pem form + * @param string $der_cert der form cert + * @return string pem form cert + */ + public static function x509_der2pem($der_cert) { + $x509_pem = "-----BEGIN CERTIFICATE-----\r\n"; + $x509_pem .= chunk_split(base64_encode($der_cert),64); + $x509_pem .= "-----END CERTIFICATE-----\r\n"; + return $x509_pem; + } + + /** + * get x.509 DER/PEM Certificate and return DER encoded x.509 Certificate + * @param string $certin pem/der form cert + * @return string der form cert + */ + public static function get_cert($certin) { + if($rsccert = @openssl_x509_read ($certin)) { + openssl_x509_export ($rsccert, $cert); + return self::x509_pem2der($cert); + } else { + $pem = @self::x509_der2pem($certin); + if($rsccert = @openssl_x509_read ($pem)) { + openssl_x509_export ($rsccert, $cert); + return self::x509_pem2der($cert); + } else { + return false; + } + } + } + + /** + * parse x.509 DER/PEM Certificate structure + * @param string $certin pem/der form cert + * @param string $oidprint show oid as oid number or hex + * @return array cert structure + */ + public static function readcert($cert_in, $oidprint=false) { + if(!$der = self::get_cert($cert_in)) { + return false; + } + $hex = bin2hex($der); + $curr = asn1::parse($hex,10); + foreach($curr as $key=>$value) { + if($value['type'] == '30') { + $curr['cert']=$curr[$key]; + unset($curr[$key]); + } + } + $ar=$curr; + + $curr = $ar['cert']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '30' && !array_key_exists('tbsCertificate', $curr)) { + $curr['tbsCertificate']=$curr[$key]; + unset($curr[$key]); + } + if($value['type'] == '30') { + $curr['signatureAlgorithm']=self::oidfromhex($value[0]['value_hex']); + unset($curr[$key]); + } + if($value['type'] == '03') { + $curr['signatureValue']=substr($value['value'], 2); + unset($curr[$key]); + } + } else { + unset($curr[$key]); + } + } + $ar['cert'] = $curr; + $ar['cert']['sha1Fingerprint']=hash('sha1', $der); + $curr = $ar['cert']['tbsCertificate']; + $i=0; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == 'a0') { + $curr['version']=$value[0]['value']; + unset($curr[$key]); + } + if($value['type'] == '02') { + $curr['serialNumber']=$value['value']; + unset($curr[$key]); + } + if($value['type'] == '30' && !array_key_exists('signature', $curr)) { + $curr['signature']=$value[0]['value_hex']; + unset($curr[$key]); + continue; + } + if($value['type'] == '30' && !array_key_exists('issuer', $curr)) { + foreach($value as $issuerK=>$issuerV) { + if(is_numeric($issuerK)) { + $issuerOID = $issuerV[0][0]['value_hex']; + if($oidprint == 'oid') { + $issuerOID = self::oidfromhex($issuerOID); + } elseif($oidprint == 'hex') { + } else { + $issuerOID = self::oidfromhex($issuerOID); + } + $issuer[$issuerOID][] = hex2bin($issuerV[0][1]['value_hex']); + } + } + $hexdump = $value['hexdump']; + $issuer['sha1'] = hash('sha1', hex2bin($hexdump)); + $issuer['opensslHash'] = self::opensslSubjHash($hexdump); + $issuer['hexdump'] = $hexdump; + + $curr['issuer']=$issuer; + unset($curr[$key]); + continue; + } + if($value['type'] == '30' && !array_key_exists('validity', $curr)) { + $curr['validity']['notBefore']=hex2bin($value[0]['value_hex']); + $curr['validity']['notAfter']=hex2bin($value[1]['value_hex']); + unset($curr[$key]); + continue; + } + if($value['type'] == '30' && !array_key_exists('subject', $curr)) { + $asn1SubjectToHash = ''; + foreach($value as $subjectK=>$subjectV) { + if(is_numeric($subjectK)) { + $subjectOID = $subjectV[0][0]['value_hex']; + if($oidprint == 'oid') { + $subjectOID = self::oidfromhex($subjectOID); + } elseif($oidprint == 'hex') { + } else { + $subjectOID = self::oidfromhex($subjectOID); + } + $subject[$subjectOID][] = hex2bin($subjectV[0][1]['value_hex']); + } + } + $hexdump = $value['hexdump']; + $subject['sha1'] = hash('sha1', hex2bin($hexdump)); + $subject['opensslHash'] = self::opensslSubjHash($hexdump); + $subject['hexdump'] = $hexdump; + + $curr['subject']=$subject; + unset($curr[$key]); + continue; + } + if($value['type'] == '30' && !array_key_exists('subjectPublicKeyInfo', $curr)) { + foreach($value as $subjectPublicKeyInfoK=>$subjectPublicKeyInfoV) { + if(is_numeric($subjectPublicKeyInfoK)) { + if($subjectPublicKeyInfoV['type'] == '30') { + $subjectPublicKeyInfo['algorithm']=self::oidfromhex($subjectPublicKeyInfoV[0]['value_hex']); + } + if($subjectPublicKeyInfoV['type'] == '03') { + $subjectPublicKeyInfo['subjectPublicKey']=substr($subjectPublicKeyInfoV['value'], 2); + } + } else { + unset($curr[$key]); + } + } + $subjectPublicKeyInfo['hex']=$value['hexdump']; + $subjectPublicKey_parse =asn1::parse($subjectPublicKeyInfo['subjectPublicKey']); + $subjectPublicKeyInfo['keyLength']=(strlen(substr($subjectPublicKey_parse[0][0]['value'], 2))/2)*8; + $subjectPublicKeyInfo['sha1']=hash('sha1', pack('H*', $subjectPublicKeyInfo['subjectPublicKey'])); + + $curr['subjectPublicKeyInfo']=$subjectPublicKeyInfo; + unset($curr[$key]); + continue; + } + if($value['type'] == 'a3') { + $curr['attributes']=$value[0]; + unset($curr[$key]); + } + $i++; + } else { + $tbsCertificateTag[$key]=$value; + } + } + $ar['cert']['tbsCertificate'] = $curr; + + if(array_key_exists('attributes', $ar['cert']['tbsCertificate'])) { + $curr = $ar['cert']['tbsCertificate']['attributes']; + foreach($curr as $key=>$value) { + if(is_numeric($key)) { + if($value['type'] == '30') { + $critical = 0; + $extvalue = $value[1]; + $name_hex = $value[0]['value_hex']; + $value_hex = $value[1]['hexdump']; + + if($value[1]['type'] == '01' && $value[1]['value_hex'] == 'ff') { + $critical = 1; + $extvalue = $value[2]; + } + if($name_hex == '551d0e') { // OBJ_subject_key_identifier + $extvalue = $value[1][0]['value_hex']; + } + if($name_hex == '551d23') { // OBJ_authority_key_identifier + foreach($value[1][0] as $OBJ_authority_key_identifierKey=>$OBJ_authority_key_identifierVal) { + if(is_numeric($OBJ_authority_key_identifierKey)) { + if($OBJ_authority_key_identifierVal['type'] == '80') { + $OBJ_authority_key_identifier['keyid'] = $OBJ_authority_key_identifierVal['value_hex']; + } + if($OBJ_authority_key_identifierVal['type'] == 'a1') { + $OBJ_authority_key_identifier['issuerName'] = $OBJ_authority_key_identifierVal['value_hex']; + } + if($OBJ_authority_key_identifierVal['type'] == '82') { + $OBJ_authority_key_identifier['issuerSerial'] = $OBJ_authority_key_identifierVal['value_hex']; + } + } + } + $extvalue = $OBJ_authority_key_identifier; + } + if($name_hex == '2b06010505070101') { // OBJ_info_access + foreach($value[1][0] as $OBJ_info_accessK=>$OBJ_info_accessV) { + if(is_numeric($OBJ_info_accessK)) { + $OBJ_info_accessHEX = $OBJ_info_accessV[0]['value_hex']; + $OBJ_info_accessOID = self::oidfromhex($OBJ_info_accessHEX); + $OBJ_info_accessNAME = $OBJ_info_accessOID; + $OBJ_info_access[$OBJ_info_accessNAME][] = hex2bin($OBJ_info_accessV[1]['value_hex']); + } + } + $extvalue = $OBJ_info_access; + } + if($name_hex == '551d1f') { // OBJ_crl_distribution_points 551d1f + foreach($value[1][0] as $OBJ_crl_distribution_pointsK=>$OBJ_crl_distribution_pointsV) { + if(is_numeric($OBJ_crl_distribution_pointsK)) { + $OBJ_crl_distribution_points[] = hex2bin($OBJ_crl_distribution_pointsV[0][0][0]['value_hex']); + } + } + $extvalue = $OBJ_crl_distribution_points; + } + if($name_hex == '551d0f') { // OBJ_key_usage + // $extvalue = self::parse_keyUsage($extvalue[0]['value']); + } + if($name_hex == '551d13') { // OBJ_basic_constraints + $bc['ca'] = '0'; + $bc['pathLength'] = ''; + foreach($extvalue[0] as $bck=>$bcv) { + if(is_numeric($bck)) { + if($bcv['type'] == '01') { + if($bcv['value_hex'] == 'ff') { + $bc['ca'] = '1'; + } + } + if($bcv['type'] == '02') { + $bc['pathLength'] = $bcv['value']; + } + } + } + $extvalue = $bc; + } + if($name_hex == '551d25') { // OBJ_ext_key_usage 551d1f + foreach($extvalue[0] as $OBJ_ext_key_usageK=>$OBJ_ext_key_usageV) { + if(is_numeric($OBJ_ext_key_usageK)) { + $OBJ_ext_key_usageHEX = $OBJ_ext_key_usageV['value_hex']; + $OBJ_ext_key_usageOID = self::oidfromhex($OBJ_ext_key_usageHEX); + $OBJ_ext_key_usageNAME = $OBJ_ext_key_usageOID; + $OBJ_ext_key_usage[] = $OBJ_ext_key_usageNAME; + } + } + $extvalue = $OBJ_ext_key_usage; + } + + $extsVal=array( + 'name_hex'=>$value[0]['value_hex'], + 'name_oid'=>self::oidfromhex($value[0]['value_hex']), + 'name'=>self::oidfromhex($value[0]['value_hex']), + 'critical'=>$critical, + 'value'=>$extvalue + ); + + $extNameOID = $value[0]['value_hex']; + if($oidprint == 'oid') { + $extNameOID = self::oidfromhex($extNameOID); + } elseif($oidprint == 'hex') { + } else { + $extNameOID = self::oidfromhex($extNameOID); + } + $curr[$extNameOID] = $extsVal; + unset($curr[$key]); + } + } else { + unset($curr[$key]); + } + unset($ar['cert']['tbsCertificate']['attributes']); + $ar['cert']['tbsCertificate']['attributes'] = $curr; + } + } + return $ar['cert']; + } + + /** + * read oid number of given hex + * @param string $hex hex form oid number + * @return string oid number + */ + private static function oidfromhex($hex) { + $split = str_split($hex, 2); + $i = 0; + foreach($split as $val) { + $dec = hexdec($val); + $mplx[$i] = ($dec-128)*128; + $i++; + } + $i = 0; + $nex = false; + $result = false; + foreach($split as $val) { + $dec = hexdec($val); + if($i == 0) { + if($dec >= 128) { + $nex = (128*($dec-128))-80; + if($dec > 129) { + $nex = (128*($dec-128))-80; + } + $result = "2."; + } + if($dec >= 80 && $dec < 128) { + $first = $dec-80; + $result = "2.$first."; + } + if($dec >= 40 && $dec < 80) { + $first = $dec-40; + $result = "1.$first."; + } + if($dec < 40) { + $first = $dec-0; + $result = "0.$first."; + } + } else { + if($dec > 127) { + if($nex == false) { + $nex = $mplx[$i]; + } else { + $nex = ($nex*128)+$mplx[$i]; + } + } else { + $result .= ($dec+$nex)."."; + if($dec <= 127) { + $nex = 0; + } + } + } + $i++; + } + return rtrim($result, "."); + } } /** * @class tcpdf_asn1 * Asn.1 encode/decode - * @version 1.0.001 + * @version 1.1 * @author M Hida */ -class tcpdf_asn1 { - // throw error to errorMsg - public $errorMsg; - - /** - * parse asn.1 to array - * to be called from $this->parse_recursive() function - * @param string $hex asn.1 hex form - * @return array asn.1 structure - * @protected - */ - protected function parse($hex) { - if(!@ctype_xdigit($hex) || @strlen($hex)%2!=0) { - $this->errorMsg = "input not hex string!."; - return false; - } - $stop = false; - while($stop == false) { - $asn1_type = substr($hex, 0, 2); - $tlv_tagLength = hexdec(substr($hex, 2, 2)); - if($tlv_tagLength > 127) { - $tlv_lengthLength = $tlv_tagLength-128; - $tlv_valueLength = substr($hex, 4, ($tlv_lengthLength*2)); - } else { - $tlv_lengthLength = 0; - $tlv_valueLength = substr($hex, 2, 2+($tlv_lengthLength*2)); - } - if($tlv_lengthLength >4) { // limit tlv_lengthLength to FFFF - return false; - } - $tlv_valueLength = hexdec($tlv_valueLength); - - $totalTlLength = 2+2+($tlv_lengthLength*2); - $reduction = 2+2+($tlv_lengthLength*2)+($tlv_valueLength*2); - $tlv_value = substr($hex, $totalTlLength, $tlv_valueLength*2); - $remain = substr($hex, $totalTlLength+($tlv_valueLength*2)); - $newhexdump = substr($hex, 0, $totalTlLength+($tlv_valueLength*2)); - - $result[] = array( - 'tlv_tagLength'=>strlen(dechex($tlv_tagLength))%2==0?dechex($tlv_tagLength):'0'.dechex($tlv_tagLength), - 'tlv_lengthLength'=>$tlv_lengthLength, - 'tlv_valueLength'=>$tlv_valueLength, - 'newhexdump'=>$newhexdump, - 'typ'=>$asn1_type, - 'tlv_value'=>$tlv_value - ); - - if($remain == '') { // if remains string was empty & contents also empty, function return FALSE - $stop = true; - } else { - $hex = $remain; - } - } - return $result; - } - - /** - * parse asn.1 to array recursively - * @param string $hex asn.1 hex form - * @param int $maxDepth maximum parsing depth - * @return array asn.1 structure recursively to specific depth - * @public - */ - public function parse_recursive($hex, $maxDepth=5) { - $result = array(); - $info = array(); - $parse_recursive = array(); - $asn1parse_array = $this->parse($hex); - static $currentDepth = 0; - if($asn1parse_array) { - foreach($asn1parse_array as $ff){ - $k = $ff['typ']; - $v = $ff['tlv_value']; - $info['depth']=$currentDepth; - $info['hexdump']=$ff['newhexdump']; - $info['type'] = $k; - $info['value_hex'] = $v; - if(($currentDepth <= $maxDepth)) { - if($k == '06') { - - } else if($k == '13' || $k == '18') { - $info['value'] = hex2bin($info['value_hex']); - } else if($k == '03' || $k == '02') { - $info['value'] = $v; - } else if($k == '05') { - - } else { - $currentDepth++; - $parse_recursive = $this->parse_recursive($v, $maxDepth); - $currentDepth--; - } - if($parse_recursive) { - $result[] = array_merge($info, $parse_recursive); - } else { - $result[] = $info; - } - unset($info['value']); - } - } - } else { - $result = false; - } - return $result; - } - - /** - * create asn.1 TLV tag length, length length and value length - * to be called from asn.1 builder functions - * @param string $str string value of asn.1 - * @return string hex of asn.1 TLV tag length - * @protected - */ - protected static 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; - } - - /** - * build asn.1 SEQUENCE tag - * @param string hex value of asn.1 SEQUENCE - * @return tring hex of asn.1 SEQUENCE with value - * @public - */ - public static function SEQ($hex) { - $ret = "30".self::asn1_header($hex).$hex; - return $ret; - } - - /** - * build asn.1 OCTET tag - * @param string hex value of asn.1 OCTET - * @return tring hex of asn.1 OCTET with value - * @public - */ - public static function OCT($hex) { - $ret = "04".self::asn1_header($hex).$hex; - return $ret; - } - - /** - * build asn.1 INTEGER tag - * @param string hex value of asn.1 INTEGER - * @return tring hex of asn.1 INTEGER with value - * @public - */ - public static function INT($int) { - if(strlen($int)%2 != 0) { - $int = "0$int"; - } - $int = "$int"; - $ret = "02".self::asn1_header($int).$int; - return $ret; - } - - /** - * build asn.1 SET tag - * @param string hex value of asn.1 SET - * @return tring hex of asn.1 SET with value - * @public - */ - public static function SET($hex) { - $ret = "31".self::asn1_header($hex).$hex; - return $ret; - } - - /** - * build asn.1 EXPLICIT tag - * @param string hex value of asn.1 EXPLICIT - * @return tring hex of asn.1 EXPLICIT with value - * @public - */ - public static function EXPLICIT($num, $hex) { - $ret = "a$num".self::asn1_header($hex).$hex; - return $ret; - } +class asn1 { + // =====Begin ASN.1 Parser section===== + /** + * get asn.1 type tag name + * @param string $id hex asn.1 type tag + * @return string asn.1 tag name + * @protected + */ + protected static function type($id) { + $asn1_Types = array( + "00" => "ASN1_EOC", + "01" => "ASN1_BOOLEAN", + "02" => "ASN1_INTEGER", + "03" => "ASN1_BIT_STRING", + "04" => "ASN1_OCTET_STRING", + "05" => "ASN1_NULL", + "06" => "ASN1_OBJECT", + "07" => "ASN1_OBJECT_DESCRIPTOR", + "08" => "ASN1_EXTERNAL", + "09" => "ASN1_REAL", + "0a" => "ASN1_ENUMERATED", + "0c" => "ASN1_UTF8STRING", + "30" => "ASN1_SEQUENCE", + "31" => "ASN1_SET", + "12" => "ASN1_NUMERICSTRING", + "13" => "ASN1_PRINTABLESTRING", + "14" => "ASN1_T61STRING", + "15" => "ASN1_VIDEOTEXSTRING", + "16" => "ASN1_IA5STRING", + "17" => "ASN1_UTCTIME", + "18" => "ASN1_GENERALIZEDTIME", + "19" => "ASN1_GRAPHICSTRING", + "1a" => "ASN1_VISIBLESTRING", + "1b" => "ASN1_GENERALSTRING", + "1c" => "ASN1_UNIVERSALSTRING", + "1d" => "ASN1_BMPSTRING" + ); + return array_key_exists($id,$asn1_Types)?$asn1_Types[$id]:$id; + } + + /** + * parse asn.1 to array + * to be called from parse() function + * @param string $hex asn.1 hex form + * @return array asn.1 structure + * @protected + */ + protected static function oneParse($hex) { + if($hex == '') { + return false; + } + if(!@ctype_xdigit($hex) || @strlen($hex)%2!=0) { + // echo "input:\"$hex\" not hex string!.\n"; + return false; + } + $stop = false; + while($stop == false) { + $asn1_type = substr($hex, 0, 2); + $tlv_tagLength = hexdec(substr($hex, 2, 2)); + if($tlv_tagLength > 127) { + $tlv_lengthLength = $tlv_tagLength-128; + $tlv_valueLength = substr($hex, 4, ($tlv_lengthLength*2)); + } else { + $tlv_lengthLength = 0; + $tlv_valueLength = substr($hex, 2, 2+($tlv_lengthLength*2)); + } + if($tlv_lengthLength >4) { // limit tlv_lengthLength to FFFF + return false; + } + $tlv_valueLength = hexdec($tlv_valueLength); + + $totalTlLength = 2+2+($tlv_lengthLength*2); + $reduction = 2+2+($tlv_lengthLength*2)+($tlv_valueLength*2); + $tlv_value = substr($hex, $totalTlLength, $tlv_valueLength*2); + $remain = substr($hex, $totalTlLength+($tlv_valueLength*2)); + $newhexdump = substr($hex, 0, $totalTlLength+($tlv_valueLength*2)); + + $result[] = array( + 'tlv_tagLength'=>strlen(dechex($tlv_tagLength))%2==0?dechex($tlv_tagLength):'0'.dechex($tlv_tagLength), + 'tlv_lengthLength'=>$tlv_lengthLength, + 'tlv_valueLength'=>$tlv_valueLength, + 'newhexdump'=>$newhexdump, + 'typ'=>$asn1_type, + 'tlv_value'=>$tlv_value + ); + + if($remain == '') { // if remains string was empty & contents also empty, function return FALSE + $stop = true; + } else { + $hex = $remain; + } + } + return $result; + } + + /** + * parse asn.1 to array recursively + * @param string $hex asn.1 hex form + * @param int $maxDepth maximum parsing depth + * @return array asn.1 structure recursively to specific depth + * @public + */ + public static function parse($hex, $maxDepth=5) { + $result = array(); + static $currentDepth = 0; + if($asn1parse_array = self::oneParse($hex)) { + foreach($asn1parse_array as $ff){ + $parse_recursive = false; + unset($info); + $k = $ff['typ']; + $v = $ff['tlv_value']; + $info['depth']=$currentDepth; + $info['hexdump']=$ff['newhexdump']; + $info['type'] = $k; + $info['typeName'] = self::type($k); + $info['value_hex'] = $v; + if(($currentDepth <= $maxDepth)) { + if($k == '06') { + + } else if($k == '13' || $k == '18') { + $info['value'] = hex2bin($info['value_hex']); + } else if($k == '03' || $k == '02' || $k == 'a04') { + $info['value'] = $v; + } else if($k == '05') { + + } else if($k == '01') { + + } else { + $currentDepth++; + $parse_recursive = self::parse($v, $maxDepth); + $currentDepth--; + } + if($parse_recursive) { + $result[] = array_merge($info, $parse_recursive); + } else { + $result[] = $info; + } + } + } + } + return $result; + } + // =====End ASN.1 Parser section===== + + // =====Begin ASN.1 Builder section===== + /** + * create asn.1 TLV tag length, length length and value length + * to be called from asn.1 builder functions + * @param string $str string value of asn.1 + * @return string hex of asn.1 TLV tag length + * @protected + */ + protected static 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; + } + + /** + * Create asn.1 SEQUENCE + * @param string $hex hex value of asn.1 SEQUENCE + * @return tring hex of asn.1 SEQUENCE tag with value + * @public + */ + public static function SEQ($hex) { + $ret = "30".self::asn1_header($hex).$hex; + return $ret; + } + + /** + * Create asn.1 OCTET + * @param string $hex hex value of asn.1 OCTET + * @return string hex of asn.1 OCTET tag with value + * @public + */ + public static function OCT($hex) { + $ret = "04".self::asn1_header($hex).$hex; + return $ret; + } + + /** + * Create asn.1 OBJECT + * @param string $hex hex value of asn.1 OBJECT + * @return string hex of asn.1 OBJECT tag with value + * @public + */ + public static function OBJ($hex) { + $ret = "06".self::asn1_header($hex).$hex; + return $ret; + } + + /** + * Create asn.1 BITString + * @param string $hex hex value of asn.1 BITString + * @return string hex of asn.1 BITString tag with value + * @public + */ + public static function BIT($hex) { + $ret = "03".self::asn1_header($hex).$hex; + return $ret; + } + + /** + * Create asn.1 INTEGER + * @param string $int number value of asn.1 INTEGER + * @return string hex of asn.1 INTEGER tag with value + * @public + */ + public static function INT($int) { + if(strlen($int)%2 != 0) { + $int = "0$int"; + } + $int = "$int"; + $ret = "02".self::asn1_header($int).$int; + return $ret; + } + + /** + * Create asn.1 SET tag + * @param string $hex hex value of asn.1 SET + * @return string hex of asn.1 SET with value + * @public + */ + public static function SET($hex) { + $ret = "31".self::asn1_header($hex).$hex; + return $ret; + } + + /** + * Create asn.1 EXPLICIT + * @param string $num value of asn.1 EXPLICIT number + * @param string $hex value of asn.1 EXPLICIT + * @return string hex of asn.1 EXPLICIT with value + * @public + */ + public static function EXPL($num, $hex) { + $ret = "a$num".self::asn1_header($hex).$hex; + return $ret; + } + + /** + * Create asn.1 UTF8String + * @param string $str string value of asn.1 UTF8String + * @return string hex of asn.1 UTF8String tag with value + * @public + */ + public static function UTF8($str) { + $ret = "0c".self::asn1_header(bin2hex($str)).bin2hex($str); + return $ret; + } + // =====End ASN.1 Builder section===== } ?> \ No newline at end of file diff --git a/tcpdf.php b/tcpdf.php index f6a1e4f7..cf80bbac 100644 --- a/tcpdf.php +++ b/tcpdf.php @@ -1274,7 +1274,8 @@ class TCPDF { * @protected * @since 4.6.005 (2009-04-24) */ - protected $signature_max_length = 23000; + //protected $signature_max_length = 19000; + protected $signature_max_length = 27000; /** * Data for digital signature appearance. @@ -1295,6 +1296,7 @@ class TCPDF { * @protected * @since 6.0.085 (2014-06-19) */ + protected $signature_ltv = false; protected $tsa_timestamp = false; /** @@ -1302,6 +1304,7 @@ class TCPDF { * @protected * @since 6.0.085 (2014-06-19) */ + protected $signature_ltv_data = array(); protected $tsa_data = array(); /** @@ -1982,7 +1985,9 @@ public function __construct($orientation='P', $unit='mm', $format='A4', $unicode $this->setTextShadow(); // signature $this->sign = false; + $this->signature_ltv = false; $this->tsa_timestamp = false; + $this->signature_ltv_data = array(); $this->tsa_data = array(); $this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature'); $this->empty_signature_appearance = array(); @@ -7694,8 +7699,8 @@ public function Output($name='doc.pdf', $dest='I') { $signature = base64_decode(trim($signature)); // convert signature to hex $signature = current(unpack('H*', $signature)); - // add TSA timestamp to signature - $signature = $this->applyTSA($signature); + // add LTV/TSA to signature + $signature = $this->applyLtvTsa($signature); $signature = str_pad($signature, $this->signature_max_length, '0'); // Add signature to the document @@ -7870,7 +7875,9 @@ public function _destroy($destroyall=false, $preserve_objcopy=false) { 'signature_data', 'signature_max_length', 'byterange_string', + 'signature_ltv', 'tsa_timestamp', + 'signature_ltv_data', 'tsa_data' ); foreach (array_keys(get_object_vars($this)) as $val) { @@ -13673,23 +13680,51 @@ public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $ * @author M Hida * @since 6.6.2 (2023-05-25) */ - protected function applyTSA($signature) { - if (!$this->tsa_timestamp) { - return $signature; - } - require_once(dirname(__FILE__).'/include/tcpdf_cmssignature.php'); - $tcpdf_cms = new tcpdf_cms_signature; - $tcpdf_cms->pkcs7_data($signature); - $tsaQuery = $tcpdf_cms->tsa_query($tcpdf_cms->pkcs7_EncryptedDigest); - if(!$tsaResp = $tcpdf_cms->tsa_send($tsaQuery, $this->tsa_data['tsa_host'], $this->tsa_data['tsa_username'], $this->tsa_data['tsa_password'])) { - $this->Error("Can't send TSA Request to: ".$this->tsa_data['tsa_host']." error:".$tcpdf_cms->errorMsg); - } - if(@$signatureWithTs = $tcpdf_cms->pkcs7_appendTsa($tsaResp)) { - $signature = $signatureWithTs; - } else { - $this->Error("Can't append TSA data! error: ".$tcpdf_cms->errorMsg); - } - return $signature; + public function setLtv($ocspURI=null, $crlURIorFILE=null, $issuerURIorFILE=null) { + $this->signature_ltv_data = array(); + $this->signature_ltv_data['ocspURI'] = $ocspURI; + $this->signature_ltv_data['crlURIorFILE'] = $crlURIorFILE; + $this->signature_ltv_data['issuerURIorFILE'] = $issuerURIorFILE; + $this->signature_ltv = true; + } + + /** + * Applying LTV and TSA + * @param string $signature hex form pkcs7 digital signature + * @return hex string LTV/TSA embedded digital signature + * @protected + * @author M Hida + * @since 6.6.2 (2023-05-25) + */ + protected function applyLtvTsa($signature) { + if ($this->signature_ltv || $this->tsa_timestamp) { + require_once(dirname(__FILE__).'/include/tcpdf_cmssignature.php'); + $tcpdf_cms = new tcpdf_cmssignature; + $tcpdf_cms->signature_data = $this->signature_data; + } + if ($this->signature_ltv) { + $tcpdf_cms->signature_ltv_data = $this->signature_ltv_data; + $tcpdf_cms->pkcs7_data($signature); + $tcpdf_cms->log .= "info: append LTV start\n"; + if(@$signatureWithLtv = $tcpdf_cms->pkcs7_appendLtv()) { + $tcpdf_cms->log .= "info: append LTV end success\n"; + $signature = $signatureWithLtv; + } else { + $tcpdf_cms->log .= "error: append LTV end failed!\n"; + } + } + if ($this->tsa_timestamp) { + $tcpdf_cms->tsa_data = $this->tsa_data; + $tcpdf_cms->pkcs7_data($signature); + $tcpdf_cms->log .= "info: append TSA start!\n"; + if(@$signatureWithTs = $tcpdf_cms->pkcs7_appendTsa()) { + $tcpdf_cms->log .= "info: append TSA end success\n"; + $signature = $signatureWithTs; + } else { + $tcpdf_cms->log .= "error: append TSA end failed!\n"; + } + } + return $signature; } /**