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

LibreSign / libresign / 19839372203

01 Dec 2025 10:16PM UTC coverage: 40.771%. First build
19839372203

Pull #5872

github

web-flow
Merge 7fad3608b into c3ec57e4c
Pull Request #5872: feat: certificate type classification

33 of 70 new or added lines in 7 files covered. (47.14%)

4963 of 12173 relevant lines covered (40.77%)

3.99 hits per line

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

3.48
/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\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\Install\InstallService;
24
use OCP\Files\AppData\IAppDataFactory;
25
use OCP\IAppConfig;
26
use OCP\IConfig;
27
use OCP\IDateTimeFormatter;
28
use OCP\ITempManager;
29
use OCP\IURLGenerator;
30
use Psr\Log\LoggerInterface;
31

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

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

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

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

76
        #[\Override]
77
        public function generateRootCert(
78
                string $commonName,
79
                array $names = [],
80
        ): void {
81
                $this->cfsslServerHandler->createConfigServer(
×
82
                        $commonName,
×
83
                        $names,
×
84
                        $this->getCaExpiryInDays(),
×
85
                        $this->getCrlDistributionUrl(),
×
86
                );
×
87

88
                $this->gencert();
×
89

90
                $this->stopIfRunning();
×
91

92
                for ($i = 1; $i <= 4; $i++) {
×
93
                        if ($this->isUp()) {
×
94
                                break;
×
95
                        }
96
                        sleep(2);
×
97
                }
98
        }
99

100
        #[\Override]
101
        public function generateCertificate(): string {
102
                $this->validateRootCertificate();
×
103

104
                $certKeys = $this->newCert();
×
105
                $pkcs12 = parent::exportToPkcs12(
×
106
                        $certKeys['certificate'],
×
107
                        $certKeys['private_key'],
×
108
                        [
×
109
                                'friendly_name' => $this->getFriendlyName(),
×
110
                                'extracerts' => [
×
111
                                        $certKeys['certificate'],
×
112
                                        $certKeys['certificate_request'],
×
113
                                ],
×
114
                        ],
×
115
                );
×
116

117
                $parsed = $this->readCertificate($pkcs12, $this->getPassword());
×
118
                $this->persistSerialNumberToCrl($parsed);
×
119

120
                return $pkcs12;
×
121
        }
122

123
        #[\Override]
124
        public function isSetupOk(): bool {
125
                $configPath = $this->getCurrentConfigPath();
×
126
                $certificate = file_exists($configPath . DIRECTORY_SEPARATOR . 'ca.pem');
×
127
                $privateKey = file_exists($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem');
×
128
                if (!$certificate || !$privateKey) {
×
129
                        return false;
×
130
                }
131
                try {
132
                        $this->getClient();
×
133
                        return true;
×
134
                } catch (\Throwable) {
×
135
                }
136
                return false;
×
137
        }
138

139
        #[\Override]
140
        protected function getConfigureCheckResourceName(): string {
141
                return 'cfssl-configure';
×
142
        }
143

144
        #[\Override]
145
        protected function getCertificateRegenerationTip(): string {
146
                return 'Consider regenerating the root certificate with: occ libresign:configure:cfssl --cn="Your CA Name"';
×
147
        }
148

149
        #[\Override]
150
        protected function getEngineSpecificChecks(): array {
151
                return $this->checkBinaries();
×
152
        }
153

154
        #[\Override]
155
        protected function getSetupSuccessMessage(): string {
156
                return 'Root certificate config files found.';
×
157
        }
158

159
        #[\Override]
160
        protected function getSetupErrorMessage(): string {
161
                return 'CFSSL (root certificate) not configured.';
×
162
        }
163

164
        #[\Override]
165
        protected function getSetupErrorTip(): string {
166
                return 'Run occ libresign:configure:cfssl --help';
×
167
        }
168

169
        #[\Override]
170
        public function toArray(): array {
171
                $return = parent::toArray();
×
172
                if (!empty($return['configPath'])) {
×
173
                        $return['cfsslUri'] = $this->appConfig->getValueString(Application::APP_ID, 'cfssl_uri');
×
174
                }
175
                return $return;
×
176
        }
177

178
        public function getCommonName(): string {
179
                $uid = $this->getUID();
×
180
                if (!$uid) {
×
181
                        return $this->commonName;
×
182
                }
183
                return $uid . ', ' . $this->commonName;
×
184
        }
185

186
        private function newCert(): array {
187
                $json = [
×
188
                        'json' => [
×
189
                                'profile' => 'client',
×
190
                                'request' => [
×
191
                                        'hosts' => $this->getHosts(),
×
192
                                        'CN' => $this->getCommonName(),
×
193
                                        'key' => [
×
194
                                                'algo' => 'rsa',
×
195
                                                'size' => 2048,
×
196
                                        ],
×
197
                                        'names' => [],
×
198
                                        'crl_url' => $this->getCrlDistributionUrl(),
×
199
                                ],
×
200
                        ],
×
201
                ];
×
202

203
                $names = $this->getNames();
×
204
                foreach ($names as $key => $value) {
×
205
                        if (!empty($value) && is_array($value)) {
×
206
                                $names[$key] = implode(', ', $value);
×
207
                        }
208
                }
209
                if (!empty($names)) {
×
210
                        $json['json']['request']['names'][] = $names;
×
211
                }
212

213
                try {
214
                        $response = $this->getClient()
×
215
                                ->request('post',
×
216
                                        'newcert',
×
217
                                        $json
×
218
                                )
×
219
                        ;
×
220
                } catch (RequestException|ConnectException $th) {
×
221
                        if ($th->getHandlerContext() && $th->getHandlerContext()['error']) {
×
222
                                throw new \Exception($th->getHandlerContext()['error'], 1);
×
223
                        }
224
                        throw new LibresignException($th->getMessage(), 500);
×
225
                }
226

227
                $responseDecoded = json_decode((string)$response->getBody(), true);
×
228
                if (!isset($responseDecoded['success']) || !$responseDecoded['success']) {
×
229
                        throw new LibresignException('Error while generating certificate keys!', 500);
×
230
                }
231

232
                return $responseDecoded['result'];
×
233
        }
234

235
        private function gencert(): void {
236
                $binary = $this->getBinary();
×
237
                $configPath = $this->getCurrentConfigPath();
×
238
                $csrFile = $configPath . '/csr_server.json';
×
239

240
                $cmd = escapeshellcmd($binary) . ' gencert -initca ' . escapeshellarg($csrFile);
×
241
                $output = shell_exec($cmd);
×
242

243
                if (!$output) {
×
244
                        throw new \RuntimeException('cfssl without output.');
×
245
                }
246

247
                $json = json_decode($output, true);
×
248
                if (!$json || !isset($json['cert'], $json['key'], $json['csr'])) {
×
249
                        throw new \RuntimeException('Error generating CA: invalid cfssl output.');
×
250
                }
251

252
                file_put_contents($configPath . '/ca.pem', $json['cert']);
×
253
                file_put_contents($configPath . '/ca-key.pem', $json['key']);
×
254
                file_put_contents($configPath . '/ca.csr', $json['csr']);
×
255

256
                $this->persistRootCertificateFromData($json['cert']);
×
257
        }
258

259
        private function getClient(): Client {
260
                if (!$this->client) {
×
261
                        $this->setClient(new Client(['base_uri' => $this->getCfsslUri()]));
×
262
                }
263
                $this->wakeUp();
×
264
                return $this->client;
×
265
        }
266

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

293
                $responseDecoded = json_decode((string)$response->getBody(), true);
×
294
                if (!isset($responseDecoded['success']) || !$responseDecoded['success']) {
×
295
                        throw new LibresignException('Error while check cfssl API health!', 500);
×
296
                }
297

298
                if (empty($responseDecoded['result']) || empty($responseDecoded['result']['healthy'])) {
×
299
                        return false;
×
300
                }
301

302
                return (bool)$responseDecoded['result']['healthy'];
×
303
        }
304

305
        private function wakeUp(): void {
306
                if ($this->portOpen()) {
×
307
                        return;
×
308
                }
309
                $binary = $this->getBinary();
×
310
                $configPath = $this->getCurrentConfigPath();
×
311
                if (!$configPath) {
×
312
                        throw new LibresignException('CFSSL not configured.');
×
313
                }
314
                $this->cfsslServerHandler->updateExpirity($this->getCaExpiryInDays());
×
315
                $cmd = 'nohup ' . $binary . ' serve -address=127.0.0.1 '
×
316
                        . '-ca-key ' . $configPath . DIRECTORY_SEPARATOR . 'ca-key.pem '
×
317
                        . '-ca ' . $configPath . DIRECTORY_SEPARATOR . 'ca.pem '
×
318
                        . '-config ' . $configPath . DIRECTORY_SEPARATOR . 'config_server.json > /dev/null 2>&1 & echo $!';
×
319
                shell_exec($cmd);
×
320
                $loops = 0;
×
321
                while (!$this->portOpen() && $loops <= 4) {
×
322
                        sleep(1);
×
323
                        $loops++;
×
324
                }
325
        }
326

327
        private function portOpen(): bool {
328
                $host = parse_url($this->getCfsslUri(), PHP_URL_HOST);
×
329
                $port = parse_url($this->getCfsslUri(), PHP_URL_PORT);
×
330

331
                set_error_handler(function (): void { });
×
332
                $socket = fsockopen($host, $port, $errno, $errstr, 0.1);
×
333
                restore_error_handler();
×
334
                if (!$socket || $errno || $errstr) {
×
335
                        return false;
×
336
                }
337
                fclose($socket);
×
338
                return true;
×
339
        }
340

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

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

370
        private function stopIfRunning(): void {
371
                $pid = $this->getServerPid();
×
372
                if ($pid > 0) {
×
373
                        exec($this->parseCommand('kill -9 ' . $pid));
×
374
                }
375
        }
376

377
        private function getBinary(): string {
378
                if ($this->binary) {
×
379
                        return $this->binary;
×
380
                }
381

382
                if (PHP_OS_FAMILY === 'Windows') {
×
383
                        throw new LibresignException('Incompatible with Windows');
×
384
                }
385

386
                if ($this->appConfig->hasKey(Application::APP_ID, 'cfssl_bin')) {
×
387
                        $binary = $this->appConfig->getValueString(Application::APP_ID, 'cfssl_bin');
×
388
                        if (!file_exists($binary)) {
×
389
                                $this->appConfig->deleteKey(Application::APP_ID, 'cfssl_bin');
×
390
                        }
391
                        return $binary;
×
392
                }
393
                throw new LibresignException('Binary of CFSSL not found. Install binaries.');
×
394
        }
395

396
        private function getCfsslUri(): string {
397
                if ($this->cfsslUri) {
×
398
                        return $this->cfsslUri;
×
399
                }
400

401
                if ($uri = $this->appConfig->getValueString(Application::APP_ID, 'cfssl_uri')) {
×
402
                        return $uri;
×
403
                }
404
                $this->appConfig->deleteKey(Application::APP_ID, 'cfssl_uri');
×
405

406
                $this->cfsslUri = self::CFSSL_URI;
×
407
                return $this->cfsslUri;
×
408
        }
409

410
        public function setCfsslUri($uri): void {
411
                if ($uri) {
×
412
                        $this->appConfig->setValueString(Application::APP_ID, 'cfssl_uri', $uri);
×
413
                } else {
414
                        $this->appConfig->deleteKey(Application::APP_ID, 'cfssl_uri');
×
415
                }
416
                $this->cfsslUri = $uri;
×
417
        }
418

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

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

495
        /**
496
         * Get Authority Key Identifier from certificate (needed for CFSSL revocation)
497
         *
498
         * @param string $certificatePem PEM encoded certificate
499
         * @return string Authority Key Identifier in lowercase without colons
500
         */
501
        public function getAuthorityKeyId(string $certificatePem): string {
502
                $cert = openssl_x509_read($certificatePem);
×
503
                if (!$cert) {
×
504
                        throw new \RuntimeException('Invalid certificate format');
×
505
                }
506

507
                $parsed = openssl_x509_parse($cert);
×
508
                if (!$parsed || !isset($parsed['extensions']['authorityKeyIdentifier'])) {
×
509
                        throw new \RuntimeException('Certificate does not contain Authority Key Identifier');
×
510
                }
511

512
                $authKeyId = $parsed['extensions']['authorityKeyIdentifier'];
×
513

514
                if (preg_match('/keyid:([A-Fa-f0-9:]+)/', $authKeyId, $matches)) {
×
515
                        return strtolower(str_replace(':', '', $matches[1]));
×
516
                }
517

518
                throw new \RuntimeException('Could not parse Authority Key Identifier');
×
519
        }
520

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

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

540
                        $responseData = json_decode((string)$response->getBody(), true);
×
541

542
                        if (!isset($responseData['success'])) {
×
543
                                $errorMessage = isset($responseData['errors'])
×
544
                                        ? implode(', ', array_column($responseData['errors'], 'message'))
×
545
                                        : 'Unknown CFSSL error';
×
546
                                throw new \RuntimeException('CFSSL revocation failed: ' . $errorMessage);
×
547
                        }
548

549
                        return $responseData['success'];
×
550

551
                } catch (RequestException|ConnectException $e) {
×
552
                        throw new \RuntimeException('Failed to communicate with CFSSL server: ' . $e->getMessage());
×
553
                } catch (\Throwable $e) {
×
554
                        throw new \RuntimeException('CFSSL certificate revocation error: ' . $e->getMessage());
×
555
                }
556
        }
557

558
        private function persistSerialNumberToCrl(array $parsed): void {
559
                if (!isset($parsed['serialNumberHex']) || !isset($parsed['valid_to'])) {
×
560
                        return;
×
561
                }
562

563
                $serialNumber = $parsed['serialNumberHex'];
×
564

565
                $owner = $this->getCommonName() ?? 'Unknown';
×
566

567
                $expiresAt = null;
×
568
                if (isset($parsed['validTo_time_t'])) {
×
569
                        $expiresAt = new \DateTime('@' . $parsed['validTo_time_t']);
×
570
                }
571

NEW
572
                $issuer = $parsed['issuer'] ?? [];
×
NEW
573
                $subject = $parsed['subject'] ?? [];
×
574

575
                $this->crlMapper->createCertificate(
×
576
                        $serialNumber,
×
577
                        $owner,
×
578
                        'cfssl',
×
579
                        $this->caIdentifierService->getInstanceId(),
×
580
                        $this->caIdentifierService->getCaIdParsed()['generation'],
×
581
                        new \DateTime(),
×
NEW
582
                        $expiresAt,
×
NEW
583
                        $issuer,
×
NEW
584
                        $subject,
×
NEW
585
                        CertificateType::LEAF->value,
×
586
                );
×
587
        }
588

589
        private function persistRootCertificateFromData(string $certPem): void {
590
                $x509Resource = openssl_x509_read($certPem);
×
591
                if (!$x509Resource) {
×
592
                        throw new \RuntimeException('Failed to parse root certificate');
×
593
                }
594

595
                $parsed = openssl_x509_parse($x509Resource);
×
596
                if (!$parsed) {
×
597
                        throw new \RuntimeException('Failed to extract root certificate information');
×
598
                }
599

600
                $serialNumber = $parsed['serialNumberHex'] ?? '';
×
601
                if (empty($serialNumber)) {
×
602
                        throw new \RuntimeException('Root certificate has no serial number');
×
603
                }
604

605
                $owner = $this->getCommonName() ?? 'Root CA';
×
606

607
                $expiresAt = null;
×
608
                if (isset($parsed['validTo_time_t'])) {
×
609
                        $expiresAt = new \DateTime('@' . $parsed['validTo_time_t']);
×
610
                }
611

612
                /** @var array<string, mixed> $issuer */
NEW
613
                $issuer = $parsed['issuer'] ?? [];
×
614
                /** @var array<string, mixed> $subject */
NEW
615
                $subject = $parsed['subject'] ?? [];
×
616

617
                $this->crlMapper->createCertificate(
×
618
                        $serialNumber,
×
619
                        $owner,
×
620
                        'cfssl',
×
621
                        $this->caIdentifierService->getInstanceId(),
×
622
                        $this->caIdentifierService->getCaIdParsed()['generation'],
×
623
                        new \DateTime(),
×
NEW
624
                        $expiresAt,
×
NEW
625
                        $issuer,
×
NEW
626
                        $subject,
×
NEW
627
                        CertificateType::ROOT->value,
×
628
                );
×
629
        }
630
}
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