CertificateGenerator.inc 4.0 KB

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