1<?php 2 3class CertificateGenerator 4{ 5 const CONFIG = __DIR__. DIRECTORY_SEPARATOR . 'openssl.cnf'; 6 7 /** @var OpenSSLCertificate */ 8 private $ca; 9 10 /** @var resource */ 11 private $caKey; 12 13 /** @var resource|null */ 14 private $lastCert; 15 16 /** @var resource|null */ 17 private $lastKey; 18 19 public function __construct() 20 { 21 if (!extension_loaded('openssl')) { 22 throw new RuntimeException( 23 'openssl extension must be loaded to generate certificates' 24 ); 25 } 26 $this->generateCa(); 27 } 28 29 /** 30 * @param int|null $keyLength 31 * @return resource 32 */ 33 private static function generateKey($keyLength = null) 34 { 35 if (null === $keyLength) { 36 $keyLength = 2048; 37 } 38 39 return openssl_pkey_new([ 40 'private_key_bits' => $keyLength, 41 'private_key_type' => OPENSSL_KEYTYPE_RSA, 42 'encrypt_key' => false, 43 ]); 44 } 45 46 private function generateCa() 47 { 48 $this->caKey = self::generateKey(); 49 $dn = [ 50 'countryName' => 'GB', 51 'stateOrProvinceName' => 'Berkshire', 52 'localityName' => 'Newbury', 53 'organizationName' => 'Example Certificate Authority', 54 'commonName' => 'CA for PHP Tests' 55 ]; 56 57 $this->ca = openssl_csr_sign( 58 openssl_csr_new( 59 $dn, 60 $this->caKey, 61 [ 62 'x509_extensions' => 'v3_ca', 63 'config' => self::CONFIG, 64 ] 65 ), 66 null, 67 $this->caKey, 68 2, 69 [ 70 'config' => self::CONFIG, 71 ] 72 ); 73 } 74 75 public function getCaCert() 76 { 77 $output = ''; 78 openssl_x509_export($this->ca, $output); 79 80 return $output; 81 } 82 83 public function saveCaCert($file) 84 { 85 openssl_x509_export_to_file($this->ca, $file); 86 } 87 88 private function generateCertAndKey( 89 $commonNameForCert, $file, $keyLength = null, $subjectAltName = null 90 ) { 91 $dn = [ 92 'countryName' => 'BY', 93 'stateOrProvinceName' => 'Minsk', 94 'localityName' => 'Minsk', 95 'organizationName' => 'Example Org', 96 ]; 97 if ($commonNameForCert !== null) { 98 $dn['commonName'] = $commonNameForCert; 99 } 100 101 $subjectAltNameConfig = 102 $subjectAltName ? "subjectAltName = $subjectAltName" : ""; 103 $configCode = <<<CONFIG 104[ req ] 105distinguished_name = req_distinguished_name 106default_md = sha256 107default_bits = 1024 108 109[ req_distinguished_name ] 110 111[ v3_req ] 112basicConstraints = CA:FALSE 113keyUsage = nonRepudiation, digitalSignature, keyEncipherment 114$subjectAltNameConfig 115 116[ usr_cert ] 117basicConstraints = CA:FALSE 118$subjectAltNameConfig 119CONFIG; 120 $configFile = $file . '.cnf'; 121 file_put_contents($configFile, $configCode); 122 123 $config = [ 124 'config' => $configFile, 125 'req_extensions' => 'v3_req', 126 'x509_extensions' => 'usr_cert', 127 ]; 128 129 $this->lastKey = self::generateKey($keyLength); 130 $csr = openssl_csr_new($dn, $this->lastKey, $config); 131 $this->lastCert = openssl_csr_sign( 132 $csr, 133 $this->ca, 134 $this->caKey, 135 /* days */ 2, 136 $config, 137 ); 138 139 return $config; 140 } 141 142 public function saveNewCertAsFileWithKey( 143 $commonNameForCert, $file, $keyLength = null, $subjectAltName = null 144 ) { 145 $config = $this->generateCertAndKey($commonNameForCert, $file, $keyLength, $subjectAltName); 146 147 $certText = ''; 148 openssl_x509_export($this->lastCert, $certText); 149 150 $keyText = ''; 151 openssl_pkey_export($this->lastKey, $keyText, null, $config); 152 153 file_put_contents($file, $certText . PHP_EOL . $keyText); 154 155 unlink($config['config']); 156 } 157 158 public function saveNewCertAndKey( 159 $commonNameForCert, $certFile, $keyFile, $keyLength = null, $subjectAltName = null 160 ) { 161 $config = $this->generateCertAndKey($commonNameForCert, $certFile, $keyLength, $subjectAltName); 162 163 openssl_x509_export_to_file($this->lastCert, $certFile); 164 openssl_pkey_export_to_file($this->lastKey, $keyFile, null, $config); 165 166 unlink($config['config']); 167 } 168 169 public function getCertDigest($algo) 170 { 171 return openssl_x509_fingerprint($this->lastCert, $algo); 172 } 173} 174