• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LibreSign / libresign / 27467144385

13 Jun 2026 12:45PM UTC coverage: 56.926%. First build
27467144385

Pull #7740

github

web-flow
Merge ed57780d5 into ae3c9e54d
Pull Request #7740: chore: migrate to PHP 8.3

56 of 75 new or added lines in 20 files covered. (74.67%)

10779 of 18935 relevant lines covered (56.93%)

7.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

24.13
/lib/Handler/CertificateEngine/CfsslHandler.php
1
<?php
2

3
declare(strict_types=1);
4
/**
5
 * SPDX-FileCopyrightText: 2020-2024 LibreCode coop and contributors
6
 * SPDX-License-Identifier: AGPL-3.0-or-later
7
 */
8

9
namespace OCA\Libresign\Handler\CertificateEngine;
10

11
use GuzzleHttp\Client;
12
use GuzzleHttp\Exception\ConnectException;
13
use GuzzleHttp\Exception\RequestException;
14
use OCA\Libresign\AppInfo\Application;
15
use OCA\Libresign\Db\CrlMapper;
16
use OCA\Libresign\Enum\CertificateType;
17
use OCA\Libresign\Exception\EmptyCertificateException;
18
use OCA\Libresign\Exception\LibresignException;
19
use OCA\Libresign\Handler\CfsslServerHandler;
20
use OCA\Libresign\Helper\ConfigureCheckHelper;
21
use OCA\Libresign\Service\CaIdentifierService;
22
use OCA\Libresign\Service\CertificatePolicyService;
23
use OCA\Libresign\Service\Crl\CrlRevocationChecker;
24
use OCA\Libresign\Service\Install\InstallService;
25
use OCA\Libresign\Service\Process\ProcessManager;
26
use OCA\Libresign\Vendor\Symfony\Component\Process\Process;
27
use OCP\Files\AppData\IAppDataFactory;
28
use OCP\IAppConfig;
29
use OCP\IConfig;
30
use OCP\IDateTimeFormatter;
31
use OCP\ITempManager;
32
use OCP\IURLGenerator;
33
use Psr\Log\LoggerInterface;
34

35
/**
36
 * Class CfsslHandler
37
 *
38
 * @package OCA\Libresign\Handler
39
 *
40
 * @method CfsslHandler setClient(Client $client)
41
 */
42
class CfsslHandler extends AEngineHandler implements IEngineHandler {
43
        public const CFSSL_URI = 'http://127.0.0.1:8888/api/v1/cfssl/';
44
        private const string PROCESS_SOURCE = 'cfssl';
45

46
        /** @var Client */
47
        protected $client;
48
        protected $cfsslUri;
49
        private string $binary = '';
50

51
        public function __construct(
52
                protected IConfig $config,
53
                protected IAppConfig $appConfig,
54
                protected IAppDataFactory $appDataFactory,
55
                protected IDateTimeFormatter $dateTimeFormatter,
56
                protected ITempManager $tempManager,
57
                protected CfsslServerHandler $cfsslServerHandler,
58
                protected CertificatePolicyService $certificatePolicyService,
59
                protected IURLGenerator $urlGenerator,
60
                protected CaIdentifierService $caIdentifierService,
61
                protected CrlMapper $crlMapper,
62
                protected LoggerInterface $logger,
63
                CrlRevocationChecker $crlRevocationChecker,
64
                private ProcessManager $processManager,
65
        ) {
66
                parent::__construct(
79✔
67
                        $config,
79✔
68
                        $appConfig,
79✔
69
                        $appDataFactory,
79✔
70
                        $dateTimeFormatter,
79✔
71
                        $tempManager,
79✔
72
                        $certificatePolicyService,
79✔
73
                        $urlGenerator,
79✔
74
                        $caIdentifierService,
79✔
75
                        $logger,
79✔
76
                        $crlRevocationChecker,
79✔
77
                );
79✔
78

79
                $this->cfsslServerHandler->configCallback(fn () => $this->getCurrentConfigPath());
79✔
80
        }
81

82
        #[\Override]
83
        public function generateRootCert(
84
                string $commonName,
85
                array $names = [],
86
        ): void {
87
                if (empty($commonName)) {
×
88
                        throw new EmptyCertificateException('Common Name (CN) cannot be empty for root certificate');
×
89
                }
90

91
                $this->cfsslServerHandler->createConfigServer(
×
92
                        $commonName,
×
93
                        $names,
×
94
                        $this->getCaExpiryInDays(),
×
95
                        $this->getCrlDistributionUrl(),
×
96
                );
×
97

98
                $this->gencert();
×
99

100
                $this->stopIfRunning();
×
101

102
                for ($i = 1; $i <= 4; $i++) {
×
103
                        if ($this->isUp()) {
×
104
                                break;
×
105
                        }
106
                        sleep(2);
×
107
                }
108
        }
109

110
        #[\Override]
111
        public function generateCertificate(): string {
112
                $this->validateRootCertificate();
×
113

114
                $certKeys = $this->newCert();
×
115
                $pkcs12 = parent::exportToPkcs12(
×
116
                        $certKeys['certificate'],
×
117
                        $certKeys['private_key'],
×
118
                        [
×
119
                                'friendly_name' => $this->getFriendlyName(),
×
120
                                'extracerts' => [
×
121
                                        $certKeys['certificate'],
×
122
                                        $certKeys['certificate_request'],
×
123
                                ],
×
124
                        ],
×
125
                );
×
126

127
                $parsed = $this->readCertificate($pkcs12, $this->getPassword());
×
128
                $this->persistSerialNumberToCrl($parsed);
×
129

130
                return $pkcs12;
×
131
        }
132

133
        #[\Override]
134
        public function isSetupOk(): bool {
135
                $configPath = $this->getCurrentConfigPath();
×
136
                $certificate = file_exists($configPath . DIRECTORY_SEPARATOR . 'ca.pem');
×
137
                $privateKey = file_exists($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem');
×
138
                $csrServer = file_exists($configPath . DIRECTORY_SEPARATOR . 'csr_server.json');
×
139
                $configServer = file_exists($configPath . DIRECTORY_SEPARATOR . 'config_server.json');
×
140
                if (!$certificate || !$privateKey || !$csrServer || !$configServer) {
×
141
                        return false;
×
142
                }
143
                try {
144
                        $this->getClient();
×
145
                        return true;
×
146
                } catch (\Throwable) {
×
147
                }
148
                return false;
×
149
        }
150

151
        #[\Override]
152
        protected function getConfigureCheckResourceName(): string {
153
                return 'cfssl-configure';
×
154
        }
155

156
        #[\Override]
157
        protected function getCertificateRegenerationTip(): string {
158
                return 'Consider regenerating the root certificate with: occ libresign:configure:cfssl --cn="Your CA Name"';
×
159
        }
160

161
        #[\Override]
162
        protected function getEngineSpecificChecks(): array {
163
                return $this->checkBinaries();
×
164
        }
165

166
        #[\Override]
167
        protected function getSetupSuccessMessage(): string {
168
                return 'Root certificate config files found.';
×
169
        }
170

171
        #[\Override]
172
        protected function getSetupErrorMessage(): string {
173
                return 'CFSSL (root certificate) not configured.';
×
174
        }
175

176
        #[\Override]
177
        protected function getSetupErrorTip(): string {
178
                return 'Run occ libresign:configure:cfssl --help';
×
179
        }
180

181
        #[\Override]
182
        public function toArray(): array {
183
                $return = parent::toArray();
×
184
                if (!empty($return['configPath'])) {
×
185
                        $return['cfsslUri'] = $this->appConfig->getValueString(Application::APP_ID, 'cfssl_uri');
×
186
                }
187
                return $return;
×
188
        }
189

190
        public function getCommonName(): string {
191
                $uid = $this->getUID();
×
192
                if (!$uid) {
×
193
                        return $this->commonName;
×
194
                }
195
                return $uid . ', ' . $this->commonName;
×
196
        }
197

198
        private function newCert(): array {
199
                $json = [
×
200
                        'json' => [
×
201
                                'profile' => 'client',
×
202
                                'request' => [
×
203
                                        'hosts' => $this->getHosts(),
×
204
                                        'CN' => $this->getCommonName(),
×
205
                                        'key' => [
×
206
                                                'algo' => 'rsa',
×
207
                                                'size' => 2048,
×
208
                                        ],
×
209
                                        'names' => [],
×
210
                                        'crl_url' => $this->getCrlDistributionUrl(),
×
211
                                ],
×
212
                        ],
×
213
                ];
×
214

215
                $names = $this->getNames();
×
216
                foreach ($names as $key => $value) {
×
217
                        if (!empty($value) && is_array($value)) {
×
218
                                $names[$key] = implode(', ', $value);
×
219
                        }
220
                }
221
                if (!empty($names)) {
×
222
                        $json['json']['request']['names'][] = $names;
×
223
                }
224

225
                try {
226
                        $response = $this->getClient()
×
227
                                ->request('post',
×
228
                                        'newcert',
×
229
                                        $json
×
230
                                )
×
231
                        ;
×
232
                } catch (RequestException|ConnectException $th) {
×
233
                        if ($th->getHandlerContext() && $th->getHandlerContext()['error']) {
×
234
                                throw new \Exception($th->getHandlerContext()['error'], 1);
×
235
                        }
236
                        throw new LibresignException($th->getMessage(), 500);
×
237
                }
238

239
                $responseDecoded = json_decode((string)$response->getBody(), true);
×
240
                if (!isset($responseDecoded['success']) || !$responseDecoded['success']) {
×
241
                        throw new LibresignException('Error while generating certificate keys!', 500);
×
242
                }
243

244
                return $responseDecoded['result'];
×
245
        }
246

247
        private function gencert(): void {
248
                $binary = $this->getBinary();
×
249
                $configPath = $this->getCurrentConfigPath();
×
250
                $csrFile = $configPath . '/csr_server.json';
×
251

252
                $process = $this->createProcess([$binary, 'gencert', '-initca', $csrFile]);
×
253
                $process->run();
×
254
                $output = $process->getOutput();
×
255

256
                if (!$process->isSuccessful() || $output === '') {
×
257
                        throw new \RuntimeException('cfssl without output.');
×
258
                }
259

260
                $json = json_decode($output, true);
×
261
                if (!$json || !isset($json['cert'], $json['key'], $json['csr'])) {
×
262
                        throw new \RuntimeException('Error generating CA: invalid cfssl output.');
×
263
                }
264

265
                file_put_contents($configPath . '/ca.pem', $json['cert']);
×
266
                file_put_contents($configPath . '/ca-key.pem', $json['key']);
×
267
                file_put_contents($configPath . '/ca.csr', $json['csr']);
×
268

269
                $this->persistRootCertificateFromData($json['cert']);
×
270
        }
271

272
        private function getClient(): Client {
273
                if (!$this->client) {
×
274
                        $this->setClient(new Client(['base_uri' => $this->getCfsslUri()]));
×
275
                }
276
                $this->wakeUp();
×
277
                return $this->client;
×
278
        }
279

280
        private function isUp(): bool {
281
                try {
282
                        $client = $this->getClient();
×
283
                        if (!$this->portOpen()) {
×
284
                                throw new LibresignException('CFSSL server is down', 500);
×
285
                        }
286
                        $response = $client
×
287
                                ->request('get',
×
288
                                        'health',
×
289
                                        [
×
290
                                                'base_uri' => $this->getCfsslUri()
×
291
                                        ]
×
292
                                )
×
293
                        ;
×
294
                } catch (RequestException|ConnectException $th) {
×
295
                        switch ($th->getCode()) {
×
296
                                case 404:
×
297
                                        throw new \Exception('Endpoint /health of CFSSL server not found. Maybe you are using incompatible version of CFSSL server. Use latests version.', 1);
×
298
                                default:
299
                                        if ($th->getHandlerContext() && $th->getHandlerContext()['error']) {
×
300
                                                throw new \Exception($th->getHandlerContext()['error'], 1);
×
301
                                        }
302
                                        throw new LibresignException($th->getMessage(), 500);
×
303
                        }
304
                }
305

306
                $responseDecoded = json_decode((string)$response->getBody(), true);
×
307
                if (!isset($responseDecoded['success']) || !$responseDecoded['success']) {
×
308
                        throw new LibresignException('Error while check cfssl API health!', 500);
×
309
                }
310

311
                if (empty($responseDecoded['result']) || empty($responseDecoded['result']['healthy'])) {
×
312
                        return false;
×
313
                }
314

315
                return (bool)$responseDecoded['result']['healthy'];
×
316
        }
317

318
        private function wakeUp(): void {
319
                if ($this->portOpen()) {
×
320
                        return;
×
321
                }
322
                $binary = $this->getBinary();
×
323
                $configPath = $this->getCurrentConfigPath();
×
324
                if (!$configPath) {
×
325
                        throw new LibresignException('CFSSL not configured.');
×
326
                }
327
                $this->cfsslServerHandler->updateExpirity($this->getCaExpiryInDays());
×
328
                $process = $this->createProcess([
×
329
                        $binary,
×
330
                        'serve',
×
331
                        '-address=127.0.0.1',
×
332
                        '-ca-key', $configPath . DIRECTORY_SEPARATOR . 'ca-key.pem',
×
333
                        '-ca', $configPath . DIRECTORY_SEPARATOR . 'ca.pem',
×
334
                        '-config', $configPath . DIRECTORY_SEPARATOR . 'config_server.json',
×
335
                ]);
×
336
                $process->setOptions(['create_new_console' => true]);
×
337
                $process->setTimeout(null);
×
338
                $process->disableOutput();
×
339
                $process->start();
×
340

341
                $pid = (int)($process->getPid() ?? 0);
×
342
                if ($pid > 0) {
×
343
                        $this->processManager->register(self::PROCESS_SOURCE, $pid, [
×
344
                                'uri' => $this->getCfsslUri(),
×
345
                        ]);
×
346
                }
347

348
                $loops = 0;
×
349
                while (!$this->portOpen() && $loops <= 4) {
×
350
                        sleep(1);
×
351
                        $loops++;
×
352
                }
353
        }
354

355
        private function portOpen(): bool {
356
                $host = parse_url($this->getCfsslUri(), PHP_URL_HOST);
×
357
                $port = parse_url($this->getCfsslUri(), PHP_URL_PORT);
×
358

NEW
359
                set_error_handler(fn (int $errno, string $errstr, string $errfile = '', int $errline = 0, array $errcontext = []): bool => true);
×
360
                $socket = fsockopen($host, $port, $errno, $errstr, 0.1);
×
361
                restore_error_handler();
×
362
                if (!$socket || $errno || $errstr) {
×
363
                        return false;
×
364
                }
365
                fclose($socket);
×
366
                return true;
×
367
        }
368

369
        private function getServerPid(): int {
370
                $uri = $this->getCfsslUri();
1✔
371
                return $this->processManager->findRunningPid(
1✔
372
                        self::PROCESS_SOURCE,
1✔
373
                        fn (array $entry): bool => ($entry['context']['uri'] ?? '') === $uri,
1✔
374
                );
1✔
375
        }
376

377
        private function stopIfRunning(): void {
378
                $uri = $this->getCfsslUri();
1✔
379
                $port = (int)(parse_url($uri, PHP_URL_PORT) ?? 0);
1✔
380
                $this->processManager->setSourceHint(self::PROCESS_SOURCE, [
1✔
381
                        'uri' => $uri,
1✔
382
                        'port' => $port,
1✔
383
                ]);
1✔
384

385
                $this->processManager->findRunningPid(
1✔
386
                        self::PROCESS_SOURCE,
1✔
387
                        fn (array $entry): bool => ($entry['context']['uri'] ?? '') === $uri,
1✔
388
                );
1✔
389

390
                foreach ($this->processManager->listRunning(self::PROCESS_SOURCE) as $entry) {
1✔
391
                        if (($entry['context']['uri'] ?? '') !== $uri) {
1✔
392
                                continue;
×
393
                        }
394

395
                        $pid = (int)($entry['pid'] ?? 0);
1✔
396
                        if ($pid <= 0) {
1✔
397
                                continue;
×
398
                        }
399
                        $this->processManager->stopPid($pid, SIGKILL);
1✔
400
                        $this->processManager->unregister(self::PROCESS_SOURCE, $pid);
1✔
401
                }
402
        }
403

404
        private function getBinary(): string {
405
                if ($this->binary) {
×
406
                        return $this->binary;
×
407
                }
408

409
                if (PHP_OS_FAMILY === 'Windows') {
×
410
                        throw new LibresignException('Incompatible with Windows');
×
411
                }
412

413
                if ($this->appConfig->hasKey(Application::APP_ID, 'cfssl_bin')) {
×
414
                        $binary = $this->appConfig->getValueString(Application::APP_ID, 'cfssl_bin');
×
415
                        if (!file_exists($binary)) {
×
416
                                $this->appConfig->deleteKey(Application::APP_ID, 'cfssl_bin');
×
417
                        }
418
                        return $binary;
×
419
                }
420
                throw new LibresignException('Binary of CFSSL not found. Install binaries.');
×
421
        }
422

423
        private function getCfsslUri(): string {
424
                if ($this->cfsslUri) {
2✔
425
                        return $this->cfsslUri;
×
426
                }
427

428
                if ($uri = $this->appConfig->getValueString(Application::APP_ID, 'cfssl_uri')) {
2✔
429
                        return $uri;
×
430
                }
431
                $this->appConfig->deleteKey(Application::APP_ID, 'cfssl_uri');
2✔
432

433
                $this->cfsslUri = self::CFSSL_URI;
2✔
434
                return $this->cfsslUri;
2✔
435
        }
436

437
        public function setCfsslUri($uri): void {
438
                if ($uri) {
×
439
                        $this->appConfig->setValueString(Application::APP_ID, 'cfssl_uri', $uri);
×
440
                } else {
441
                        $this->appConfig->deleteKey(Application::APP_ID, 'cfssl_uri');
×
442
                }
443
                $this->cfsslUri = $uri;
×
444
        }
445

446
        private function checkBinaries(): array {
447
                if (PHP_OS_FAMILY === 'Windows') {
4✔
448
                        return [
×
449
                                (new ConfigureCheckHelper())
×
450
                                        ->setErrorMessage('CFSSL is incompatible with Windows')
×
451
                                        ->setResource('cfssl'),
×
452
                        ];
×
453
                }
454
                $binary = $this->appConfig->getValueString(Application::APP_ID, 'cfssl_bin');
4✔
455
                if (!$binary) {
4✔
456
                        return [
×
457
                                (new ConfigureCheckHelper())
×
458
                                        ->setErrorMessage('CFSSL not installed.')
×
459
                                        ->setResource('cfssl')
×
460
                                        ->setTip('Run occ libresign:install --cfssl'),
×
461
                        ];
×
462
                }
463

464
                if (!file_exists($binary)) {
4✔
465
                        return [
×
466
                                (new ConfigureCheckHelper())
×
467
                                        ->setErrorMessage('CFSSL not found.')
×
468
                                        ->setResource('cfssl')
×
469
                                        ->setTip('Run occ libresign:install --cfssl'),
×
470
                        ];
×
471
                }
472
                $process = $this->createProcess([$binary, 'version']);
4✔
473
                $process->run();
4✔
474
                $version = $process->getOutput();
4✔
475
                if (!$process->isSuccessful() || empty($version)) {
4✔
476
                        return [
1✔
477
                                (new ConfigureCheckHelper())
1✔
478
                                        ->setErrorMessage(sprintf(
1✔
479
                                                'Failed to run the command "%s" with user %s',
1✔
480
                                                "$binary version",
1✔
481
                                                get_current_user()
1✔
482
                                        ))
1✔
483
                                        ->setResource('cfssl')
1✔
484
                                        ->setTip('Run occ libresign:install --cfssl')
1✔
485
                        ];
1✔
486
                }
487
                preg_match_all('/: (?<version>.*)/', $version, $matches);
3✔
488
                if (!$matches || !isset($matches['version']) || count($matches['version']) !== 2) {
3✔
489
                        return [
1✔
490
                                (new ConfigureCheckHelper())
1✔
491
                                        ->setErrorMessage(sprintf(
1✔
492
                                                'Failed to identify cfssl version with command %s',
1✔
493
                                                "$binary version"
1✔
494
                                        ))
1✔
495
                                        ->setResource('cfssl')
1✔
496
                                        ->setTip('Run occ libresign:install --cfssl')
1✔
497
                        ];
1✔
498
                }
499
                if (!str_contains($matches['version'][0], InstallService::CFSSL_VERSION)) {
2✔
500
                        return [
1✔
501
                                (new ConfigureCheckHelper())
1✔
502
                                        ->setErrorMessage(sprintf(
1✔
503
                                                'Invalid version. Expected: %s, actual: %s',
1✔
504
                                                InstallService::CFSSL_VERSION,
1✔
505
                                                $matches['version'][0]
1✔
506
                                        ))
1✔
507
                                        ->setResource('cfssl')
1✔
508
                                        ->setTip('Run occ libresign:install --cfssl')
1✔
509
                        ];
1✔
510
                }
511
                $return = [];
1✔
512
                $return[] = (new ConfigureCheckHelper())
1✔
513
                        ->setSuccessMessage('CFSSL binary path: ' . $binary)
1✔
514
                        ->setResource('cfssl');
1✔
515
                $return[] = (new ConfigureCheckHelper())
1✔
516
                        ->setSuccessMessage('CFSSL version: ' . $matches['version'][0])
1✔
517
                        ->setResource('cfssl');
1✔
518
                $return[] = (new ConfigureCheckHelper())
1✔
519
                        ->setSuccessMessage('Runtime: ' . $matches['version'][1])
1✔
520
                        ->setResource('cfssl');
1✔
521
                return $return;
1✔
522
        }
523

524
        /**
525
         * @param string[] $command
526
         */
527
        protected function createProcess(array $command): Process {
528
                return new Process($command);
×
529
        }
530

531
        /**
532
         * Get Authority Key Identifier from certificate (needed for CFSSL revocation)
533
         *
534
         * @param string $certificatePem PEM encoded certificate
535
         * @return string Authority Key Identifier in lowercase without colons
536
         */
537
        public function getAuthorityKeyId(string $certificatePem): string {
538
                $cert = openssl_x509_read($certificatePem);
×
539
                if (!$cert) {
×
540
                        throw new \RuntimeException('Invalid certificate format');
×
541
                }
542

543
                $parsed = openssl_x509_parse($cert);
×
544
                if (!$parsed || !isset($parsed['extensions']['authorityKeyIdentifier'])) {
×
545
                        throw new \RuntimeException('Certificate does not contain Authority Key Identifier');
×
546
                }
547

548
                $authKeyId = $parsed['extensions']['authorityKeyIdentifier'];
×
549

550
                if (preg_match('/keyid:([A-Fa-f0-9:]+)/', $authKeyId, $matches)) {
×
551
                        return strtolower(str_replace(':', '', $matches[1]));
×
552
                }
553

554
                throw new \RuntimeException('Could not parse Authority Key Identifier');
×
555
        }
556

557
        /**
558
         * Revoke a certificate using CFSSL API
559
         *
560
         * @param string $serialNumber Certificate serial number in decimal format
561
         * @param string $authorityKeyId Authority key identifier (lowercase, no colons)
562
         * @param string $reason CRLReason description string (e.g., 'superseded', 'keyCompromise')
563
         */
564
        public function revokeCertificate(string $serialNumber, string $authorityKeyId, string $reason): bool {
565
                try {
566
                        $json = [
×
567
                                'json' => [
×
568
                                        'serial' => $serialNumber,
×
569
                                        'authority_key_id' => $authorityKeyId,
×
570
                                        'reason' => $reason,
×
571
                                ],
×
572
                        ];
×
573

574
                        $response = $this->getClient()->request('POST', 'revoke', $json);
×
575

576
                        $responseData = json_decode((string)$response->getBody(), true);
×
577

578
                        if (!isset($responseData['success'])) {
×
579
                                $errorMessage = isset($responseData['errors'])
×
580
                                        ? implode(', ', array_column($responseData['errors'], 'message'))
×
581
                                        : 'Unknown CFSSL error';
×
582
                                throw new \RuntimeException('CFSSL revocation failed: ' . $errorMessage);
×
583
                        }
584

585
                        return $responseData['success'];
×
586
                } catch (RequestException|ConnectException $e) {
×
587
                        throw new \RuntimeException('Failed to communicate with CFSSL server: ' . $e->getMessage());
×
588
                } catch (\Throwable $e) {
×
589
                        throw new \RuntimeException('CFSSL certificate revocation error: ' . $e->getMessage());
×
590
                }
591
        }
592

593
        private function persistSerialNumberToCrl(array $parsed): void {
594
                if (!isset($parsed['serialNumberHex']) || !isset($parsed['valid_to'])) {
×
595
                        return;
×
596
                }
597

598
                $serialNumber = $parsed['serialNumberHex'];
×
599

600
                $owner = $this->getCommonName() ?? 'Unknown';
×
601

602
                $expiresAt = null;
×
603
                if (isset($parsed['validTo_time_t'])) {
×
604
                        $expiresAt = new \DateTime('@' . $parsed['validTo_time_t']);
×
605
                }
606

607
                $issuer = $parsed['issuer'] ?? [];
×
608
                $subject = $parsed['subject'] ?? [];
×
609

610
                $this->crlMapper->createCertificate(
×
611
                        $serialNumber,
×
612
                        $owner,
×
613
                        'cfssl',
×
614
                        $this->caIdentifierService->getInstanceId(),
×
615
                        $this->caIdentifierService->getCaIdParsed()['generation'],
×
616
                        new \DateTime(),
×
617
                        $expiresAt,
×
618
                        $issuer,
×
619
                        $subject,
×
620
                        CertificateType::LEAF->value,
×
621
                );
×
622
        }
623

624
        private function persistRootCertificateFromData(string $certPem): void {
625
                $x509Resource = openssl_x509_read($certPem);
×
626
                if (!$x509Resource) {
×
627
                        throw new \RuntimeException('Failed to parse root certificate');
×
628
                }
629

630
                $parsed = openssl_x509_parse($x509Resource);
×
631
                if (!$parsed) {
×
632
                        throw new \RuntimeException('Failed to extract root certificate information');
×
633
                }
634

635
                $serialNumber = $parsed['serialNumberHex'] ?? '';
×
636
                if (empty($serialNumber)) {
×
637
                        throw new \RuntimeException('Root certificate has no serial number');
×
638
                }
639

640
                $owner = $this->getCommonName() ?? 'Root CA';
×
641

642
                $expiresAt = null;
×
643
                if (isset($parsed['validTo_time_t'])) {
×
644
                        $expiresAt = new \DateTime('@' . $parsed['validTo_time_t']);
×
645
                }
646

647
                /** @var array<string, mixed> $issuer */
648
                $issuer = $parsed['issuer'] ?? [];
×
649
                /** @var array<string, mixed> $subject */
650
                $subject = $parsed['subject'] ?? [];
×
651

652
                $this->crlMapper->createCertificate(
×
653
                        $serialNumber,
×
654
                        $owner,
×
655
                        'cfssl',
×
656
                        $this->caIdentifierService->getInstanceId(),
×
657
                        $this->caIdentifierService->getCaIdParsed()['generation'],
×
658
                        new \DateTime(),
×
659
                        $expiresAt,
×
660
                        $issuer,
×
661
                        $subject,
×
662
                        CertificateType::ROOT->value,
×
663
                );
×
664
        }
665
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc