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

LibreSign / libresign / 27851215783

19 Jun 2026 10:27PM UTC coverage: 57.37%. First build
27851215783

Pull #7821

github

web-flow
Merge fcefe5def into e7d9ccf99
Pull Request #7821: fix: avoid failing on transient CFSSL startup errors

0 of 9 new or added lines in 1 file covered. (0.0%)

11038 of 19240 relevant lines covered (57.37%)

6.99 hits per line

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

24.32
/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()) {
×
NEW
284
                                return false;
×
285
                        }
286
                        $response = $client
×
287
                                ->request('get',
×
288
                                        'health',
×
289
                                        [
×
290
                                                'base_uri' => $this->getCfsslUri()
×
291
                                        ]
×
292
                                )
×
293
                        ;
×
NEW
294
                } catch (ConnectException) {
×
295
                        // Port not yet accepting connections — server still starting
NEW
296
                        return false;
×
NEW
297
                } catch (RequestException $th) {
×
NEW
298
                        if ($th->getCode() === 404) {
×
NEW
299
                                throw new \Exception('Endpoint /health of CFSSL server not found. Maybe you are using incompatible version of CFSSL server. Use latests version.', 1);
×
300
                        }
NEW
301
                        return false;
×
302
                }
303

304
                $responseDecoded = json_decode((string)$response->getBody(), true);
×
NEW
305
                if (empty($responseDecoded['result']['healthy'])) {
×
306
                        return false;
×
307
                }
308

309
                return (bool)$responseDecoded['result']['healthy'];
×
310
        }
311

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

335
                $pid = (int)($process->getPid() ?? 0);
×
336
                if ($pid > 0) {
×
337
                        $this->processManager->register(self::PROCESS_SOURCE, $pid, [
×
338
                                'uri' => $this->getCfsslUri(),
×
339
                        ]);
×
340
                }
341

342
                $loops = 0;
×
NEW
343
                while (!$this->portOpen() && $loops <= 9) {
×
344
                        sleep(1);
×
345
                        $loops++;
×
346
                }
347
        }
348

349
        private function portOpen(): bool {
350
                $host = parse_url($this->getCfsslUri(), PHP_URL_HOST);
×
351
                $port = parse_url($this->getCfsslUri(), PHP_URL_PORT);
×
352

353
                set_error_handler(fn (int $errno, string $errstr, string $errfile = '', int $errline = 0, array $errcontext = []): bool => true);
×
354
                $socket = fsockopen($host, $port, $errno, $errstr, 0.1);
×
355
                restore_error_handler();
×
356
                if (!$socket || $errno || $errstr) {
×
357
                        return false;
×
358
                }
359
                fclose($socket);
×
360
                return true;
×
361
        }
362

363
        private function getServerPid(): int {
364
                $uri = $this->getCfsslUri();
1✔
365
                return $this->processManager->findRunningPid(
1✔
366
                        self::PROCESS_SOURCE,
1✔
367
                        fn (array $entry): bool => ($entry['context']['uri'] ?? '') === $uri,
1✔
368
                );
1✔
369
        }
370

371
        private function stopIfRunning(): void {
372
                $uri = $this->getCfsslUri();
1✔
373
                $port = (int)(parse_url($uri, PHP_URL_PORT) ?? 0);
1✔
374
                $this->processManager->setSourceHint(self::PROCESS_SOURCE, [
1✔
375
                        'uri' => $uri,
1✔
376
                        'port' => $port,
1✔
377
                ]);
1✔
378

379
                $this->processManager->findRunningPid(
1✔
380
                        self::PROCESS_SOURCE,
1✔
381
                        fn (array $entry): bool => ($entry['context']['uri'] ?? '') === $uri,
1✔
382
                );
1✔
383

384
                foreach ($this->processManager->listRunning(self::PROCESS_SOURCE) as $entry) {
1✔
385
                        if (($entry['context']['uri'] ?? '') !== $uri) {
1✔
386
                                continue;
×
387
                        }
388

389
                        $pid = (int)($entry['pid'] ?? 0);
1✔
390
                        if ($pid <= 0) {
1✔
391
                                continue;
×
392
                        }
393
                        $this->processManager->stopPid($pid, SIGKILL);
1✔
394
                        $this->processManager->unregister(self::PROCESS_SOURCE, $pid);
1✔
395
                }
396
        }
397

398
        private function getBinary(): string {
399
                if ($this->binary) {
×
400
                        return $this->binary;
×
401
                }
402

403
                if (PHP_OS_FAMILY === 'Windows') {
×
404
                        throw new LibresignException('Incompatible with Windows');
×
405
                }
406

407
                if ($this->appConfig->hasKey(Application::APP_ID, 'cfssl_bin')) {
×
408
                        $binary = $this->appConfig->getValueString(Application::APP_ID, 'cfssl_bin');
×
409
                        if (!file_exists($binary)) {
×
410
                                $this->appConfig->deleteKey(Application::APP_ID, 'cfssl_bin');
×
411
                        }
412
                        return $binary;
×
413
                }
414
                throw new LibresignException('Binary of CFSSL not found. Install binaries.');
×
415
        }
416

417
        private function getCfsslUri(): string {
418
                if ($this->cfsslUri) {
2✔
419
                        return $this->cfsslUri;
×
420
                }
421

422
                if ($uri = $this->appConfig->getValueString(Application::APP_ID, 'cfssl_uri')) {
2✔
423
                        return $uri;
×
424
                }
425
                $this->appConfig->deleteKey(Application::APP_ID, 'cfssl_uri');
2✔
426

427
                $this->cfsslUri = self::CFSSL_URI;
2✔
428
                return $this->cfsslUri;
2✔
429
        }
430

431
        public function setCfsslUri($uri): void {
432
                if ($uri) {
×
433
                        $this->appConfig->setValueString(Application::APP_ID, 'cfssl_uri', $uri);
×
434
                } else {
435
                        $this->appConfig->deleteKey(Application::APP_ID, 'cfssl_uri');
×
436
                }
437
                $this->cfsslUri = $uri;
×
438
        }
439

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

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

518
        /**
519
         * @param string[] $command
520
         */
521
        protected function createProcess(array $command): Process {
522
                return new Process($command);
×
523
        }
524

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

537
                $parsed = openssl_x509_parse($cert);
×
538
                if (!$parsed || !isset($parsed['extensions']['authorityKeyIdentifier'])) {
×
539
                        throw new \RuntimeException('Certificate does not contain Authority Key Identifier');
×
540
                }
541

542
                $authKeyId = $parsed['extensions']['authorityKeyIdentifier'];
×
543

544
                if (preg_match('/keyid:([A-Fa-f0-9:]+)/', $authKeyId, $matches)) {
×
545
                        return strtolower(str_replace(':', '', $matches[1]));
×
546
                }
547

548
                throw new \RuntimeException('Could not parse Authority Key Identifier');
×
549
        }
550

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

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

570
                        $responseData = json_decode((string)$response->getBody(), true);
×
571

572
                        if (!isset($responseData['success'])) {
×
573
                                $errorMessage = isset($responseData['errors'])
×
574
                                        ? implode(', ', array_column($responseData['errors'], 'message'))
×
575
                                        : 'Unknown CFSSL error';
×
576
                                throw new \RuntimeException('CFSSL revocation failed: ' . $errorMessage);
×
577
                        }
578

579
                        return $responseData['success'];
×
580
                } catch (RequestException|ConnectException $e) {
×
581
                        throw new \RuntimeException('Failed to communicate with CFSSL server: ' . $e->getMessage());
×
582
                } catch (\Throwable $e) {
×
583
                        throw new \RuntimeException('CFSSL certificate revocation error: ' . $e->getMessage());
×
584
                }
585
        }
586

587
        private function persistSerialNumberToCrl(array $parsed): void {
588
                if (!isset($parsed['serialNumberHex']) || !isset($parsed['valid_to'])) {
×
589
                        return;
×
590
                }
591

592
                $serialNumber = $parsed['serialNumberHex'];
×
593

594
                $owner = $this->getCommonName() ?? 'Unknown';
×
595

596
                $expiresAt = null;
×
597
                if (isset($parsed['validTo_time_t'])) {
×
598
                        $expiresAt = new \DateTime('@' . $parsed['validTo_time_t']);
×
599
                }
600

601
                $issuer = $parsed['issuer'] ?? [];
×
602
                $subject = $parsed['subject'] ?? [];
×
603

604
                $this->crlMapper->createCertificate(
×
605
                        $serialNumber,
×
606
                        $owner,
×
607
                        'cfssl',
×
608
                        $this->caIdentifierService->getInstanceId(),
×
609
                        $this->caIdentifierService->getCaIdParsed()['generation'],
×
610
                        new \DateTime(),
×
611
                        $expiresAt,
×
612
                        $issuer,
×
613
                        $subject,
×
614
                        CertificateType::LEAF->value,
×
615
                );
×
616
        }
617

618
        private function persistRootCertificateFromData(string $certPem): void {
619
                $x509Resource = openssl_x509_read($certPem);
×
620
                if (!$x509Resource) {
×
621
                        throw new \RuntimeException('Failed to parse root certificate');
×
622
                }
623

624
                $parsed = openssl_x509_parse($x509Resource);
×
625
                if (!$parsed) {
×
626
                        throw new \RuntimeException('Failed to extract root certificate information');
×
627
                }
628

629
                $serialNumber = $parsed['serialNumberHex'] ?? '';
×
630
                if (empty($serialNumber)) {
×
631
                        throw new \RuntimeException('Root certificate has no serial number');
×
632
                }
633

634
                $owner = $this->getCommonName() ?? 'Root CA';
×
635

636
                $expiresAt = null;
×
637
                if (isset($parsed['validTo_time_t'])) {
×
638
                        $expiresAt = new \DateTime('@' . $parsed['validTo_time_t']);
×
639
                }
640

641
                /** @var array<string, mixed> $issuer */
642
                $issuer = $parsed['issuer'] ?? [];
×
643
                /** @var array<string, mixed> $subject */
644
                $subject = $parsed['subject'] ?? [];
×
645

646
                $this->crlMapper->createCertificate(
×
647
                        $serialNumber,
×
648
                        $owner,
×
649
                        'cfssl',
×
650
                        $this->caIdentifierService->getInstanceId(),
×
651
                        $this->caIdentifierService->getCaIdParsed()['generation'],
×
652
                        new \DateTime(),
×
653
                        $expiresAt,
×
654
                        $issuer,
×
655
                        $subject,
×
656
                        CertificateType::ROOT->value,
×
657
                );
×
658
        }
659
}
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