1<?php
2
3class CertificateGenerator
4{
5    const CONFIG = __DIR__. DIRECTORY_SEPARATOR . 'openssl.cnf';
6
7    /** @var resource */
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    }
71
72    public function getCaCert()
73    {
74        $output = '';
75        openssl_x509_export($this->ca, $output);
76
77        return $output;
78    }
79
80    public function saveCaCert($file)
81    {
82        openssl_x509_export_to_file($this->ca, $file);
83    }
84
85    public function saveNewCertAsFileWithKey($commonNameForCert, $file, $keyLength = null)
86    {
87        $dn = [
88            'countryName' => 'BY',
89            'stateOrProvinceName' => 'Minsk',
90            'localityName' => 'Minsk',
91            'organizationName' => 'Example Org',
92            'commonName' => $commonNameForCert,
93        ];
94
95        $this->lastKey = self::generateKey($keyLength);
96        $this->lastCert = openssl_csr_sign(
97            openssl_csr_new($dn, $this->lastKey, ['req_extensions' => 'v3_req']),
98            $this->ca,
99            $this->caKey,
100            2
101        );
102
103        $certText = '';
104        openssl_x509_export($this->lastCert, $certText);
105
106        $keyText = '';
107        openssl_pkey_export($this->lastKey, $keyText);
108
109        file_put_contents($file, $certText . PHP_EOL . $keyText);
110    }
111
112    public function getCertDigest($algo)
113    {
114        return openssl_x509_fingerprint($this->lastCert, $algo);
115    }
116}
117