3333 * @method CfsslHandler setClient(Client $client)
3434 */
3535class OpenSslHandler extends AEngineHandler implements IEngineHandler {
36+ /** @var list<string> */
37+ private array $ lastOpenSslErrors = [];
38+
3639 public function __construct (
3740 protected IConfig $ config ,
3841 protected IAppConfig $ appConfig ,
@@ -71,13 +74,20 @@ public function generateRootCert(
7174 throw new EmptyCertificateException ('Common Name (CN) cannot be empty for root certificate ' );
7275 }
7376
74- $ privateKey = openssl_pkey_new ([
77+ $ privateKey = $ this -> createPrivateKey ([
7578 'private_key_bits ' => 2048 ,
7679 'private_key_type ' => OPENSSL_KEYTYPE_RSA ,
7780 ]);
81+ if ($ privateKey === false ) {
82+ throw $ this ->buildOpenSslException ('Failed to generate OpenSSL private key for root certificate ' );
83+ }
7884
79- $ csr = openssl_csr_new ($ this ->getCsrNames (), $ privateKey , ['digest_alg ' => 'sha256 ' ]);
80- $ options = $ this ->getRootCertOptions ();
85+ $ configFile = $ this ->generateCaConfig ();
86+ $ csr = $ this ->createCsr ($ this ->getCsrNames (), $ privateKey , $ this ->getRootCsrOptions ($ configFile ));
87+ if ($ csr === false ) {
88+ throw $ this ->buildOpenSslException ('Failed to generate OpenSSL CSR for root certificate ' );
89+ }
90+ $ options = $ this ->getRootCertOptions ($ configFile );
8191
8292 $ caDays = $ this ->getCaExpiryInDays ();
8393
@@ -97,20 +107,36 @@ public function generateRootCert(
97107 );
98108 $ serialNumber = (int )$ serialNumberString ;
99109
100- $ x509 = openssl_csr_sign ($ csr , null , $ privateKey , $ caDays , $ options , $ serialNumber );
110+ $ x509 = $ this ->signCsr ($ csr , null , $ privateKey , $ caDays , $ options , $ serialNumber );
111+ if ($ x509 === false ) {
112+ throw $ this ->buildOpenSslException ('Failed to sign OpenSSL root certificate ' );
113+ }
101114
102- openssl_csr_export ($ csr , $ csrout );
103- openssl_x509_export ($ x509 , $ certout );
104- openssl_pkey_export ($ privateKey , $ pkeyout );
115+ if (!openssl_csr_export ($ csr , $ csrout )) {
116+ throw $ this ->buildOpenSslException ('Failed to export OpenSSL root CSR ' );
117+ }
118+ if (!openssl_x509_export ($ x509 , $ certout )) {
119+ throw $ this ->buildOpenSslException ('Failed to export OpenSSL root certificate ' );
120+ }
121+ if (!openssl_pkey_export ($ privateKey , $ pkeyout )) {
122+ throw $ this ->buildOpenSslException ('Failed to export OpenSSL private key ' );
123+ }
105124
106125 $ configPath = $ this ->getCurrentConfigPath ();
107126 CertificateHelper::saveFile ($ configPath . '/ca.csr ' , $ csrout );
108127 CertificateHelper::saveFile ($ configPath . '/ca.pem ' , $ certout );
109128 CertificateHelper::saveFile ($ configPath . '/ca-key.pem ' , $ pkeyout );
110129 }
111130
112- private function getRootCertOptions (): array {
113- $ configFile = $ this ->generateCaConfig ();
131+ private function getRootCsrOptions (string $ configFile ): array {
132+ return [
133+ 'digest_alg ' => 'sha256 ' ,
134+ 'config ' => $ configFile ,
135+ 'config_section_name ' => 'req ' ,
136+ ];
137+ }
138+
139+ private function getRootCertOptions (string $ configFile ): array {
114140
115141 return [
116142 'digest_alg ' => 'sha256 ' ,
@@ -142,15 +168,17 @@ public function generateCertificate(): string {
142168
143169 $ this ->inheritRootSubjectFields ($ rootCertificate );
144170
145- $ privateKey = openssl_pkey_new ([
171+ $ privateKey = $ this -> createPrivateKey ([
146172 'private_key_bits ' => 2048 ,
147173 'private_key_type ' => OPENSSL_KEYTYPE_RSA ,
148174 ]);
175+ if ($ privateKey === false ) {
176+ throw $ this ->buildOpenSslException ('Failed to generate OpenSSL private key ' );
177+ }
149178
150- $ csr = @ openssl_csr_new ($ this ->getCsrNames (), $ privateKey , ['digest_alg ' => 'sha256 ' ]);
179+ $ csr = $ this -> createCsr ($ this ->getCsrNames (), $ privateKey , ['digest_alg ' => 'sha256 ' ]);
151180 if ($ csr === false ) {
152- $ message = openssl_error_string ();
153- throw new LibresignException ('OpenSSL error: ' . $ message );
181+ throw $ this ->buildOpenSslException ('Failed to generate OpenSSL CSR ' );
154182 }
155183
156184 $ parsedRoot = openssl_x509_parse ($ rootCertificate );
@@ -173,7 +201,10 @@ public function generateCertificate(): string {
173201 $ serialNumber = (int )$ serialNumberString ;
174202 $ options = $ this ->getLeafCertOptions ();
175203
176- $ x509 = openssl_csr_sign ($ csr , $ rootCertificate , $ rootPrivateKey , $ this ->getLeafExpiryInDays (), $ options , $ serialNumber );
204+ $ x509 = $ this ->signCsr ($ csr , $ rootCertificate , $ rootPrivateKey , $ this ->getLeafExpiryInDays (), $ options , $ serialNumber );
205+ if ($ x509 === false ) {
206+ throw $ this ->buildOpenSslException ('Failed to sign OpenSSL certificate ' );
207+ }
177208
178209 return parent ::exportToPkcs12 (
179210 $ x509 ,
@@ -209,6 +240,54 @@ private function inheritRootSubjectFields(string $rootCertificate): void {
209240 }
210241 }
211242
243+ protected function createPrivateKey (array $ options ): mixed {
244+ return $ this ->runOpenSslOperation (static fn () => openssl_pkey_new ($ options ));
245+ }
246+
247+ protected function createCsr (array $ distinguishedNames , mixed $ privateKey , array $ options ): mixed {
248+ return $ this ->runOpenSslOperation (static fn () => openssl_csr_new ($ distinguishedNames , $ privateKey , $ options ));
249+ }
250+
251+ protected function signCsr (
252+ mixed $ csr ,
253+ mixed $ caCertificate ,
254+ mixed $ privateKey ,
255+ int $ days ,
256+ array $ options ,
257+ int $ serialNumber ,
258+ ): mixed {
259+ return $ this ->runOpenSslOperation (static fn () => openssl_csr_sign ($ csr , $ caCertificate , $ privateKey , $ days , $ options , $ serialNumber ));
260+ }
261+
262+ private function runOpenSslOperation (callable $ operation ): mixed {
263+ $ this ->lastOpenSslErrors = [];
264+
265+ set_error_handler (function (int $ severity , string $ message ): bool {
266+ $ this ->lastOpenSslErrors [] = $ message ;
267+ return true ;
268+ });
269+
270+ try {
271+ return $ operation ();
272+ } finally {
273+ restore_error_handler ();
274+ }
275+ }
276+
277+ private function buildOpenSslException (string $ message ): LibresignException {
278+ $ errors = $ this ->lastOpenSslErrors ;
279+ while (($ error = openssl_error_string ()) !== false ) {
280+ $ errors [] = $ error ;
281+ }
282+ $ this ->lastOpenSslErrors = [];
283+
284+ if (empty ($ errors )) {
285+ return new LibresignException ($ message . ': unknown OpenSSL error ' );
286+ }
287+
288+ return new LibresignException ($ message . ': ' . implode (' | ' , $ errors ));
289+ }
290+
212291 private function generateCaConfig (): string {
213292 $ config = $ this ->buildCaCertificateConfig ();
214293 $ this ->cleanupCaConfig ($ config );
@@ -228,6 +307,12 @@ private function generateLeafConfig(): string {
228307 */
229308 private function buildCaCertificateConfig (): array {
230309 $ config = [
310+ 'req ' => [
311+ 'distinguished_name ' => 'req_distinguished_name ' ,
312+ 'x509_extensions ' => 'v3_ca ' ,
313+ 'prompt ' => 'no ' ,
314+ ],
315+ 'req_distinguished_name ' => [],
231316 'ca ' => [
232317 'default_ca ' => 'CA_default '
233318 ],
@@ -267,6 +352,12 @@ private function buildCaCertificateConfig(): array {
267352
268353 private function buildLeafCertificateConfig (): array {
269354 $ config = [
355+ 'req ' => [
356+ 'distinguished_name ' => 'req_distinguished_name ' ,
357+ 'req_extensions ' => 'v3_req ' ,
358+ 'prompt ' => 'no ' ,
359+ ],
360+ 'req_distinguished_name ' => [],
270361 'v3_req ' => [
271362 'basicConstraints ' => 'CA:FALSE ' ,
272363 'keyUsage ' => 'digitalSignature, keyEncipherment, nonRepudiation ' ,
0 commit comments