Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.56% covered (success)
97.56%
40 / 41
90.00% covered (success)
90.00%
9 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
CaIdentifierService
97.56% covered (success)
97.56%
40 / 41
90.00% covered (success)
90.00%
9 / 10
13
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getInstanceId
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 generateCaId
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 getCaId
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isValidCaId
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 generatePkiDirectoryName
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 generatePkiDirectoryNameFromParams
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCaIdParsed
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 parseCaId
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
2.01
 getNextGeneration
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4/**
5 * SPDX-FileCopyrightText: 2025 LibreCode coop and contributors
6 * SPDX-License-Identifier: AGPL-3.0-or-later
7 */
8
9namespace OCA\Libresign\Service;
10
11use OCA\Libresign\AppInfo\Application;
12use OCP\IAppConfig;
13use OCP\Security\ISecureRandom;
14
15class CaIdentifierService {
16    private const array ENGINE_TYPES = [
17        'openssl' => 'o',
18        'cfssl' => 'c',
19    ];
20
21    public function __construct(
22        private IAppConfig $appConfig,
23        private ISecureRandom $secureRandom,
24    ) {
25    }
26
27    public function getInstanceId(): string {
28        $instanceId = $this->appConfig->getValueString(Application::APP_ID, 'instance_id', '');
29        if (strlen($instanceId) === 10) {
30            return $instanceId;
31        }
32        $instanceId = $this->secureRandom->generate(10, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS);
33        $this->appConfig->setValueString(Application::APP_ID, 'instance_id', $instanceId);
34        return $instanceId;
35    }
36
37    public function generateCaId(string $engineName): string {
38        $instanceId = $this->getInstanceId();
39
40        $generation = $this->getNextGeneration();
41
42        $caId = sprintf(
43            'libresign-ca-id:%s_g:%d_e:%s',
44            $instanceId,
45            $generation,
46            self::ENGINE_TYPES[$engineName] ?? throw new \InvalidArgumentException("Invalid engine name: $engineName"),
47        );
48        $this->appConfig->setValueString(Application::APP_ID, 'ca_id', $caId);
49        return $caId;
50    }
51
52    public function getCaId(): string {
53        $caId = $this->appConfig->getValueString(Application::APP_ID, 'ca_id');
54        return $caId;
55    }
56
57    public function isValidCaId(string $caId, string $instanceId): bool {
58        $enginePattern = '[' . implode('', array_values(self::ENGINE_TYPES)) . ']';
59
60        $newPattern = '/^libresign-ca-id:' . preg_quote($instanceId, '/') . '_g:\d+_e:' . $enginePattern . '$/';
61        if (preg_match($newPattern, $caId)) {
62            return true;
63        }
64        return false;
65    }
66
67    public function generatePkiDirectoryName(string $caId): string {
68        $parsed = $this->parseCaId($caId);
69        return $this->generatePkiDirectoryNameFromParams($parsed['instanceId'], $parsed['generation'], $parsed['engineName']);
70    }
71
72    public function generatePkiDirectoryNameFromParams(string $instanceId, int $generation, string $engineName): string {
73        return 'pki/' . $instanceId . '_' . $generation . '_' . $engineName;
74    }
75
76    public function getCaIdParsed(): array {
77        $caId = $this->getCaId();
78        return $this->parseCaId($caId);
79    }
80
81    private function parseCaId(string $caId): array {
82        $pattern = '/^libresign-ca-id:(?P<instanceId>[a-z0-9]+)_g:(?P<generation>\d+)_e:(?P<engineType>[' . implode('', array_values(self::ENGINE_TYPES)) . '])$/';
83        if (!preg_match($pattern, $caId, $matches)) {
84            throw new \InvalidArgumentException('Invalid CA ID format');
85        }
86        $parsed['engineName'] = array_search($matches['engineType'], self::ENGINE_TYPES, true);
87        $parsed['instanceId'] = $matches['instanceId'];
88        $parsed['generation'] = (int)$matches['generation'];
89        $parsed['engineType'] = $matches['engineType'];
90        return $parsed;
91    }
92
93    private function getNextGeneration(): int {
94        $currentNumber = $this->appConfig->getValueInt(Application::APP_ID, 'ca_generation_counter', 0);
95        $nextNumber = $currentNumber + 1;
96        $this->appConfig->setValueInt(Application::APP_ID, 'ca_generation_counter', $nextNumber);
97
98        return $nextNumber;
99    }
100}