From 37ff2659d88dc1f9a8c6a2cf22f6319c437c7934 Mon Sep 17 00:00:00 2001 From: M Hida Date: Sat, 13 Jul 2024 18:37:50 +0700 Subject: [PATCH] Support LTV and Timestamp (#70) * first update to support timestamp * Add last parameter for chain certs Add last parameter for chain certs * Add last parameter for support chain certs * expand length to embedded tsa * update * add ltv and timestamp * Support Timestamp and LTV And improve signature process . no need to write/read/parse tempfile in signing process. * Update PDFDoc.php * Update PDFDoc.php * Update pdfsign.php * Update CMS.php * removed ex test and ex log * Update PDFDoc.php * Update PDFUtilFnc.php * Update CMS.php remove logging function, use sapp default logging function * aligned classes structure migrate asn1 class functions to dynamic functions * remove example * Update pdfsign.php -change some message text. -set default $ocspUrl & $crl to prevent php notice message append in pdf result * update args & text * Update asn1.php * Update x509.php prevent php notice msg when ocsp server send not common resp status * Update CMS.php prevent same extracerts certificate (duplicate cert) to embeded * Fix example scripts * Small fixes * calculate __SIGNATURE_MAX_LENGTH calculated __SIGNATURE_MAX_LENGTH exactly. no longer waste signature space with zero padding. * merged pdfsignltv.php & pdfsigntsa.php * Update CMS.php - support path validation (unlimited) - removed ocsp, crl and issuer parameter (because it is difficult to implement if the certificate has a long path) * merged * Support * Update pdfsignlts.php * Create pdfsigntsa.php * Update asn1.php * minor bugs * minor bugs --------- Co-authored-by: erikn69 Co-authored-by: Carlos de Alfonso Laguna --- pdfsign.php | 2 +- pdfsigni.php | 1 - pdfsignlts.php | 73 +++ pdfsigntsa.php | 66 +++ pdfsignx.php | 1 - src/PDFDoc.php | 81 ++- src/PDFSignatureObject.php | 17 +- src/PDFUtilFnc.php | 41 -- src/helpers/CMS.php | 541 ++++++++++++++++++++ src/helpers/asn1.php | 258 ++++++++++ src/helpers/x509.php | 984 +++++++++++++++++++++++++++++++++++++ 11 files changed, 2009 insertions(+), 56 deletions(-) create mode 100644 pdfsignlts.php create mode 100644 pdfsigntsa.php create mode 100644 src/helpers/CMS.php create mode 100644 src/helpers/asn1.php create mode 100644 src/helpers/x509.php diff --git a/pdfsign.php b/pdfsign.php index 48641ad..0225256 100644 --- a/pdfsign.php +++ b/pdfsign.php @@ -53,4 +53,4 @@ } } } -} \ No newline at end of file +} diff --git a/pdfsigni.php b/pdfsigni.php index d13c880..229f67d 100755 --- a/pdfsigni.php +++ b/pdfsigni.php @@ -88,4 +88,3 @@ } } } -?> diff --git a/pdfsignlts.php b/pdfsignlts.php new file mode 100644 index 0000000..4691963 --- /dev/null +++ b/pdfsignlts.php @@ -0,0 +1,73 @@ +#!/usr/bin/env php +. +*/ + +use ddn\sapp\PDFDoc; + +require_once('vendor/autoload.php'); + +if ($argc < 3) + fwrite(STDERR, sprintf("usage: %s \n +tsaUrl - optional TSA server url to timestamp pdf document. +", $argv[0])); +else { + if (!file_exists($argv[1])) + fwrite(STDERR, "failed to open file " . $argv[1]); + else { + // Silently prompt for the password + fwrite(STDERR, "Password: "); + system('stty -echo'); + $password = trim(fgets(STDIN)); + system('stty echo'); + fwrite(STDERR, "\n"); + + $tsa = $argv[3] ?? null; + if (empty($tsa)) { + // Silently prompt for the timestamp autority + fwrite(STDERR, "TSA(\"http://timestamp.digicert.com\") type \"no\" to bypass tsa: "); + system('stty -echo'); + $tsa = trim(fgets(STDIN)) ?: "http://timestamp.digicert.com"; + system('stty echo'); + fwrite(STDERR, "\n"); + } + + $file_content = file_get_contents($argv[1]); + $obj = PDFDoc::from_string($file_content); + + if ($obj === false) + fwrite(STDERR, "failed to parse file " . $argv[1]); + else { + if (!$obj->set_signature_certificate($argv[2], $password)) + fwrite(STDERR, "the certificate is not valid"); + else { + if ($tsa != 'no') { + $obj->set_tsa($tsa); + } + $obj->set_ltv(); + $docsigned = $obj->to_pdf_file_s(); + if ($docsigned === false) + fwrite(STDERR, "could not sign the document"); + else + echo $docsigned; + } + } + } +} diff --git a/pdfsigntsa.php b/pdfsigntsa.php new file mode 100644 index 0000000..309a86b --- /dev/null +++ b/pdfsigntsa.php @@ -0,0 +1,66 @@ +#!/usr/bin/env php +. +*/ + +use ddn\sapp\PDFDoc; + +require_once('vendor/autoload.php'); + +if ($argc < 3) + fwrite(STDERR, sprintf("usage: %s \n +tsaUrl - optional TSA server url to timestamp pdf document. +", $argv[0])); +else { + if (!file_exists($argv[1])) + fwrite(STDERR, "failed to open file " . $argv[1]); + else { + // Silently prompt for the password + fwrite(STDERR, "Password: "); + system('stty -echo'); + $password = trim(fgets(STDIN)); + system('stty echo'); + fwrite(STDERR, "\n"); + + $tsa = $argv[3] ?? null; + if (empty($tsa)) { + // Silently prompt for the timestamp autority + fwrite(STDERR, "TSA(\"http://timestamp.digicert.com\"): "); + system('stty -echo'); + $tsa = trim(fgets(STDIN)) ?: "http://timestamp.digicert.com"; + system('stty echo'); + fwrite(STDERR, "\n"); + } + + $file_content = file_get_contents($argv[1]); + $obj = PDFDoc::from_string($file_content); + + if ($obj === false) + fwrite(STDERR, "failed to parse file " . $argv[1]); + else { + if (!$obj->set_signature_certificate($argv[2], $password)) + fwrite(STDERR, "the certificate is not valid"); + else { + $obj->set_tsa($tsa); + $docsigned = $obj->to_pdf_file_s(); + if ($docsigned === false) + fwrite(STDERR, "could not sign the document"); + else + echo $docsigned; + } + } + } +} \ No newline at end of file diff --git a/pdfsignx.php b/pdfsignx.php index 43e572b..d939650 100755 --- a/pdfsignx.php +++ b/pdfsignx.php @@ -56,4 +56,3 @@ } } } -?> diff --git a/src/PDFDoc.php b/src/PDFDoc.php index a23cbf9..76f2859 100644 --- a/src/PDFDoc.php +++ b/src/PDFDoc.php @@ -30,6 +30,9 @@ use ddn\sapp\pdfvalue\PDFValueSimple; use ddn\sapp\pdfvalue\PDFValueHexString; use ddn\sapp\pdfvalue\PDFValueString; +use ddn\sapp\helpers\CMS; +use ddn\sapp\helpers\x509; +use ddn\sapp\helpers\asn1; use ddn\sapp\helpers\Buffer; use ddn\sapp\helpers\UUID; use ddn\sapp\helpers\DependencyTreeObject; @@ -67,6 +70,8 @@ class PDFDoc extends Buffer { protected $_buffer = ""; protected $_backup_state = []; protected $_certificate = null; + protected $_signature_ltv_data = null; + protected $_signature_tsa = null; protected $_appearance = null; protected $_xref_table_version; protected $_revisions; @@ -286,7 +291,8 @@ public function clear_signature_certificate() { /** * Function that stores the certificate to use, when signing the document - * @param certfile a file that contains a user certificate in pkcs12 format, or an array [ 'cert' => , 'pkey' => ] + * @param certfile a file that contains a user certificate in pkcs12 format, + * or an array [ 'cert' => , 'pkey' => , 'extracerts' => ] * that would be the output of openssl_pkcs12_read * @param password the password to read the private key * @return valid true if the certificate can be used to sign the document, false otherwise @@ -302,6 +308,12 @@ public function set_signature_certificate($certfile, $certpass = null) { return p_error("invalid private key"); if (! openssl_x509_check_private_key($certificate["cert"], $certificate["pkey"])) return p_error("private key doesn't corresponds to certificate"); + + if (is_string($certificate['extracerts'] ?? null)) { + $certificate['extracerts'] = array_filter(explode("-----END CERTIFICATE-----\n", $certificate['extracerts'])); + foreach ($certificate['extracerts'] as &$extracerts) + $extracerts = $extracerts . "-----END CERTIFICATE-----\n"; + } } else { $certfilecontent = file_get_contents($certfile); if ($certfilecontent === false) @@ -316,6 +328,32 @@ public function set_signature_certificate($certfile, $certpass = null) { return true; } + /** + * Function that stores the ltv configuration to use, when signing the document + * @param $ocspURI OCSP Url to validate cert file + * @param $crlURIorFILE Crl filename/url to validate cert + * @param $issuerURIorFILE issuer filename/url + */ + public function set_ltv($ocspURI=null, $crlURIorFILE=null, $issuerURIorFILE=null) { + $this->_signature_ltv_data['ocspURI'] = $ocspURI; + $this->_signature_ltv_data['crlURIorFILE'] = $crlURIorFILE; + $this->_signature_ltv_data['issuerURIorFILE'] = $issuerURIorFILE; + } + + /** + * Function that stores the tsa configuration to use, when signing the document + * @param $tsaurl Link to tsa service + * @param $tsauser the user for tsa service + * @param $tsapass the password for tsa service + */ + public function set_tsa($tsa, $tsauser = null, $tsapass = null) { + $this->_signature_tsa['host'] = $tsa; + if ($tsauser && $tsapass) { + $this->_signature_tsa['user'] = $tsauser; + $this->_signature_tsa['password'] = $tsapass; + } + } + /** * Function to set the metadata properties for the certificate options * @param $name @@ -415,10 +453,31 @@ protected function _generate_signature_in_document() { // Prepare the signature object (we need references to it) $signature = null; if ($this->_certificate !== null) { + // Perform signature test to get signature size to define __SIGNATURE_MAX_LENGTH + p_debug(" ########## PERFORM SIGNATURE LENGTH CHECK ##########\n"); + $CMS = new helpers\CMS; + $CMS->signature_data['signcert'] = $this->_certificate['cert']; + $CMS->signature_data['extracerts'] = $this->_certificate['extracerts']??null; + $CMS->signature_data['hashAlgorithm'] = 'sha256'; + $CMS->signature_data['privkey'] = $this->_certificate['pkey']; + $CMS->signature_data['tsa'] = $this->_signature_tsa; + $CMS->signature_data['ltv'] = $this->_signature_ltv_data; + $res = $CMS->pkcs7_sign('0'); + $len = strlen($res); + p_debug(" Signature Length is \"$len\" Bytes"); + p_debug(" ########## FINISHED SIGNATURE LENGTH CHECK #########\n\n"); + define('__SIGNATURE_MAX_LENGTH', $len); + $signature = $this->create_object([], "ddn\sapp\PDFSignatureObject", false); //$signature = new PDFSignatureObject([]); $signature->set_metadata($this->_metadata_name, $this->_metadata_reason, $this->_metadata_location, $this->_metadata_contact_info); $signature->set_certificate($this->_certificate); + if($this->_signature_tsa !== null) { + $signature->set_signature_tsa($this->_signature_tsa); + } + if($this->_signature_ltv_data !== null) { + $signature->set_signature_ltv($this->_signature_ltv_data); + } // Update the value to the annotation object $annotation_object["V"] = new PDFValueReference($signature->get_oid()); @@ -811,17 +870,17 @@ public function to_pdf_file_b($rebuild = false) : Buffer { $_signature->set_sizes($_doc_to_xref->size(), $_doc_from_xref->size()); $_signature["Contents"] = new PDFValueSimple(""); $_signable_document = new Buffer($_doc_to_xref->get_raw() . $_signature->to_pdf_entry() . $_doc_from_xref->get_raw()); - - // We need to write the content to a temporary folder to use the pkcs7 signature mechanism - $temp_filename = tempnam(__TMP_FOLDER, 'pdfsign'); - $temp_file = fopen($temp_filename, 'wb'); - fwrite($temp_file, $_signable_document->get_raw()); - fclose($temp_file); - - // Calculate the signature and remove the temporary file $certificate = $_signature->get_certificate(); - $signature_contents = PDFUtilFnc::calculate_pkcs7_signature($temp_filename, $certificate['cert'], $certificate['pkey'], __TMP_FOLDER); - unlink($temp_filename); + $extracerts = (array_key_exists('extracerts', $certificate)) ? $certificate['extracerts'] : null; + $cms = new CMS; + $cms->signature_data['hashAlgorithm'] = 'sha256'; + $cms->signature_data['privkey'] = $certificate['pkey']; + $cms->signature_data['extracerts'] = $extracerts; + $cms->signature_data['signcert'] = $certificate['cert']; + $cms->signature_data['ltv'] = $_signature->get_ltv(); + $cms->signature_data['tsa'] = $_signature->get_tsa(); + $signature_contents = $cms->pkcs7_sign($_signable_document->get_raw()); + $signature_contents = str_pad($signature_contents, __SIGNATURE_MAX_LENGTH, '0'); // Then restore the contents field $_signature["Contents"] = new PDFValueHexString($signature_contents); diff --git a/src/PDFSignatureObject.php b/src/PDFSignatureObject.php index 4b442c9..b029223 100644 --- a/src/PDFSignatureObject.php +++ b/src/PDFSignatureObject.php @@ -35,7 +35,8 @@ // The maximum signature length, needed to create a placeholder to calculate the range of bytes // that will cover the signature. if (!defined('__SIGNATURE_MAX_LENGTH')) - define('__SIGNATURE_MAX_LENGTH', 11742); + //define('__SIGNATURE_MAX_LENGTH', 11742); + define('__SIGNATURE_MAX_LENGTH', 27742); // The maximum expected length of the byte range, used to create a placeholder while the size // is not known. 68 digits enable 20 digits for the size of the document @@ -49,6 +50,8 @@ class PDFSignatureObject extends PDFObject { // A placeholder for the certificate to use to sign the document protected $_certificate = null; + protected $_signature_ltv_data = null; + protected $_signature_tsa = null; /** * Sets the certificate to use to sign * @param cert the pem-formatted certificate and private to use to sign as @@ -57,6 +60,12 @@ class PDFSignatureObject extends PDFObject { public function set_certificate($certificate) { $this->_certificate = $certificate; } + public function set_signature_ltv($signature_ltv_data) { + $this->_signature_ltv_data = $signature_ltv_data; + } + public function set_signature_tsa($signature_tsa) { + $this->_signature_tsa = $signature_tsa; + } /** * Obtains the certificate set with function set_certificate * @return cert the certificate @@ -64,6 +73,12 @@ public function set_certificate($certificate) { public function get_certificate() { return $this->_certificate; } + public function get_tsa() { + return $this->_signature_tsa; + } + public function get_ltv() { + return $this->_signature_ltv_data; + } /** * Constructs the object and sets the default values needed to sign * @param oid the oid for the object diff --git a/src/PDFUtilFnc.php b/src/PDFUtilFnc.php index dfa682a..083247b 100644 --- a/src/PDFUtilFnc.php +++ b/src/PDFUtilFnc.php @@ -24,7 +24,6 @@ use ddn\sapp\PDFObjectParser; use ddn\sapp\helpers\StreamReader; use ddn\sapp\helpers\Buffer; - use function ddn\sapp\helpers\p_debug; use function ddn\sapp\helpers\p_debug_var; use function ddn\sapp\helpers\p_error; @@ -446,46 +445,6 @@ public static function acquire_structure(&$_buffer, $depth = null) { ]; } - /** - * Signs a file using the certificate and key and obtains the signature content padded to the max signature length - * @param filename the name of the file to sign - * @param certificate the public key to sign - * @param key the private key to sign - * @param tmpfolder the folder in which to store a temporary file needed - * @return signature the signature, in hexadecimal string, padded to the maximum length (i.e. for PDF) or false in case of error - */ - public static function calculate_pkcs7_signature($filenametosign, $certificate, $key, $tmpfolder = "/tmp") { - $filesize_original = filesize($filenametosign); - if ($filesize_original === false) - return p_error("could not open file $filenametosign"); - - $temp_filename = tempnam($tmpfolder, "pdfsign"); - - if ($temp_filename === false) - return p_error("could not create a temporary filename"); - - if (openssl_pkcs7_sign($filenametosign, $temp_filename, $certificate, $key, array(), PKCS7_BINARY | PKCS7_DETACHED) !== true) { - unlink($temp_filename); - return p_error("failed to sign file $filenametosign"); - } - - $signature = file_get_contents($temp_filename); - // extract signature - $signature = substr($signature, $filesize_original); - $signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13)); - - $tmparr = explode("\n\n", $signature); - $signature = $tmparr[1]; - // decode signature - $signature = base64_decode(trim($signature)); - - // convert signature to hex - $signature = current(unpack('H*', $signature)); - $signature = str_pad($signature, __SIGNATURE_MAX_LENGTH, '0'); - - return $signature; - } - /** * Function that finds a the object at the specific position in the buffer * @param buffer the buffer from which to read the document diff --git a/src/helpers/CMS.php b/src/helpers/CMS.php new file mode 100644 index 0000000..799c107 --- /dev/null +++ b/src/helpers/CMS.php @@ -0,0 +1,541 @@ + $r) { + if (stripos($r, 'HTTP/') === 0) { + list(,$code, $status) = explode(' ', $r, 3); + break; + } + } + if($code != '200') { + p_error(" response error! Code=\"$code\", Status=\"".trim($status??"")."\""); + return false; + } + $contentTypeHeader = ''; + $headers = explode("\n", $header); + foreach ($headers as $key => $r) { + // Match the header name up to ':', compare lower case + if (stripos($r, "Content-Type".':') === 0) { + list($headername, $headervalue) = explode(":", $r, 2); + $contentTypeHeader = trim($headervalue); + } + } + if($contentTypeHeader != $reqData['resp_contentType']) { + p_error(" response content type not {$reqData['resp_contentType']}, but: \"$contentTypeHeader\""); + return false; + } + if(empty($body)) { + p_error(' error empty response!'); + } + 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 function tsa_parseResp($binaryTsaRespData) { + if(!@$ar = asn1::parse(bin2hex($binaryTsaRespData), 3)) { + p_error(" 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 { + return false; + } + } + + /** + * Create timestamp query + * @param string $data binary data to hashed/digested + * @param string $hashAlg hash algorithm + * @return string hex TSTinfo. + */ + protected function createTimestamp($data, $hashAlg='sha1') { + $TSTInfo=false; + $tsaQuery = x509::tsa_query($data, $hashAlg); + $tsaData = $this->signature_data['tsa']; + $reqData = array( + 'data'=>$tsaQuery, + 'uri'=>$tsaData['host'], + 'req_contentType'=>'application/timestamp-query', + 'resp_contentType'=>'application/timestamp-reply' + ) + $tsaData; + + p_debug(" sending TSA query to \"".$tsaData['host']."\"..."); + if(!$binaryTsaResp = self::sendReq($reqData)) { + p_error(" TSA query send FAILED!"); + } else { + p_debug(" TSA query send OK"); + p_debug(" Parsing Timestamp response..."); + if(!$tsaResp = $this->tsa_parseResp($binaryTsaResp)) { + p_error(" parsing FAILED!"); + } + p_debug(" parsing OK"); + $TSTInfo = $tsaResp['TimeStampResp']['timeStampToken']['hexdump']; + } + return $TSTInfo; + } + + /** + * Perform OCSP/CRL Validation + * @param array $parsedCert parsed certificate + * @param string $ocspURI + * @param string $crlURIorFILE + * @param string $issuerURIorFILE + * @return array + */ + protected function LTVvalidation($parsedCert, $ocspURI=null, $crlURIorFILE=null, $issuerURIorFILE=false) { + $ltvResult['issuer']=false; + $ltvResult['ocsp']=false; + $ltvResult['crl']=false; + $certSigner_parse = $parsedCert; + p_debug(" getting OCSP & CRL address..."); + p_debug(" reading AIA OCSP 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]; + if(empty(trim($ocspURI))) { + p_warning(" FAILED!"); + } else { + p_debug(" OK got address:\"$ocspURI\""); + } + $ocspURI = trim($ocspURI); + p_debug(" reading CRL CDP attribute..."); + $crlURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['2.5.29.31']['value'][0]; + if(empty(trim($crlURIorFILE??""))) { + p_warning(" FAILED!"); + } else { + p_debug(" OK got address:\"$crlURIorFILE\""); + } + if(empty($ocspURI) && empty($crlURIorFILE)) { + p_error(" can't get OCSP/CRL address! Process terminated."); + } else { // Perform if either ocspURI/crlURIorFILE exists + p_debug(" getting Issuer..."); + p_debug(" looking for issuer address from AIA attribute..."); + $issuerURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.2'][0]; + $issuerURIorFILE = trim($issuerURIorFILE??""); + if(empty($issuerURIorFILE)) { + p_debug(" Failed!"); + } else { + p_debug(" OK got address \"$issuerURIorFILE\"..."); + p_debug(" load issuer from \"$issuerURIorFILE\"..."); + if($issuerCert = @file_get_contents($issuerURIorFILE)) { + p_debug(" OK. size ".round(strlen($issuerCert)/1024,2)."Kb"); + p_debug(" reading issuer certificate..."); + if($issuer_certDER = x509::get_cert($issuerCert)) { + p_debug(" OK"); + p_debug(" check if issuer is cert issuer..."); + $certIssuer_parse = x509::readcert($issuer_certDER, 'oid'); // Parsing Issuer cert + $certSigner_signatureField = $certSigner_parse['signatureValue']; + if(openssl_public_decrypt(hex2bin($certSigner_signatureField), $decrypted, x509::x509_der2pem($issuer_certDER), OPENSSL_PKCS1_PADDING)) { + p_debug(" OK issuer is cert issuer."); + $ltvResult['issuer'] = $issuer_certDER; + } else { + p_warning(" FAILED! issuer is not cert issuer."); + } + } else { + p_warning(" FAILED!"); + } + } else { + p_warning(" FAILED!."); + } + } + + if(!$ltvResult['issuer']) { + p_debug(" search for issuer in extracerts....."); + if(array_key_exists('extracerts', $this->signature_data) && ($this->signature_data['extracerts'] !== null) && (count($this->signature_data['extracerts']) > 0)) { + $i=0; + foreach($this->signature_data['extracerts'] as $extracert) { + p_debug(" extracerts[$i] ..."); + $certSigner_signatureField = $certSigner_parse['signatureValue']; + if(openssl_public_decrypt(hex2bin($certSigner_signatureField), $decrypted, $extracert, OPENSSL_PKCS1_PADDING)) { + p_debug(" OK got issuer."); + $certIssuer_parse = x509::readcert($extracert, 'oid'); // Parsing Issuer cert + $ltvResult['issuer'] = x509::get_cert($extracert); + } else { + p_debug(" FAIL!"); + } + $i++; + } + } else { + p_error(" FAILED! no extracerts available"); + } + } + + } + + if($ltvResult['issuer']) { + if(!empty($ocspURI)) { + p_debug(" OCSP start..."); + $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']; + p_debug(" OCSP create request..."); + if($ocspReq = x509::ocsp_request($ocspReq_serialNumber, $ocspReq_issuerNameHash, $ocspReq_issuerKeyHash)) { + p_debug(" OK."); + $ocspBinReq = pack("H*", $ocspReq); + $reqData = array( + 'data'=>$ocspBinReq, + 'uri'=>$ocspURI, + 'req_contentType'=>'application/ocsp-request', + 'resp_contentType'=>'application/ocsp-response' + ); + p_debug(" OCSP send request to \"$ocspURI\"..."); + if($ocspResp = self::sendReq($reqData)) { + p_debug(" OK."); + p_debug(" OCSP parsing response..."); + if($ocsp_parse = x509::ocsp_response_parse($ocspResp, $return)) { + p_debug(" OK."); + p_debug(" OCSP check cert validity..."); + $certStatus = $ocsp_parse['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses'][0]['certStatus']; + if($certStatus == 'valid') { + p_debug(" OK. VALID."); + $ocspRespHex = $ocsp_parse['hexdump']; + $ltvResult['ocsp'] = $ocspRespHex; + } else { + p_warning(" FAILED! cert not valid, status:\"".strtoupper($certStatus)."\""); + } + } else { + p_warning(" FAILED! Ocsp server status \"$return\""); + } + } else { + p_warning(" FAILED!"); + } + } else { + p_warning(" FAILED!"); + } + } + + if(!$ltvResult['ocsp']) {// CRL not processed if OCSP validation already success + if(!empty($crlURIorFILE)) { + p_debug(" processing CRL validation since OCSP not done/failed..."); + p_debug(" getting crl from \"$crlURIorFILE\"..."); + if($crl = @file_get_contents($crlURIorFILE)) { + p_debug(" OK. size ".round(strlen($crl)/1024,2)."Kb"); + p_debug(" reading crl..."); + if($crlread=x509::crl_read($crl)) { + p_debug(" OK"); + p_debug(" verify crl signature..."); + $crl_signatureField = $crlread['parse']['signature']; + if(openssl_public_decrypt(hex2bin($crl_signatureField), $decrypted, x509::x509_der2pem($ltvResult['issuer']), OPENSSL_PKCS1_PADDING)) { + p_debug(" OK"); + p_debug(" check CRL validity..."); + $crl_parse=$crlread['parse']; + $thisUpdate = str_pad($crl_parse['TBSCertList']['thisUpdate'], 15, "20", STR_PAD_LEFT); + $thisUpdateTime = strtotime($thisUpdate); + $nextUpdate = str_pad($crl_parse['TBSCertList']['nextUpdate'], 15, "20", STR_PAD_LEFT); + $nextUpdateTime = strtotime($nextUpdate); + $nowz = strtotime("now"); + if(($nowz-$thisUpdateTime) < 0) { // 0 sec after valid + p_error(" FAILED! not yet valid! valid at ".date("d/m/Y H:i:s", $thisUpdateTime)); + } elseif(($nextUpdateTime-$nowz) < 1) { // not accept if crl 1 sec remain to expired + p_error(" FAILED! Expired crl at ".date("d/m/Y H:i:s", $nextUpdateTime)." and now ".date("d/m/Y H:i:s", $nowz)."!"); + } else { + p_debug(" OK CRL still valid until ".date("d/m/Y H:i:s", $nextUpdateTime).""); + $crlCertValid=true; + p_debug(" check if cert not revoked..."); + 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; + p_error(" FAILED! Certificate Revoked!"); + } + } + if($crlCertValid == true) { + p_debug(" OK. VALID"); + $crlHex = current(unpack('H*', $crlread['der'])); + $ltvResult['crl'] = $crlHex; + } + } + } else { + p_error(" FAILED! Wrong CRL."); + } + } else { + p_error(" FAILED! can't read crl"); + } + } else { + p_error(" FAILED! can't get crl"); + } + } + } + } + if(!$ltvResult['issuer']) { + return false; + } + if(!$ltvResult['ocsp'] && !$ltvResult['crl']) { + return false; + } + return $ltvResult; + } + + /** + * Perform PKCS7 Signing + * @param string $binaryData + * @return string hex + padding 0 + * @public + */ + public function pkcs7_sign($binaryData) { + $hexOidHashAlgos = array( + 'md2'=>'06082A864886F70D0202', + 'md4'=>'06082A864886F70D0204', + 'md5'=>'06082A864886F70D0205', + 'sha1'=>'06052B0E03021A', + 'sha224'=>'0609608648016503040204', + 'sha256'=>'0609608648016503040201', + 'sha384'=>'0609608648016503040202', + 'sha512'=>'0609608648016503040203' + ); + $hashAlgorithm = $this->signature_data['hashAlgorithm']; + if(!array_key_exists($hashAlgorithm, $hexOidHashAlgos)) { + p_error("not support hash algorithm!"); + return false; + } + p_debug("hash algorithm is \"$hashAlgorithm\""); + $x509 = new x509; + if(!$certParse = $x509->readcert($this->signature_data['signcert'])) { + p_error("certificate error! check certificate"); + } + $hexEmbedCerts[] = bin2hex($x509->get_cert($this->signature_data['signcert'])); + $appendLTV = ''; + $ltvData = $this->signature_data['ltv']; + if(!empty($ltvData)) { + p_debug(" LTV Validation start..."); + $appendLTV = ''; + $LTVvalidation_ocsp = ''; + $LTVvalidation_crl = ''; + $LTVvalidation_issuer = ''; + $LTVvalidationEnd = false; + + $isRootCA = false; + if($certParse['tbsCertificate']['issuer']['hexdump'] == $certParse['tbsCertificate']['subject']['hexdump']) { // check whether root ca + if(openssl_public_decrypt(hex2bin($certParse['signatureValue']), $decrypted, x509::x509_der2pem($x509->get_cert($this->signature_data['signcert'])), OPENSSL_PKCS1_PADDING)) { + p_debug("***** \"{$certParse['tbsCertificate']['subject']['2.5.4.3'][0]}\" is a ROOT CA. No validation performed ***"); + $isRootCA = true; + } + } + if($isRootCA == false) { + $i = 0; + $LTVvalidation = true; + $certtoCheck = $certParse; + while($LTVvalidation !== false) { + p_debug("========= $i checking \"{$certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0]}\"==============="); + $LTVvalidation = self::LTVvalidation($certtoCheck); + $i++; + if($LTVvalidation) { + $curr_issuer = $LTVvalidation['issuer']; + $certtoCheck = $x509->readcert($curr_issuer, 'oid'); + if(@$LTVvalidation['ocsp'] || @$LTVvalidation['crl']) { + $LTVvalidation_ocsp .= $LTVvalidation['ocsp']; + $LTVvalidation_crl .= $LTVvalidation['crl']; + $hexEmbedCerts[] = bin2hex($LTVvalidation['issuer']); + } + + if($certtoCheck['tbsCertificate']['issuer']['hexdump'] == $certtoCheck['tbsCertificate']['subject']['hexdump']) { // check whether root ca + if(openssl_public_decrypt(hex2bin($certtoCheck['signatureValue']), $decrypted, $x509->x509_der2pem($curr_issuer), OPENSSL_PKCS1_PADDING)) { + p_debug("========= FINISH Reached ROOT CA \"{$certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0]}\"==============="); + $LTVvalidationEnd = true; + break; + } + } + } + } + + if($LTVvalidationEnd) { + p_debug(" LTV Validation SUCCESS\n"); + $ocsp = ''; + if(!empty($LTVvalidation_ocsp)) { + $ocsp = asn1::expl(1, + asn1::seq( + $LTVvalidation_ocsp + ) + ); + } + $crl = ''; + if(!empty($LTVvalidation_crl)) { + $crl = asn1::expl(0, + asn1::seq( + $LTVvalidation_crl + ) + ); + } + $appendLTV = asn1::seq( + "06092A864886F72F010108". // adbe-revocationInfoArchival (1.2.840.113583.1.1.8) + asn1::set( + asn1::seq( + $ocsp. + $crl + ) + ) + ); + } else { + p_warning(" LTV Validation FAILED!\n"); + } + } + foreach($this->signature_data['extracerts'] ?? [] as $extracert) { + $hex_extracert = bin2hex($x509->x509_pem2der($extracert)); + if(!in_array($hex_extracert, $hexEmbedCerts)) { + $hexEmbedCerts[] = $hex_extracert; + } + } + } + $messageDigest = hash($hashAlgorithm, $binaryData); + $authenticatedAttributes= asn1::seq( + '06092A864886F70D010903'. //OBJ_pkcs9_contentType 1.2.840.113549.1.9.3 + asn1::set('06092A864886F70D010701') //OBJ_pkcs7_data 1.2.840.113549.1.7.1 + ). + asn1::seq( // signing time + '06092A864886F70D010905'. //OBJ_pkcs9_signingTime 1.2.840.113549.1.9.5 + asn1::set( + asn1::utime(date("ymdHis")) //UTTC Time + ) + ). + asn1::seq( // messageDigest + '06092A864886F70D010904'. //OBJ_pkcs9_messageDigest 1.2.840.113549.1.9.4 + asn1::set(asn1::oct($messageDigest)) + ). + $appendLTV; + $tohash = asn1::set($authenticatedAttributes); + $hash = hash($hashAlgorithm, hex2bin($tohash)); + $toencrypt = asn1::seq( + asn1::seq($hexOidHashAlgos[$hashAlgorithm]."0500"). // OBJ $messageDigest & OBJ_null + asn1::oct($hash) + ); + $pkey = $this->signature_data['privkey']; + if(!openssl_private_encrypt(hex2bin($toencrypt), $encryptedDigest, $pkey, OPENSSL_PKCS1_PADDING)) { + p_error("openssl_private_encrypt error! can't encrypt"); + return false; + } + $hexencryptedDigest = bin2hex($encryptedDigest); + $timeStamp = ''; + if(!empty($this->signature_data['tsa'])) { + p_debug(" Timestamping process start..."); + if($TSTInfo = self::createTimestamp($encryptedDigest, $hashAlgorithm)) { + p_debug(" Timestamping SUCCESS."); + $TimeStampToken = asn1::seq( + "060B2A864886F70D010910020E". // OBJ_id_smime_aa_timeStampToken 1.2.840.113549.1.9.16.2.14 + asn1::set($TSTInfo) + ); + $timeStamp = asn1::expl(1,$TimeStampToken); + } else { + p_warning(" Timestamping FAILED!"); + } + } + $issuerName = $certParse['tbsCertificate']['issuer']['hexdump']; + $serialNumber = $certParse['tbsCertificate']['serialNumber']; + $signerinfos = asn1::seq( + asn1::int('1'). + asn1::seq($issuerName . asn1::int($serialNumber)). + asn1::seq($hexOidHashAlgos[$hashAlgorithm].'0500'). + asn1::expl(0, $authenticatedAttributes). + asn1::seq( + '06092A864886F70D010101'. //OBJ_rsaEncryption + '0500' + ). + asn1::oct($hexencryptedDigest). + $timeStamp + ); + $certs = asn1::expl(0,implode('', $hexEmbedCerts)); + $pkcs7contentSignedData = asn1::seq( + asn1::int('1'). + asn1::set(asn1::seq($hexOidHashAlgos[$hashAlgorithm].'0500')). + asn1::seq('06092A864886F70D010701'). //OBJ_pkcs7_data + $certs. + asn1::set($signerinfos) + ); + $pkcs7ContentInfo = asn1::seq( + "06092A864886F70D010702". // Hexadecimal form of pkcs7-signedData + asn1::expl(0,$pkcs7contentSignedData) + ); + return $pkcs7ContentInfo; + } +} diff --git a/src/helpers/asn1.php b/src/helpers/asn1.php new file mode 100644 index 0000000..7c4e27c --- /dev/null +++ b/src/helpers/asn1.php @@ -0,0 +1,258 @@ + "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 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 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(in_array($k, ['13', '18'])) { + $info['value'] = hex2bin($info['value_hex']); + } else if(in_array($k, ['03', '02', 'a04'])) { + $info['value'] = $v; + } 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 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 various dynamic function for asn1 + */ + private static function asn1Tag($name) { + $functionList = array( + 'seq'=>'30', + 'oct'=>'04', + 'obj'=>'06', + 'bit'=>'03', + 'printable'=>'13', + 'int'=>'02', + 'set'=>'31', + 'expl'=>'a', + 'utime'=>'17', + 'gtime'=>'18', + 'utf8'=>'0c', + 'ia5'=>'16', + 'visible'=>'1a', + 't61'=>'14', + 'impl'=>'80', + 'other'=>'' + ); + if(array_key_exists($name, $functionList)) { + return $functionList[$name]; + } else { + // echo "func \"$name\" not available"; + return false; + } + } + + public static function __callStatic($func, $params) { + $func = strtolower($func); + $asn1Tag = self::asn1Tag($func); + if($asn1Tag !== false){ + $num = $asn1Tag; //valu of array + $hex = $params[0]; + $val = $hex; + if(in_array($func, ['printable', 'utf8', 'ia5', 'visible', 't61'])) { // ($string) + $val = bin2hex($hex); + } + if($func == 'int') { + $val = (strlen($val)%2 != 0)?"0$val":"$val"; + } + if($func == 'expl') { //expl($num, $hex) + $num = $num.$params[0]; + $val = $params[1]; + } + if($func == 'impl') { //impl($num="0") + $val = (!$val)?"00":$val; + $val = (strlen($val)%2 != 0)?"0$val":$val; + return $num.$val; + } + if($func == 'other') { //OTHER($id, $hex, $chr = false) + $id = $params[0]; + $hex = $params[1]; + $chr = @$params[2]; + $str = $hex; + if($chr != false) { + $str = bin2hex($hex); + } + $ret = "$id".self::asn1_header($str).$str; + return $ret; + } + if($func == 'utime') { + $time = $params[0]; //yymmddhhiiss + $oldTz = date_default_timezone_get(); + date_default_timezone_set("UTC"); + $time = date("ymdHis", $time); + date_default_timezone_set($oldTz); + $val = bin2hex($time."Z"); + } + if($func == 'gtime') { + if(!$time = strtotime($params[0])) { + // echo "asn1::GTIME function strtotime cant recognize time!! please check at input=\"{$params[0]}\""; + return false; + } + $oldTz = date_default_timezone_get(); + // date_default_timezone_set("UTC"); + $time = date("YmdHis", $time); + date_default_timezone_set($oldTz); + $val = bin2hex($time."Z"); + } + $hdr = self::asn1_header($val); + return $num.$hdr.$val; + } else { + // echo "asn1 \"$func\" not exists!"; + } + } + // =====End ASN.1 Builder section===== +} diff --git a/src/helpers/x509.php b/src/helpers/x509.php new file mode 100644 index 0000000..af4a000 --- /dev/null +++ b/src/helpers/x509.php @@ -0,0 +1,984 @@ +'06082A864886F70D0202', + 'md4'=>'06082A864886F70D0204', + 'md5'=>'06082A864886F70D0205', + 'sha1'=>'06052B0E03021A', + 'sha224'=>'0609608648016503040204', + 'sha256'=>'0609608648016503040201', + 'sha384'=>'0609608648016503040202', + 'sha512'=>'0609608648016503040203' + ); + if(!array_key_exists($hashAlg, $hexOidHashAlgos)) { + return false; + } + $hash = hash($hashAlg, $binaryData); + $tsReqData = asn1::seq( + asn1::int(1). + asn1::seq( + asn1::seq($hexOidHashAlgos[$hashAlg]."0500"). // object OBJ $hexOidHashAlgos[$hashAlg] & OBJ_null + asn1::oct($hash) + ). + asn1::int(hash('crc32', rand()).'001'). // tsa nonce + '0101ff' // req return cert + ); + return hex2bin($tsReqData); + } + + /** + * Calculate 32bit (8 hex) openssl subject hash old and new + * @param string $hex_subjSequence hex subject name sequence + * @return array subject hash old and new + */ + private 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; + } + 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['depth']); + unset($ocsp['type']); + unset($ocsp['typeName']); + unset($ocsp['value_hex']); + } + } + //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 + 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; + } + $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 (convert hex to oid) + * @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, "."); + } +}