cmpOpenSSLVersion; $this->cmpOpenSslConf = array ( "GLOBAL" => array ( "oid_section" => "OIDs", ), "OIDs" => array ( "cmpExtOid" => "1.2.3.20190828", "certificateTemplateName" => "\${cmpExtOid}.1", ), "ca" => array ( "default_ca" => "cmp_ca", ), "cmp_ca" => array ( "dir" => ".", "certs" => "\$dir", "crl_dir" => "\$dir}", "database" => "\$dir/index.txt", "new_certs_dir" => "\$dir", "serial" => "\$dir/serial", "crl" => "\$dir/crl.pem", "certificate" => "\$dir/ca.crt", "private_key" => "\$dir/ca.key", "RANDFILE" => "\$dir/.rand", "x509_extensions" => "cmp_x509_ext_basic", "crl_extensions" => "cmp_crl_ext", "default_days" => 3650, "default_crl_days" => 30, "default_md" => "sha256", "preserve" => "no", "policy" => "cmp_policy_anything", ), "cmp_policy_anything" => array ( "countryName" => "optional", "stateOrProvinceName" => "optional", "localityName" => "optional", "organizationName" => "optional", "organizationalUnitName" => "optional", "commonName" => "supplied", "name" => "optional", "emailAddress" => "optional", ), "req" => array ( "default_bits" => 2048, "default_keyfile" => "privkey.pem", "default_md" => "sha256", "distinguished_name" => "cmp_dst_ext", "x509_extensions" => "cmp_x509_ext_rsa", "req_extensions" => "cmp_req_ext_v3", ), "cmp_dst_ext" => array ( "commonName" => "Common Name (eg: your user, host, or server name)", "commonName_max" => 64, "commonName_default" => $claver . "-commonName_default", // "certificateTemplateName" => $claver , // "-certificateTemplateName", "certificateTemplateName_default" => $claver , ), "cmp_req_ext_v3" => array ( "certificateTemplateName" => "ASN1:PRINTABLESTRING:CustomUserOffline", ), "cmp_org" => array ( "countryName" => "Country Name (2 letter code)", "countryName_default" => "RU", "countryName_min" => 2, "countryName_max" => 2, "stateOrProvinceName" => "State or Province Name (full name)", "stateOrProvinceName_default" => $claver . "-stateOrProvinceName_default", "localityName" => "Locality Name (eg, city)", "localityName_default" => $claver . "-localityName_default", "organizationName" => "Organization Name (eg, company)", "organizationName_default" => $claver . "-organizationName_default", "organizationalUnitName" => "Organizational Unit Name (eg, section)", "organizationalUnitName_default" => $claver . "-organizationalUnitName_default", "emailAddress" => "Email Address", "emailAddress_default" => $claver . "@example.org", "emailAddress_max" => 64, ), "cmp_crl_ext" => array ( "authorityKeyIdentifier" => "keyid:always,issuer:always", ), "cmp_x509_ext_basic" => array ( "basicConstraints" => "CA:FALSE", "subjectKeyIdentifier" => "hash", "authorityKeyIdentifier" => "keyid,issuer:always", ), "cmp_x509_ext_rsa" => array ( "subjectKeyIdentifier" => "hash", "authorityKeyIdentifier" => "keyid:always,issuer:always", "basicConstraints" => "CA:true", "keyUsage" => "cRLSign, keyCertSign", ), "cmp_x509_ext_srv" => array ( "basicConstraints" => "CA:FALSE", "nsCertType" => "server", "nsComment" => "\"OpenSSL Generated Server Certificate\"", "subjectKeyIdentifier" => "hash", "authorityKeyIdentifier" => "keyid,issuer:always", "extendedKeyUsage" => "serverAuth", "keyUsage" => "digitalSignature, keyEncipherment", ), "cmp_x509_ext_cli" => array ( "basicConstraints" => "CA:FALSE", "nsCertType" => "client", "nsComment" => "\"OpenSSL Generated Client Certificate\"", "subjectKeyIdentifier" => "hash", "authorityKeyIdentifier" => "keyid,issuer", "keyUsage" => "critical, nonRepudiation, digitalSignature, keyEncipherment", "extendedKeyUsage" => "clientAuth", ), ); return true; } function cmpOpenSslConfTemp() { $a = array(); if(!isset($this->cmpOpenSslConf["GLOBAL"])) { $this->cmpOpenSslConfSetDefault(); } if(!isset($this->cmpOpenSslConf["GLOBAL"])) { throw new Exception("No GLOBAL section"); } foreach($this->cmpOpenSslConf["GLOBAL"] as $key => $val) { $a[] = "\t" . $key . "=" . $val . ""; } foreach($this->cmpOpenSslConf as $sec => $prm) { if($sec == "GLOBAL") continue; $a[] = ""; $a[] = "[" . $sec . "]"; foreach($prm as $key => $val) { $a[] = "\t" . $key . " = " . $val . ""; } } $a[] = ""; $t = join("\n", $a); $rand = mt_rand(100000, 999999); $file = "/tmp/openssl-$rand.conf"; $w = @file_put_contents($file, $t); if(!$w) { throw new Exception("Can't write file '$file'"); } return $file; } function cmpOpenSslConfRead($file) { $t = file_get_contents($file); $a = explode("\n", $t); $sec = "GLOBAL"; $key = ""; $val = ""; $obj = array(); for($i = 0; $i < count($a); $i++) { $a[$i] = trim($a[$i], "\n \t\r"); if(!$a[$i]) continue; if(substr($a[$i], 0, 1) == "#") continue; $m = array(); $s = ""; if(preg_match("/^(.+)#.+$/", $a[$i], $m)) { $s = trim($m[1], "\n \t\r"); } else { $s = $a[$i]; } if(preg_match("/^\[(.+)\]$/", $a[$i], $m)) { $sec = trim($m[1], "\n \t\r"); if(!isset($obj[$sec])) { $obj[$sec] = array(); } continue; } $b = explode("=", $s, 2); $key = trim($b[0], "\n \t\r"); $val = trim($b[1], "\n \t\r"); // echo "STR: " . $sec . " === " . $key . " === " . $val . "\n"; $obj[$sec][$key] = $val; } $this->cmpOpenSslConf = $obj; // var_export($obj); return true; } function cmpOpenSslParm($parm, $key, $def = "") { if(!is_array($parm)) return $def; if(!isset($parm[$key])) return $def; return $parm[$key]; } function cmpOpenSslParmConfSection($parm, $sec, &$out) { if(!isset($this->cmpOpenSslConf[$sec])) { ; } foreach($this->cmpOpenSslConf[$sec] as $key => $val) { $m = array(); if(!preg_match("/^(.+)_default$/", $key, $m)) continue; $key = $m[1]; $out[$key] = $this->cmpOpenSslParm( $parm, $key, $val ); } return true; } function cmpOpenSslCertGetInfo($crt, $prv, &$out = null) { $txtPub = ""; $txtPrv = ""; openssl_x509_export($crt , $txtPub, false ); openssl_pkey_export($prv , $txtPrv, NULL ); if(!$out) $out = array(); $out["fingerprint"] = array( "default" => @openssl_x509_fingerprint($crt), "sha256" => @openssl_x509_fingerprint($crt, "sha256") ); $out["err"] = array(); do { $cert = @openssl_x509_parse($txtPub); if(!$cert) { $out["err"][] = array(__LINE__, openssl_error_string()); break; } if(!isset($cert["subject"])) { $out["err"][] = array(__LINE__, "Invalid subject"); break; } if(!isset($cert["subject"]["CN"])) { $out["err"][] = array(__LINE__, "Invalid subject CN"); break; } if(!isset($cert["extensions"])) { $out["err"][] = array(__LINE__, "Invalid extensions"); break; } if(!isset($cert["extensions"]["authorityKeyIdentifier"])) { $out["err"][] = array(__LINE__, "Invalid extensions authorityKeyIdentifier"); break; } if(!isset($cert["extensions"]["subjectKeyIdentifier"])) { $out["err"][] = array(__LINE__, "Invalid extensions subjectKeyIdentifier"); break; } } while(0); if($out["err"]) { $this->d($out["err"]); return NULL; } unset($out["err"]); $nrmz = array( /* "A" => "a", "B" => "b", "C" => "c", "D" => "d", "E" => "e", "F" => "f", */ ":" => ""); do { if(preg_match("/^[0-9A-F:]+$/", $cert["extensions"]["authorityKeyIdentifier"])) { $certAuth = $cert["extensions"]["authorityKeyIdentifier"]; break; } $a = explode("\n", $cert["extensions"]["authorityKeyIdentifier"]); $b = null; $c = array(); for($i = 0; $i < count($a); $i++) { $b = explode(":", $a[$i], 2); $c[$b[0]] = $b[1]; } // $this->d($c); if(!isset($c["keyid"]) || !$c["keyid"]) { $out["err"][] = array(__LINE__, "Invalid authorityKeyIdentifier: '". $cert["extensions"]["authorityKeyIdentifier"] ."'"); return NULL; } $certAuth = strtolower($c["keyid"]); } while(0); $certAuth = strtr($certAuth, $nrmz); $certAuth = strtolower($certAuth); $certSubj = $cert["extensions"]["subjectKeyIdentifier"]; $certSubj = strtr($certSubj, $nrmz); $certSubj = strtolower($certSubj); $out["certAuth" ] = $certAuth; $out["validFrom"] = date("Y-m-d H:i:s", $cert["validFrom_time_t" ]); $out["validTo" ] = date("Y-m-d H:i:s", $cert["validTo_time_t" ]); $out["certSubj" ] = $certSubj; $out["certCN" ] = $cert["subject"]["CN"]; $out["public" ] = $txtPub; $out["private" ] = $txtPrv; return $out; } function cmpOpenSslCaGen($parm, &$out = null) { try { $confFile = $this->cmpOpenSslConfTemp(); $digest_alg = $this->cmpOpenSslParm($parm, "digest_alg" , "sha256" ); $x509_extensions = $this->cmpOpenSslParm($parm, "x509_extensions" , "cmp_x509_ext_rsa" ); $days = $this->cmpOpenSslParm($parm, "days" , 365 ); $outfile = $this->cmpOpenSslParm($parm, "outfile" ); $serial = $this->cmpOpenSslParm($parm, "serial" , mt_rand(0, PHP_INT_MAX) ); $this->caDN = array(); $this->cmpOpenSslParmConfSection($parm, "cmp_org", $this->caDN); $this->cmpOpenSslParmConfSection($parm, "cmp_dst_ext", $this->caDN); if(!$this->caDN["commonName"]) { throw new Exception("Empty commonName"); } $confFile = $this->cmpOpenSslConfTemp(); $pcsrs = array( "digest_alg" => $digest_alg , ); $pcsrn = array( "config" => $confFile , "digest_alg" => $digest_alg , "x509_extensions" => $x509_extensions , ); $ppkey = array( "config" => $confFile , "encrypt_key" => $this->cmpOpenSslParm($parm, "encrypt_key" , false ), "private_key_type" => $this->cmpOpenSslParm($parm, "private_key_type" , OPENSSL_KEYTYPE_RSA ), "private_key_bits" => $this->cmpOpenSslParm($parm, "private_key_bits" , 4096 ), ); $this->caPrv = openssl_pkey_new($ppkey); if(!$this->caPrv) { throw new Exception("openssl_pkey_new: " . openssl_error_string()); } $csr = openssl_csr_new($this->caDN, $this->caPrv, $pcsrn); if(!$csr) { throw new Exception("openssl_csr_new: " . openssl_error_string()); } // Создание самоподписанного сертификата со сроком жизни $days дней $this->caCrt = openssl_csr_sign($csr, null, $this->caPrv, $days, $pcsrs, $serial); if(!$this->caCrt) { throw new Exception("openssl_csr_sign: " . openssl_error_string()); } @unlink($confFile); } catch(Exception|Throwable $e) { @unlink($confFile); $this->e($e); return NULL; } $this->cmpOpenSslCertGetInfo($this->caCrt, $this->caPrv, $out); $txtPub = ""; $txtPrv = ""; openssl_x509_export($this->caCrt , $txtPub, false ); openssl_pkey_export($this->caPrv , $txtPrv, NULL ); if($out !== null) { if(!$this->cmpOpenSslCertGetInfo($this->caCrt, $this->caPrv, $out)) return NULL; } if($outfile) { openssl_x509_export_to_file($this->caCrt , "$outfile.crt" ); openssl_pkey_export_to_file($this->caPrv , "$outfile.prv" , NULL ); } $this->caPub = openssl_pkey_get_public($this->caCrt); if(!$this->caPub) { throw new Exception("openssl_pkey_get_public: " . openssl_error_string()); } // var_export($csrout); // echo "\n"; return true; } function cmpOpenSslCaCertFromFile($file) { // var_dump(openssl_get_cert_locations()); $this->caCrtFile = $file; $text = @file_get_contents($file); if($this->cmpOpenSslCaCertFromText($text)) { $this->caCrtFile = $file; return true; } return NULL; } function cmpOpenSslCaCertFromText($text) { $this->caCrtFile = ""; $this->caCrtPEM = $text; if(!$this->caCrtPEM) { throw new Exception("Invalid CA text"); } // openssl_get_privatekey() $this->caCrt = openssl_x509_read( $this->caCrtPEM ); if(!$this->caCrt) { throw new Exception("openssl_x509_read: " . openssl_error_string()); } // openssl_x509_parse(file_get_contents($file)); $this->caPub = openssl_pkey_get_public($this->caCrt); if(!$this->caPub) { throw new Exception("openssl_pkey_get_public: " . openssl_error_string()); } $pkey = openssl_pkey_get_details($this->caPub); if(!$pkey) { throw new Exception("openssl_pkey_get_details: " . openssl_error_string()); } $this->caPubPEM = $pkey["key"]; $this->caPub = openssl_pkey_get_public($this->caPubPEM); if(!$this->caPub) { throw new Exception("openssl_pkey_get_public: " . openssl_error_string()); } return true; } function cmpOpenSslCaPrivFromFile($file, $pass = NULL) { $this->caPrvFile = $file; $text = @file_get_contents($file); if($this->cmpOpenSslCaPrivFromText($text, $pass)) { $this->caPrvFile = $file; return true; } return NULL; } function cmpOpenSslCaPrivFromText($text, $pass = NULL) { $this->caPrvFile = ""; /* if(@$file) $this->caPrvPEM = @file_get_contents($file); else $this->caPrvPEM = ""; if(!$text) { $this->e(__LINE__, "Invalid CA private key text"); return NULL; } */ $this->caPrvPEM = $text; $this->caPrv = openssl_pkey_get_private($this->caPrvPEM, $pass); if(!$this->caPrv) { throw new Exception("openssl_pkey_get_private: " . openssl_error_string()); } $sign = ""; $test = "test-test"; //Вычисляем подпись if(!openssl_sign($test, $sign, $this->caPrv, "sha1WithRSAEncryption")) { throw new Exception("openssl_sign: " . openssl_error_string()); } switch( openssl_verify($test, $sign, $this->caPub, OPENSSL_ALGO_SHA1) ) { case 1: // echo "корректна\n"; return true; case 0: // echo "некорректна\n"; $this->e("Incorrect CA private key"); return NULL; case -1: $this->e(openssl_error_string()); return NULL; } return true; } function cmpOpenSslCertGen($parm, &$out = null) { try { $confFile = $this->cmpOpenSslConfTemp(); $digest_alg = $this->cmpOpenSslParm($parm, "digest_alg" , "sha256" ); $x509_extensions = $this->cmpOpenSslParm($parm, "x509_extensions" , "" ); $days = $this->cmpOpenSslParm($parm, "days" , 365 ); $outfile = $this->cmpOpenSslParm($parm, "outfile" ); $serial = $this->cmpOpenSslParm($parm, "serial" , mt_rand(0, PHP_INT_MAX) ); $this->cliDN = array(); $this->cmpOpenSslParmConfSection($parm, "cmp_org", $this->cliDN); $this->cmpOpenSslParmConfSection($parm, "cmp_dst_ext", $this->cliDN); if(!$this->cliDN["commonName"]) { throw new Exception("Empty commonName"); } if(!$x509_extensions) { throw new Exception("Empty x509_extensions"); } $pcsrn = array( "config" => $confFile , "digest_alg" => $digest_alg , "x509_extensions" => $x509_extensions , ); $ppkey = array( "config" => $confFile , "encrypt_key" => $this->cmpOpenSslParm($parm, "encrypt_key" , false ), "private_key_type" => $this->cmpOpenSslParm($parm, "private_key_type" , OPENSSL_KEYTYPE_RSA ), "private_key_bits" => $this->cmpOpenSslParm($parm, "private_key_bits" , 4096 ), ); $this->cliPrv = openssl_pkey_new($ppkey); if(!$this->cliPrv) { throw new Exception("openssl_csr_new: " . openssl_error_string()); } $csr = @openssl_csr_new($this->cliDN, $this->cliPrv, $pcsrn); if(!$csr) { throw new Exception("openssl_csr_new: " . openssl_error_string()); } $this->cliCrt = openssl_csr_sign($csr, $this->caCrt, $this->caPrv, $days, $pcsrn, $serial); if(!$this->cliCrt) { throw new Exception("openssl_csr_sign: " . openssl_error_string()); } @unlink($confFile); } catch(Exception|Throwable $e) { @unlink($confFile); $this->e($e); return NULL; } $txtPub = ""; $txtPrv = ""; openssl_x509_export($this->cliCrt, $txtPub, false ); openssl_pkey_export($this->cliPrv, $txtPrv, NULL ); if($out !== null) { if(!$this->cmpOpenSslCertGetInfo($this->cliCrt, $this->cliPrv, $out)) return NULL; } if($outfile) { openssl_x509_export_to_file($this->cliCrt , "$outfile.crt" ); openssl_pkey_export_to_file($this->cliPrv , "$outfile.prv" , NULL ); } $this->cliPub = openssl_pkey_get_public($this->cliCrt); if(!$this->cliPub) { throw new Exception("openssl_pkey_get_public: " . openssl_error_string()); } // var_export($csrout); // echo "\n"; return true; } function cmpOpenSslCertClientGen($parm = NULL, &$out = null) { $parm["x509_extensions"] = "cmp_x509_ext_cli"; return $this->cmpOpenSslCertGen($parm, $out); } function cmpOpenSslCertServerGen($parm = NULL, &$out = null) { $parm["x509_extensions"] = "cmp_x509_ext_srv"; return $this->cmpOpenSslCertGen($parm, $out); } function cmpOpenSslGenDh($bits = 2048) { $a = array( "openssl", "dhparam", $bits ); if(!method_exists($this, "cmpSysExec")) { $this->d("Invalid method cmpSysExec"); return null; } $arr = $this->cmpSysExec($a, array("return" => "outarr", "noerror" => 1)); if(0) { var_export($arr); echo "\n"; } if($arr[0] != "-----BEGIN DH PARAMETERS-----") { $this->d("Invalid first string"); return null; } $lst = count($arr) - 2; if($arr[$lst] != "-----END DH PARAMETERS-----") { $this->d("Invalid last string"); return null; } return join("\n", $arr); } function cmpOpenVpnGenTa() { $a = array( "openvpn", "--genkey", // Valid keytype arguments are: "secret" // Standard OpenVPN shared secret keys // "tls-crypt" // Alias for secret // "tls-auth" // Alias for secret ); $arr = $this->cmpSysExec($a, array("return" => "outarr", "noerror" => 1)); if(0) { var_export($arr); echo "\n"; } $idx = array(); for($i = 0; $i < count($arr); $i++) { if($arr[$i] == "-----BEGIN OpenVPN Static key V1-----") { $idx["bgn"] = $i; continue; } if($arr[$i] == "-----END OpenVPN Static key V1-----") { $idx["end"] = $i; continue; } } if(!isset($idx["bgn"]) || !isset($idx["end"])) { $this->d("Invalid output key"); return null; } return join("\n", $arr); } function cmpOpenSslGenCrl($caCrtText, $caPrvText, $outCrlFile) { $now = date("Y-m-d H:i:s"); $dir = "/tmp/tmp-ca-dir-" . md5("tmp-ca-dir-" . $now); if(!mkdir($dir)) return null; $caCrtFile = $dir . "/ca.crt"; $caPrvFile = $dir . "/ca.prv"; $indexFile = $dir . "/index.txt"; $serialFile = $dir . "/serial"; file_put_contents($caCrtFile, $caCrtText); file_put_contents($caPrvFile, $caPrvText); file_put_contents($indexFile, ""); file_put_contents($serialFile, ""); $a = array( "cd" , $dir , ["asis", "&&"] , "openssl" , "ca" , "-config" , "65-openssl.cnf" , "-gencrl" , "-out" , $outCrlFile , "-cert" , $caCrtFile , "-keyfile" , $caPrvFile , ); $arr = $this->cmpSysExec($a, array(/* "return" => "outarr", "noerror" => 1 */)); unlink($caCrtFile); unlink($caPrvFile); unlink($indexFile); unlink($serialFile); if(!rmdir($dir)) { $this->d("Can't remove directory '$dir'"); } if(1) { var_export($arr); echo "\n"; } return; } function cmpOpenSslAddCrl($caCrtText, $caPrvText, $outCrlFile, $sjCrtText) { $now = date("Y-m-d H:i:s"); $dir = "/tmp/tmp-ca-dir-" . md5("tmp-ca-dir-" /* . $now . "-" . mt_rand(10000, 99999) */ ); if(!mkdir($dir)) { ; // return null; } $caCrtFile = $dir . "/ca.crt"; $caPrvFile = $dir . "/ca.prv"; $indexFile = $dir . "/index.txt"; $serialFile= $dir . "/serial"; $sjCrtFile = $dir . "/cert.pem"; $crlFile = $dir . "/crl.pem"; if(!is_file($caCrtFile)) file_put_contents($caCrtFile, $caCrtText); if(!is_file($caPrvFile)) file_put_contents($caPrvFile, $caPrvText); file_put_contents($sjCrtFile, $sjCrtText); if(!is_file($indexFile)) file_put_contents($indexFile, ""); if(!is_file($serialFile)) file_put_contents($serialFile, ""); if(is_file($outCrlFile)) { copy($outCrlFile, $crlFile); // $a[] = "-in"; // $a[] = $outCrlFile; $this->d("Add IN"); } $a = array( "cd" , $dir , ["asis", "&&"] , "openssl" , "ca" , "-config" , "65-openssl.cnf" , "-revoke" , $sjCrtFile , // "-out" , // $outCrlFile , "-cert" , $caCrtFile , "-keyfile" , $caPrvFile , ); // $a[] = "-in"; // $a[] = $outCrlFile; $arr = $this->cmpSysExec($a, array(/* "return" => "outarr", "noerror" => 1 */)); if(1) { var_export($arr); echo "\n"; } // unlink($caCrtFile); // unlink($caPrvFile); // unlink($sjCrtFile); // unlink($indexFile); // unlink($serialFile); if(is_file($crlFile)) { copy($crlFile, $outCrlFile); // unlink($crlFile); } // unlink($dir . "/index.txt.attr"); // unlink($dir . "/index.txt.old"); // if(!rmdir($dir)) { // $this->d("Can't remove directory '$dir'"); // } return; } // trait }