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

LibreSign / libresign / 20069555189

09 Dec 2025 03:48PM UTC coverage: 44.185%. First build
20069555189

Pull #6053

github

web-flow
Merge ab271dfb1 into 797d15aa8
Pull Request #6053: test: add integration tests for certificate engine switching

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

5699 of 12898 relevant lines covered (44.19%)

5.11 hits per line

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

3.44
/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 OC\SystemConfig;
15
use OCA\Libresign\AppInfo\Application;
16
use OCA\Libresign\Db\CrlMapper;
17
use OCA\Libresign\Enum\CertificateType;
18
use OCA\Libresign\Exception\EmptyCertificateException;
19
use OCA\Libresign\Exception\LibresignException;
20
use OCA\Libresign\Handler\CfsslServerHandler;
21
use OCA\Libresign\Helper\ConfigureCheckHelper;
22
use OCA\Libresign\Service\CaIdentifierService;
23
use OCA\Libresign\Service\CertificatePolicyService;
24
use OCA\Libresign\Service\Install\InstallService;
25
use OCP\Files\AppData\IAppDataFactory;
26
use OCP\IAppConfig;
27
use OCP\IConfig;
28
use OCP\IDateTimeFormatter;
29
use OCP\ITempManager;
30
use OCP\IURLGenerator;
31
use Psr\Log\LoggerInterface;
32

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

43
        /** @var Client */
44
        protected $client;
45
        protected $cfsslUri;
46
        private string $binary = '';
47

48
        public function __construct(
49
                protected IConfig $config,
50
                protected IAppConfig $appConfig,
51
                private SystemConfig $systemConfig,
52
                protected IAppDataFactory $appDataFactory,
53
                protected IDateTimeFormatter $dateTimeFormatter,
54
                protected ITempManager $tempManager,
55
                protected CfsslServerHandler $cfsslServerHandler,
56
                protected CertificatePolicyService $certificatePolicyService,
57
                protected IURLGenerator $urlGenerator,
58
                protected CaIdentifierService $caIdentifierService,
59
                protected CrlMapper $crlMapper,
60
                protected LoggerInterface $logger,
61
        ) {
62
                parent::__construct(
59✔
63
                        $config,
59✔
64
                        $appConfig,
59✔
65
                        $appDataFactory,
59✔
66
                        $dateTimeFormatter,
59✔
67
                        $tempManager,
59✔
68
                        $certificatePolicyService,
59✔
69
                        $urlGenerator,
59✔
70
                        $caIdentifierService,
59✔
71
                        $logger,
59✔
72
                );
59✔
73

74
                $this->cfsslServerHandler->configCallback(fn () => $this->getCurrentConfigPath());
59✔
75
        }
76

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

86
                $this->cfsslServerHandler->createConfigServer(
×
87
                        $commonName,
×
88
                        $names,
×
89
                        $this->getCaExpiryInDays(),
×
90
                        $this->getCrlDistributionUrl(),
×
91
                );
×
92

93
                $this->gencert();
×
94

95
                $this->stopIfRunning();
×
96

97
                for ($i = 1; $i <= 4; $i++) {
×
98
                        if ($this->isUp()) {
×
99
                                break;
×
100
                        }
101
                        sleep(2);
×
102
                }
103
        }
104

105
        #[\Override]
106
        public function generateCertificate(): string {
107
                $this->validateRootCertificate();
×
108

109
                $certKeys = $this->newCert();
×
110
                $pkcs12 = parent::exportToPkcs12(
×
111
                        $certKeys['certificate'],
×
112
                        $certKeys['private_key'],
×
113
                        [
×
114
                                'friendly_name' => $this->getFriendlyName(),
×
115
                                'extracerts' => [
×
116
                                        $certKeys['certificate'],
×
117
                                        $certKeys['certificate_request'],
×
118
                                ],
×
119
                        ],
×
120
                );
×
121

122
                $parsed = $this->readCertificate($pkcs12, $this->getPassword());
×
123
                $this->persistSerialNumberToCrl($parsed);
×
124

125
                return $pkcs12;
×
126
        }
127

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

146
        #[\Override]
147
        protected function getConfigureCheckResourceName(): string {
148
                return 'cfssl-configure';
×
149
        }
150

151
        #[\Override]
152
        protected function getCertificateRegenerationTip(): string {
153
                return 'Consider regenerating the root certificate with: occ libresign:configure:cfssl --cn="Your CA Name"';
×
154
        }
155

156
        #[\Override]
157
        protected function getEngineSpecificChecks(): array {
158
                return $this->checkBinaries();
×
159
        }
160

161
        #[\Override]
162
        protected function getSetupSuccessMessage(): string {
163
                return 'Root certificate config files found.';
×
164
        }
165

166
        #[\Override]
167
        protected function getSetupErrorMessage(): string {
168
                return 'CFSSL (root certificate) not configured.';
×
169
        }
170

171
        #[\Override]
172
        protected function getSetupErrorTip(): string {
173
                return 'Run occ libresign:configure:cfssl --help';
×
174
        }
175

176
        #[\Override]
177
        public function toArray(): array {
178
                $return = parent::toArray();
×
179
                if (!empty($return['configPath'])) {
×
180
                        $return['cfsslUri'] = $this->appConfig->getValueString(Application::APP_ID, 'cfssl_uri');
×
181
                }
182
                return $return;
×
183
        }
184

185
        public function getCommonName(): string {
186
                $uid = $this->getUID();
×
187
                if (!$uid) {
×
188
                        return $this->commonName;
×
189
                }
190
                return $uid . ', ' . $this->commonName;
×
191
        }
192

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

210
                $names = $this->getNames();
×
211
                foreach ($names as $key => $value) {
×
212
                        if (!empty($value) && is_array($value)) {
×
213
                                $names[$key] = implode(', ', $value);
×
214
                        }
215
                }
216
                if (!empty($names)) {
×
217
                        $json['json']['request']['names'][] = $names;
×
218
                }
219

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

234
                $responseDecoded = json_decode((string)$response->getBody(), true);
×
235
                if (!isset($responseDecoded['success']) || !$responseDecoded['success']) {
×
236
                        throw new LibresignException('Error while generating certificate keys!', 500);
×
237
                }
238

239
                return $responseDecoded['result'];
×
240
        }
241

242
        private function gencert(): void {
243
                $binary = $this->getBinary();
×
244
                $configPath = $this->getCurrentConfigPath();
×
245
                $csrFile = $configPath . '/csr_server.json';
×
246

247
                $cmd = escapeshellcmd($binary) . ' gencert -initca ' . escapeshellarg($csrFile);
×
248
                $output = shell_exec($cmd);
×
249

250
                if (!$output) {
×
251
                        throw new \RuntimeException('cfssl without output.');
×
252
                }
253

254
                $json = json_decode($output, true);
×
255
                if (!$json || !isset($json['cert'], $json['key'], $json['csr'])) {
×
256
                        throw new \RuntimeException('Error generating CA: invalid cfssl output.');
×
257
                }
258

259
                file_put_contents($configPath . '/ca.pem', $json['cert']);
×
260
                file_put_contents($configPath . '/ca-key.pem', $json['key']);
×
261
                file_put_contents($configPath . '/ca.csr', $json['csr']);
×
262

263
                $this->persistRootCertificateFromData($json['cert']);
×
264
        }
265

266
        private function getClient(): Client {
267
                if (!$this->client) {
×
268
                        $this->setClient(new Client(['base_uri' => $this->getCfsslUri()]));
×
269
                }
270
                $this->wakeUp();
×
271
                return $this->client;
×
272
        }
273

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

300
                $responseDecoded = json_decode((string)$response->getBody(), true);
×
301
                if (!isset($responseDecoded['success']) || !$responseDecoded['success']) {
×
302
                        throw new LibresignException('Error while check cfssl API health!', 500);
×
303
                }
304

305
                if (empty($responseDecoded['result']) || 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
                $cmd = 'nohup ' . $binary . ' serve -address=127.0.0.1 '
×
323
                        . '-ca-key ' . $configPath . DIRECTORY_SEPARATOR . 'ca-key.pem '
×
324
                        . '-ca ' . $configPath . DIRECTORY_SEPARATOR . 'ca.pem '
×
325
                        . '-config ' . $configPath . DIRECTORY_SEPARATOR . 'config_server.json > /dev/null 2>&1 & echo $!';
×
326
                shell_exec($cmd);
×
327
                $loops = 0;
×
328
                while (!$this->portOpen() && $loops <= 4) {
×
329
                        sleep(1);
×
330
                        $loops++;
×
331
                }
332
        }
333

334
        private function portOpen(): bool {
335
                $host = parse_url($this->getCfsslUri(), PHP_URL_HOST);
×
336
                $port = parse_url($this->getCfsslUri(), PHP_URL_PORT);
×
337

338
                set_error_handler(function (): void { });
×
339
                $socket = fsockopen($host, $port, $errno, $errstr, 0.1);
×
340
                restore_error_handler();
×
341
                if (!$socket || $errno || $errstr) {
×
342
                        return false;
×
343
                }
344
                fclose($socket);
×
345
                return true;
×
346
        }
347

348
        private function getServerPid(): int {
349
                $cmd = 'ps -eo pid,command|';
×
350
                $cmd .= 'grep "cfssl.*serve.*-address"|'
×
351
                        . 'grep -v grep|'
×
352
                        . 'grep -v defunct|'
×
353
                        . 'sed -e "s/^[[:space:]]*//"|cut -d" " -f1';
×
354
                $output = shell_exec($cmd);
×
355
                if (!is_string($output)) {
×
356
                        return 0;
×
357
                }
358
                $pid = trim($output);
×
359
                return (int)$pid;
×
360
        }
361

362
        /**
363
         * Parse command
364
         *
365
         * Have commands that need to be executed as sudo otherwise don't will work,
366
         * by example the command runuser or kill. To prevent error when run in a
367
         * GitHub Actions, these commands are executed prefixed by sudo when exists
368
         * an environment called GITHUB_ACTIONS.
369
         */
370
        private function parseCommand(string $command): string {
371
                if (getenv('GITHUB_ACTIONS') !== false) {
×
372
                        $command = 'sudo ' . $command;
×
373
                }
374
                return $command;
×
375
        }
376

377
        private function stopIfRunning(): void {
378
                $pid = $this->getServerPid();
×
379
                if ($pid > 0) {
×
380
                        exec($this->parseCommand('kill -9 ' . $pid));
×
381
                }
382
        }
383

384
        private function getBinary(): string {
385
                if ($this->binary) {
×
386
                        return $this->binary;
×
387
                }
388

389
                if (PHP_OS_FAMILY === 'Windows') {
×
390
                        throw new LibresignException('Incompatible with Windows');
×
391
                }
392

393
                if ($this->appConfig->hasKey(Application::APP_ID, 'cfssl_bin')) {
×
394
                        $binary = $this->appConfig->getValueString(Application::APP_ID, 'cfssl_bin');
×
395
                        if (!file_exists($binary)) {
×
396
                                $this->appConfig->deleteKey(Application::APP_ID, 'cfssl_bin');
×
397
                        }
398
                        return $binary;
×
399
                }
400
                throw new LibresignException('Binary of CFSSL not found. Install binaries.');
×
401
        }
402

403
        private function getCfsslUri(): string {
404
                if ($this->cfsslUri) {
×
405
                        return $this->cfsslUri;
×
406
                }
407

408
                if ($uri = $this->appConfig->getValueString(Application::APP_ID, 'cfssl_uri')) {
×
409
                        return $uri;
×
410
                }
411
                $this->appConfig->deleteKey(Application::APP_ID, 'cfssl_uri');
×
412

413
                $this->cfsslUri = self::CFSSL_URI;
×
414
                return $this->cfsslUri;
×
415
        }
416

417
        public function setCfsslUri($uri): void {
418
                if ($uri) {
×
419
                        $this->appConfig->setValueString(Application::APP_ID, 'cfssl_uri', $uri);
×
420
                } else {
421
                        $this->appConfig->deleteKey(Application::APP_ID, 'cfssl_uri');
×
422
                }
423
                $this->cfsslUri = $uri;
×
424
        }
425

426
        private function checkBinaries(): array {
427
                if (PHP_OS_FAMILY === 'Windows') {
×
428
                        return [
×
429
                                (new ConfigureCheckHelper())
×
430
                                        ->setErrorMessage('CFSSL is incompatible with Windows')
×
431
                                        ->setResource('cfssl'),
×
432
                        ];
×
433
                }
434
                $binary = $this->appConfig->getValueString(Application::APP_ID, 'cfssl_bin');
×
435
                if (!$binary) {
×
436
                        return [
×
437
                                (new ConfigureCheckHelper())
×
438
                                        ->setErrorMessage('CFSSL not installed.')
×
439
                                        ->setResource('cfssl')
×
440
                                        ->setTip('Run occ libresign:install --cfssl'),
×
441
                        ];
×
442
                }
443

444
                if (!file_exists($binary)) {
×
445
                        return [
×
446
                                (new ConfigureCheckHelper())
×
447
                                        ->setErrorMessage('CFSSL not found.')
×
448
                                        ->setResource('cfssl')
×
449
                                        ->setTip('Run occ libresign:install --cfssl'),
×
450
                        ];
×
451
                }
452
                $version = shell_exec("$binary version");
×
453
                if (!is_string($version) || empty($version)) {
×
454
                        return [
×
455
                                (new ConfigureCheckHelper())
×
456
                                        ->setErrorMessage(sprintf(
×
457
                                                'Failed to run the command "%s" with user %s',
×
458
                                                "$binary version",
×
459
                                                get_current_user()
×
460
                                        ))
×
461
                                        ->setResource('cfssl')
×
462
                                        ->setTip('Run occ libresign:install --cfssl')
×
463
                        ];
×
464
                }
465
                preg_match_all('/: (?<version>.*)/', $version, $matches);
×
466
                if (!$matches || !isset($matches['version']) || count($matches['version']) !== 2) {
×
467
                        return [
×
468
                                (new ConfigureCheckHelper())
×
469
                                        ->setErrorMessage(sprintf(
×
470
                                                'Failed to identify cfssl version with command %s',
×
471
                                                "$binary version"
×
472
                                        ))
×
473
                                        ->setResource('cfssl')
×
474
                                        ->setTip('Run occ libresign:install --cfssl')
×
475
                        ];
×
476
                }
477
                if (!str_contains($matches['version'][0], InstallService::CFSSL_VERSION)) {
×
478
                        return [
×
479
                                (new ConfigureCheckHelper())
×
480
                                        ->setErrorMessage(sprintf(
×
481
                                                'Invalid version. Expected: %s, actual: %s',
×
482
                                                InstallService::CFSSL_VERSION,
×
483
                                                $matches['version'][0]
×
484
                                        ))
×
485
                                        ->setResource('cfssl')
×
486
                                        ->setTip('Run occ libresign:install --cfssl')
×
487
                        ];
×
488
                }
489
                $return = [];
×
490
                $return[] = (new ConfigureCheckHelper())
×
491
                        ->setSuccessMessage('CFSSL binary path: ' . $binary)
×
492
                        ->setResource('cfssl');
×
493
                $return[] = (new ConfigureCheckHelper())
×
494
                        ->setSuccessMessage('CFSSL version: ' . $matches['version'][0])
×
495
                        ->setResource('cfssl');
×
496
                $return[] = (new ConfigureCheckHelper())
×
497
                        ->setSuccessMessage('Runtime: ' . $matches['version'][1])
×
498
                        ->setResource('cfssl');
×
499
                return $return;
×
500
        }
501

502
        /**
503
         * Get Authority Key Identifier from certificate (needed for CFSSL revocation)
504
         *
505
         * @param string $certificatePem PEM encoded certificate
506
         * @return string Authority Key Identifier in lowercase without colons
507
         */
508
        public function getAuthorityKeyId(string $certificatePem): string {
509
                $cert = openssl_x509_read($certificatePem);
×
510
                if (!$cert) {
×
511
                        throw new \RuntimeException('Invalid certificate format');
×
512
                }
513

514
                $parsed = openssl_x509_parse($cert);
×
515
                if (!$parsed || !isset($parsed['extensions']['authorityKeyIdentifier'])) {
×
516
                        throw new \RuntimeException('Certificate does not contain Authority Key Identifier');
×
517
                }
518

519
                $authKeyId = $parsed['extensions']['authorityKeyIdentifier'];
×
520

521
                if (preg_match('/keyid:([A-Fa-f0-9:]+)/', $authKeyId, $matches)) {
×
522
                        return strtolower(str_replace(':', '', $matches[1]));
×
523
                }
524

525
                throw new \RuntimeException('Could not parse Authority Key Identifier');
×
526
        }
527

528
        /**
529
         * Revoke a certificate using CFSSL API
530
         *
531
         * @param string $serialNumber Certificate serial number in decimal format
532
         * @param string $authorityKeyId Authority key identifier (lowercase, no colons)
533
         * @param string $reason CRLReason description string (e.g., 'superseded', 'keyCompromise')
534
         */
535
        public function revokeCertificate(string $serialNumber, string $authorityKeyId, string $reason): bool {
536
                try {
537
                        $json = [
×
538
                                'json' => [
×
539
                                        'serial' => $serialNumber,
×
540
                                        'authority_key_id' => $authorityKeyId,
×
541
                                        'reason' => $reason,
×
542
                                ],
×
543
                        ];
×
544

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

547
                        $responseData = json_decode((string)$response->getBody(), true);
×
548

549
                        if (!isset($responseData['success'])) {
×
550
                                $errorMessage = isset($responseData['errors'])
×
551
                                        ? implode(', ', array_column($responseData['errors'], 'message'))
×
552
                                        : 'Unknown CFSSL error';
×
553
                                throw new \RuntimeException('CFSSL revocation failed: ' . $errorMessage);
×
554
                        }
555

556
                        return $responseData['success'];
×
557

558
                } catch (RequestException|ConnectException $e) {
×
559
                        throw new \RuntimeException('Failed to communicate with CFSSL server: ' . $e->getMessage());
×
560
                } catch (\Throwable $e) {
×
561
                        throw new \RuntimeException('CFSSL certificate revocation error: ' . $e->getMessage());
×
562
                }
563
        }
564

565
        private function persistSerialNumberToCrl(array $parsed): void {
566
                if (!isset($parsed['serialNumberHex']) || !isset($parsed['valid_to'])) {
×
567
                        return;
×
568
                }
569

570
                $serialNumber = $parsed['serialNumberHex'];
×
571

572
                $owner = $this->getCommonName() ?? 'Unknown';
×
573

574
                $expiresAt = null;
×
575
                if (isset($parsed['validTo_time_t'])) {
×
576
                        $expiresAt = new \DateTime('@' . $parsed['validTo_time_t']);
×
577
                }
578

579
                $issuer = $parsed['issuer'] ?? [];
×
580
                $subject = $parsed['subject'] ?? [];
×
581

582
                $this->crlMapper->createCertificate(
×
583
                        $serialNumber,
×
584
                        $owner,
×
585
                        'cfssl',
×
586
                        $this->caIdentifierService->getInstanceId(),
×
587
                        $this->caIdentifierService->getCaIdParsed()['generation'],
×
588
                        new \DateTime(),
×
589
                        $expiresAt,
×
590
                        $issuer,
×
591
                        $subject,
×
592
                        CertificateType::LEAF->value,
×
593
                );
×
594
        }
595

596
        private function persistRootCertificateFromData(string $certPem): void {
597
                $x509Resource = openssl_x509_read($certPem);
×
598
                if (!$x509Resource) {
×
599
                        throw new \RuntimeException('Failed to parse root certificate');
×
600
                }
601

602
                $parsed = openssl_x509_parse($x509Resource);
×
603
                if (!$parsed) {
×
604
                        throw new \RuntimeException('Failed to extract root certificate information');
×
605
                }
606

607
                $serialNumber = $parsed['serialNumberHex'] ?? '';
×
608
                if (empty($serialNumber)) {
×
609
                        throw new \RuntimeException('Root certificate has no serial number');
×
610
                }
611

612
                $owner = $this->getCommonName() ?? 'Root CA';
×
613

614
                $expiresAt = null;
×
615
                if (isset($parsed['validTo_time_t'])) {
×
616
                        $expiresAt = new \DateTime('@' . $parsed['validTo_time_t']);
×
617
                }
618

619
                /** @var array<string, mixed> $issuer */
620
                $issuer = $parsed['issuer'] ?? [];
×
621
                /** @var array<string, mixed> $subject */
622
                $subject = $parsed['subject'] ?? [];
×
623

624
                $this->crlMapper->createCertificate(
×
625
                        $serialNumber,
×
626
                        $owner,
×
627
                        'cfssl',
×
628
                        $this->caIdentifierService->getInstanceId(),
×
629
                        $this->caIdentifierService->getCaIdParsed()['generation'],
×
630
                        new \DateTime(),
×
631
                        $expiresAt,
×
632
                        $issuer,
×
633
                        $subject,
×
634
                        CertificateType::ROOT->value,
×
635
                );
×
636
        }
637
}
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