Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
35.38% covered (danger)
35.38%
23 / 65
40.00% covered (danger)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
SignSetup
35.38% covered (danger)
35.38%
23 / 65
40.00% covered (danger)
40.00%
2 / 5
105.41
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
 isEnabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 configure
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 execute
25.00% covered (danger)
25.00%
10 / 40
0.00% covered (danger)
0.00%
0 / 1
62.05
 fileGetContents
35.29% covered (danger)
35.29%
6 / 17
0.00% covered (danger)
0.00%
0 / 1
8.33
1<?php
2
3declare(strict_types=1);
4/**
5 * SPDX-FileCopyrightText: 2020-2024 LibreCode coop and contributors
6 * SPDX-License-Identifier: AGPL-3.0-or-later
7 */
8
9namespace OCA\Libresign\Command\Developer;
10
11use Exception;
12use OC\Core\Command\Base;
13use OCA\Libresign\Service\Install\InstallService;
14use OCA\Libresign\Service\Install\SignSetupService;
15use OCA\Libresign\Vendor\phpseclib4\Crypt\RSA;
16use OCA\Libresign\Vendor\phpseclib4\Exception\NoKeyLoadedException;
17use OCA\Libresign\Vendor\phpseclib4\Exception\UnexpectedValueException;
18use OCA\Libresign\Vendor\phpseclib4\File\X509;
19use OCP\IConfig;
20use SodiumException;
21use Symfony\Component\Console\Input\InputInterface;
22use Symfony\Component\Console\Input\InputOption;
23use Symfony\Component\Console\Output\OutputInterface;
24
25class SignSetup extends Base {
26    public function __construct(
27        private IConfig $config,
28        private SignSetupService $signSetupService,
29        private InstallService $installService,
30    ) {
31        parent::__construct();
32    }
33
34    #[\Override]
35    public function isEnabled(): bool {
36        return $this->config->getSystemValue('debug', false) === true;
37    }
38
39    #[\Override]
40    protected function configure(): void {
41        $this
42            ->setName('libresign:developer:sign-setup')
43            ->setDescription('Sign the current setup')
44            ->addOption('privateKey', null, InputOption::VALUE_REQUIRED, 'Path to private key to use for signing')
45            ->addOption('certificate', null, InputOption::VALUE_REQUIRED, 'Path to certificate to use for signing')
46        ;
47    }
48
49    #[\Override]
50    protected function execute(InputInterface $input, OutputInterface $output): int {
51        $privateKeyPath = $input->getOption('privateKey');
52        $keyBundlePath = $input->getOption('certificate');
53        if (is_null($privateKeyPath) || is_null($keyBundlePath)) {
54            $output->writeln('This command requires --privateKey and --certificate.');
55            $output->writeln('Example: ./occ libresign:developer:sign-setup --privateKey="/libresign/private/myapp.key" --certificate="/libresign/public/mycert.crt"');
56            return 1;
57        }
58        try {
59            $privateKey = $this->fileGetContents((string)$privateKeyPath);
60            $keyBundle = $this->fileGetContents((string)$keyBundlePath);
61        } catch (\Throwable $th) {
62            $output->writeln('<error>' . $th->getMessage() . '</error>');
63            return 1;
64        }
65
66        try {
67            $rsa = RSA::loadPrivateKey($privateKey);
68        } catch (NoKeyLoadedException) {
69            $output->writeln('Invalid private key');
70            return 1;
71        }
72        try {
73            $x509 = X509::load($keyBundle);
74        } catch (UnexpectedValueException|SodiumException) {
75            $output->writeln('Invalid certificate');
76            return 1;
77        }
78        try {
79            $this->signSetupService->setCertificate($x509);
80            $this->signSetupService->setPrivateKey($rsa);
81            foreach ($this->signSetupService->getArchitectures() as $architecture) {
82                foreach ($this->installService->getAvailableResources() as $resource) {
83                    if ($resource === 'java') {
84                        foreach (['linux', 'alpine-linux'] as $distro) {
85                            $this->signSetupService
86                                ->setDistro($distro)
87                                ->setArchitecture($architecture)
88                                ->setResource($resource)
89                                ->writeAppSignature();
90                        }
91                        continue;
92                    }
93                    $this->signSetupService
94                        ->setArchitecture($architecture)
95                        ->setResource($resource)
96                        ->writeAppSignature();
97                }
98            }
99            $output->writeln('Successfully signed');
100        } catch (\Exception $e) {
101            $output->writeln('Error: ' . $e->getMessage());
102            return 1;
103        }
104        return 0;
105    }
106
107    /**
108     * Wrapper around file_get_contents($filename)
109     *
110     * @param string $filename
111     * @return string|false
112     */
113    private function fileGetContents(string $path) {
114        $filename = $path;
115        $isRelative = false;
116        if (!str_starts_with($filename, '/')) {
117            $filename = \OC::$SERVERROOT . '/' . $filename;
118            $isRelative = true;
119        }
120        $filename = realpath($filename);
121        if (!$filename) {
122            if ($isRelative) {
123                throw new Exception(sprintf(
124                    "File %s does not exists.\nRoot dir: %s.\nCurrent dir: %s.\nLibreSign dir: %s",
125                    $path,
126                    \OC::$SERVERROOT,
127                    getcwd(),
128                    realpath(__DIR__ . '/../../../'),
129                ));
130            }
131            throw new Exception(sprintf('File "%s" does not exists.', $path));
132        }
133        return file_get_contents($filename);
134    }
135}