Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.45% covered (success)
95.45%
21 / 22
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
TsaValidationService
95.45% covered (success)
95.45%
21 / 22
80.00% covered (warning)
80.00%
4 / 5
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
 validateConfiguration
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getTsaUrl
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 validateTsaUrlFormat
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
5.05
 validateTsaHostResolution
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4/**
5 * SPDX-FileCopyrightText: 2024 LibreCode coop and contributors
6 * SPDX-License-Identifier: AGPL-3.0-or-later
7 */
8
9namespace OCA\Libresign\Service;
10
11use OCA\Libresign\Exception\LibresignException;
12use OCA\Libresign\Service\Policy\PolicyService;
13use OCA\Libresign\Service\Policy\Provider\Tsa\TsaPolicy;
14use OCA\Libresign\Service\Policy\Provider\Tsa\TsaPolicyValue;
15
16class TsaValidationService {
17    public function __construct(
18        private PolicyService $policyService,
19    ) {
20    }
21
22    /**
23     * Validate TSA configuration
24     *
25     * @throws LibresignException if TSA is misconfigured
26     */
27    public function validateConfiguration(): void {
28        $tsaUrl = $this->getTsaUrl();
29        if (empty($tsaUrl)) {
30            return;
31        }
32
33        $this->validateTsaUrlFormat($tsaUrl);
34        $this->validateTsaHostResolution($tsaUrl);
35    }
36
37    private function getTsaUrl(): string {
38        $rawPolicyValue = $this->policyService->resolve(TsaPolicy::KEY)->getEffectiveValue();
39        $decoded = TsaPolicyValue::decode($rawPolicyValue);
40        return $decoded['url'];
41    }
42
43    private function validateTsaUrlFormat(string $tsaUrl): void {
44        $tsaUrlParsed = parse_url($tsaUrl);
45
46        $scheme = $tsaUrlParsed['scheme'] ?? '';
47        if ($scheme !== '' && !in_array(strtolower($scheme), ['http', 'https'], true)) {
48            throw new LibresignException('TSA URL must use HTTP or HTTPS protocol: ' . $tsaUrl);
49        }
50
51        if (!filter_var($tsaUrl, FILTER_VALIDATE_URL)) {
52            throw new LibresignException('Invalid TSA URL format: ' . $tsaUrl);
53        }
54
55        if (!isset($tsaUrlParsed['host'])) {
56            throw new LibresignException('Invalid TSA URL: ' . $tsaUrl);
57        }
58    }
59
60    private function validateTsaHostResolution(string $tsaUrl): void {
61        $host = (string)parse_url($tsaUrl, PHP_URL_HOST);
62
63        if (filter_var($host, FILTER_VALIDATE_IP)) {
64            return;
65        }
66
67        if (!@gethostbyname($host) || gethostbyname($host) === $host) {
68            throw new LibresignException('Timestamp Authority (TSA) service is unavailable. Check DNS/network/firewall connectivity from this server: ' . $tsaUrl);
69        }
70    }
71}