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

LibreSign / libresign / 25879532082

14 May 2026 07:00PM UTC coverage: 56.834%. First build
25879532082

Pull #7675

github

web-flow
Merge eb6768005 into a7c582837
Pull Request #7675: fix: keep groups_request_sign JSON unicode serialization consistent

9 of 15 new or added lines in 2 files covered. (60.0%)

10737 of 18892 relevant lines covered (56.83%)

6.99 hits per line

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

60.5
/lib/Service/Install/SignSetupService.php
1
<?php
2

3
declare(strict_types=1);
4
/**
5
 * SPDX-FileCopyrightText: 2024 LibreCode coop and contributors
6
 * SPDX-License-Identifier: AGPL-3.0-or-later
7
 */
8

9
namespace OCA\Libresign\Service\Install;
10

11
use OC\IntegrityCheck\Helpers\EnvironmentHelper;
12
use OC\IntegrityCheck\Helpers\FileAccessHelper;
13
use OCA\Libresign\AppInfo\Application;
14
use OCA\Libresign\Exception\EmptySignatureDataException;
15
use OCA\Libresign\Exception\InvalidSignatureException;
16
use OCA\Libresign\Exception\LibresignException;
17
use OCA\Libresign\Exception\SignatureDataNotFoundException;
18
use OCA\Libresign\Handler\CertificateEngine\CertificateHelper;
19
use OCA\Libresign\Vendor\LibreSign\WhatOSAmI\OperatingSystem;
20
use OCP\App\IAppManager;
21
use OCP\Files\AppData\IAppDataFactory;
22
use OCP\Files\IAppData;
23
use OCP\Files\NotFoundException;
24
use OCP\Files\SimpleFS\ISimpleFolder;
25
use OCP\IAppConfig;
26
use OCP\IConfig;
27
use OCP\ITempManager;
28
use phpseclib3\Crypt\PublicKeyLoader;
29
use phpseclib3\Crypt\RSA;
30
use phpseclib3\Crypt\RSA\PrivateKey;
31
use phpseclib3\File\X509;
32

33
class SignSetupService {
34
        private array $exclude = [
35
                'openssl_config',
36
                'cfssl_config',
37
                'unauthetnicated',
38
        ];
39
        private string $architecture;
40
        private string $resource;
41
        private array $signatureData = [];
42
        private bool $willUseLocalCert = false;
43
        private string $distro = '';
44
        private ?X509 $x509 = null;
45
        private ?PrivateKey $privateKey = null;
46
        private string $instanceId;
47
        private IAppData $appData;
48
        public function __construct(
49
                private EnvironmentHelper $environmentHelper,
50
                private FileAccessHelper $fileAccessHelper,
51
                private IConfig $config,
52
                private IAppConfig $appConfig,
53
                private IAppManager $appManager,
54
                private IAppDataFactory $appDataFactory,
55
                protected ITempManager $tempManager,
56
        ) {
57
                $this->instanceId = $this->config->getSystemValue('instanceid');
42✔
58
                $this->appData = $appDataFactory->get('libresign');
42✔
59
        }
60

61
        public function setArchitecture(string $architecture): self {
62
                $this->architecture = $architecture;
19✔
63
                return $this;
19✔
64
        }
65

66
        public function setResource(string $resource): self {
67
                $this->resource = $resource;
19✔
68
                return $this;
19✔
69
        }
70

71
        /**
72
         * @return list<string>
73
         */
74
        public function getArchitectures(): array {
75
                $appInfo = $this->appManager->getAppInfo(Application::APP_ID);
4✔
76
                if (empty($appInfo['dependencies']['architecture'])) {
4✔
77
                        throw new \Exception('dependencies>architecture not found at info.xml');
2✔
78
                }
79

80
                $architectures = $appInfo['dependencies']['architecture'];
2✔
81
                if (is_string($architectures)) {
2✔
NEW
82
                        return [$architectures];
×
83
                }
84
                if (is_array($architectures)) {
2✔
85
                        return array_values(array_map(static fn (mixed $architecture): string => (string)$architecture, $architectures));
2✔
86
                }
87

NEW
88
                throw new \Exception('dependencies>architecture has invalid format at info.xml');
×
89
        }
90

91
        public function setPrivateKey(PrivateKey $privateKey): void {
92
                $this->privateKey = $privateKey;
×
93
        }
94

95
        public function setCertificate(x509 $x509): void {
96
                $this->x509 = $x509;
×
97
        }
98

99
        public function willUseLocalCert(bool $willUseLocalCert): void {
100
                $this->willUseLocalCert = $willUseLocalCert;
×
101
        }
102

103
        private function getPrivateKey(): PrivateKey {
104
                if (!$this->privateKey instanceof PrivateKey) {
3✔
105
                        if (file_exists(__DIR__ . '/../../../build/tools/certificates/local/libresign.key')) {
×
106
                                $privateKey = file_get_contents(__DIR__ . '/../../../build/tools/certificates/local/libresign.key');
×
107
                                $this->privateKey = PublicKeyLoader::loadPrivateKey($privateKey);
×
108
                        } else {
109
                                $this->getDevelopCert();
×
110
                        }
111
                }
112
                if (!$this->privateKey instanceof PrivateKey) {
3✔
113
                        throw new LibresignException('Private key not found');
×
114
                }
115
                return $this->privateKey;
3✔
116
        }
117

118
        private function getCertificate(): X509 {
119
                if (!$this->x509 instanceof x509) {
3✔
120
                        if (file_exists(__DIR__ . '/../../../build/tools/certificates/local/libresign.crt')) {
×
121
                                $x509 = file_get_contents(__DIR__ . '/../../../build/tools/certificates/local/libresign.crt');
×
122
                                $this->x509 = new X509();
×
123
                                $this->x509->loadX509($x509);
×
124
                                $this->x509->setPrivateKey($this->getPrivateKey());
×
125
                        } else {
126
                                $this->getDevelopCert();
×
127
                        }
128
                }
129
                if (!$this->x509 instanceof x509) {
3✔
130
                        throw new LibresignException('Certificate not found');
×
131
                }
132
                return $this->x509;
3✔
133
        }
134

135
        /**
136
         * Write the signature of the app in the specified folder
137
         *
138
         * @param string $path
139
         * @param X509 $certificate
140
         * @param RSA $privateKey
141
         * @throws \Exception
142
         */
143
        public function writeAppSignature() {
144
                try {
145
                        $iterator = $this->getFolderIterator($this->getInstallPath());
3✔
146
                        $hashes = $this->generateHashes($iterator);
3✔
147
                        $signature = $this->createSignatureData($hashes);
3✔
148
                        $this->fileAccessHelper->file_put_contents(
3✔
149
                                $this->getFileName(),
3✔
150
                                json_encode($signature, JSON_PRETTY_PRINT)
3✔
151
                        );
3✔
152
                } catch (NotFoundException $e) {
×
153
                        throw new \Exception(sprintf(
×
154
                                "Folder %s not found.\nIs necessary to run this command first: occ libresign:install --%s --architecture=%s",
×
155
                                $e->getMessage(),
×
156
                                $this->resource,
×
157
                                $this->architecture,
×
158
                        ));
×
159
                } catch (\Exception $e) {
×
160
                        $appInfoDir = $this->getAppInfoDirectory();
×
161
                        if (!$this->fileAccessHelper->is_writable($appInfoDir)) {
×
162
                                throw new \Exception($appInfoDir . ' is not writable. Original error: ' . $e->getMessage());
×
163
                        }
164
                        throw $e;
×
165
                }
166
        }
167

168
        public function getInstallPath(): string {
169
                switch ($this->resource) {
19✔
170
                        case 'java':
19✔
171
                                $path = $this->appConfig->getValueString(Application::APP_ID, 'java_path');
7✔
172
                                if (!$path) {
7✔
173
                                        // fallback
174
                                        try {
175
                                                $folder = $this->appData->getFolder('/');
×
176
                                                $path = $this->architecture . '/' . $this->getLinuxDistributionToDownloadJava() . '/java';
×
177
                                                $folder = $folder->getFolder($path, $folder);
×
178
                                                $path = $this->getDataDir() . '/' . $this->getInternalPathOfFolder($folder);
×
179
                                                if (is_dir($path)) {
×
180
                                                        return $path;
×
181
                                                }
182
                                                throw new InvalidSignatureException('Java path not found at app config.');
×
183
                                        } catch (\Throwable) {
×
184
                                                throw new InvalidSignatureException('Java path not found at app config.');
×
185
                                        }
186
                                }
187
                                $installPath = substr($path, 0, -strlen('/bin/java'));
7✔
188
                                $distro = $this->getLinuxDistributionToDownloadJava();
7✔
189
                                $expected = "{$this->instanceId}/libresign/{$this->architecture}/{$distro}/java";
7✔
190
                                if (!str_contains($installPath, $expected)) {
7✔
191
                                        $installPath = preg_replace(
3✔
192
                                                "/{$this->instanceId}\/libresign\/(\w+)\/(\w+)\/java/i",
3✔
193
                                                $expected,
3✔
194
                                                $installPath
3✔
195
                                        );
3✔
196
                                }
197
                                break;
7✔
198
                        case 'jsignpdf':
12✔
199
                                $path = $this->appConfig->getValueString(Application::APP_ID, 'jsignpdf_jar_path');
4✔
200
                                if (!$path) {
4✔
201
                                        // fallback
202
                                        try {
203
                                                $folder = $this->appData->getFolder('/');
×
204
                                                $path = $this->architecture . '/jsignpdf';
×
205
                                                $folder = $folder->getFolder($path, $folder);
×
206
                                                $path = $this->getDataDir() . '/' . $this->getInternalPathOfFolder($folder);
×
207
                                                if (is_dir($path)) {
×
208
                                                        return $path;
×
209
                                                }
210
                                                throw new InvalidSignatureException('JSignPdf path not found at app config.');
×
211
                                        } catch (\Throwable) {
×
212
                                                throw new InvalidSignatureException('JSignPdf path not found at app config.');
×
213
                                        }
214
                                }
215
                                $installPath = substr($path, 0, strrpos($path, '/', -strlen('_/JSignPdf.jar')));
4✔
216
                                break;
4✔
217
                        case 'pdftk':
8✔
218
                                $path = $this->appConfig->getValueString(Application::APP_ID, 'pdftk_path');
4✔
219
                                if (!$path) {
4✔
220
                                        // fallback
221
                                        try {
222
                                                $folder = $this->appData->getFolder('/');
×
223
                                                $path = $this->architecture . '/pdftk';
×
224
                                                $folder = $folder->getFolder($path, $folder);
×
225
                                                $path = $this->getDataDir() . '/' . $this->getInternalPathOfFolder($folder);
×
226
                                                if (is_dir($path)) {
×
227
                                                        return $path;
×
228
                                                }
229
                                                throw new InvalidSignatureException('pdftk path not found at app config.');
×
230
                                        } catch (\Throwable) {
×
231
                                                throw new InvalidSignatureException('pdftk path not found at app config.');
×
232
                                        }
233
                                }
234
                                $installPath = substr($path, 0, -strlen('/pdftk.jar'));
4✔
235
                                break;
4✔
236
                        case 'cfssl':
4✔
237
                                $path = $this->appConfig->getValueString(Application::APP_ID, 'cfssl_bin');
4✔
238
                                if (!$path) {
4✔
239
                                        // fallback
240
                                        try {
241
                                                $folder = $this->appData->getFolder('/');
×
242
                                                $path = $this->architecture . '/cfssl';
×
243
                                                $folder = $folder->getFolder($path, $folder);
×
244
                                                $path = $this->getDataDir() . '/' . $this->getInternalPathOfFolder($folder);
×
245
                                                if (is_dir($path)) {
×
246
                                                        return $path;
×
247
                                                }
248
                                                throw new InvalidSignatureException('cfssl path not found at app config.');
×
249
                                        } catch (\Throwable) {
×
250
                                                throw new InvalidSignatureException('cfssl path not found at app config.');
×
251
                                        }
252
                                }
253
                                $installPath = substr($path, 0, -strlen('/cfssl'));
4✔
254
                                break;
4✔
255
                        default:
256
                                $installPath = '';
×
257
                }
258
                if (!str_contains((string)$installPath, $this->architecture)) {
19✔
259
                        $installPath = preg_replace(
6✔
260
                                "/{$this->instanceId}\/libresign\/(\w+)/i",
6✔
261
                                "{$this->instanceId}/libresign/{$this->architecture}",
6✔
262
                                (string)$installPath
6✔
263
                        );
6✔
264
                }
265
                return (string)$installPath;
19✔
266
        }
267

268
        private function getDataDir(): string {
269
                $dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/');
×
270
                return $dataDir;
×
271
        }
272

273
        /**
274
         * @todo check a best solution to don't use reflection
275
         */
276
        private function getInternalPathOfFolder(ISimpleFolder $node): string {
277
                $reflection = new \ReflectionClass($node);
×
278
                $reflectionProperty = $reflection->getProperty('folder');
×
279
                $folder = $reflectionProperty->getValue($node);
×
280
                $path = $folder->getInternalPath();
×
281
                return $path;
×
282
        }
283

284
        private function getFileName(): string {
285
                $appInfoDir = $this->getAppInfoDirectory();
3✔
286
                return $appInfoDir . '/' . $this->getSignatureFileName();
3✔
287
        }
288

289
        public function getSignatureFileName(): string {
290
                $path[] = 'install-' . $this->architecture;
3✔
291
                if ($this->resource === 'java') {
3✔
292
                        $path[] = $this->getLinuxDistributionToDownloadJava();
3✔
293
                }
294
                $path[] = $this->resource . '.json';
3✔
295
                return implode('-', $path);
3✔
296
        }
297

298
        public function setDistro(string $distro): self {
299
                $this->distro = $distro;
19✔
300
                return $this;
19✔
301
        }
302

303
        /**
304
         * Return linux or alpine-linux
305
         */
306
        public function getLinuxDistributionToDownloadJava(): string {
307
                if ($this->distro) {
7✔
308
                        return $this->distro;
7✔
309
                }
310
                $operatingSystem = new OperatingSystem();
×
311
                $distribution = $operatingSystem->getLinuxDistribution();
×
312
                if (strtolower($distribution) === 'alpine') {
×
313
                        return 'alpine-linux';
×
314
                }
315
                return 'linux';
×
316
        }
317

318
        protected function getAppInfoDirectory(): string {
319
                $appInfoDir = (string)realpath(__DIR__ . '/../../../appinfo');
×
320
                $this->fileAccessHelper->assertDirectoryExists($appInfoDir);
×
321
                return $appInfoDir;
×
322
        }
323

324
        /**
325
         * Split the certificate file in individual certs
326
         *
327
         * @param string $cert
328
         * @return string[]
329
         */
330
        private function splitCerts(string $cert): array {
331
                preg_match_all('([\-]{3,}[\S\ ]+?[\-]{3,}[\S\s]+?[\-]{3,}[\S\ ]+?[\-]{3,})', $cert, $matches);
×
332

333
                return $matches[0];
×
334
        }
335

336
        private function getSignatureData(): array {
337
                if (!empty($this->signatureData)) {
3✔
338
                        return $this->signatureData;
3✔
339
                }
340
                $filename = $this->getFileName();
3✔
341
                if (!file_exists($filename)) {
3✔
342
                        throw new SignatureDataNotFoundException('Signature data not found.');
×
343
                }
344
                $content = $this->fileAccessHelper->file_get_contents($filename);
3✔
345
                if (\is_string($content)) {
3✔
346
                        $signatureData = json_decode($content, true);
3✔
347
                } else {
348
                        $signatureData = null;
×
349
                }
350
                if (!\is_array($signatureData)) {
3✔
351
                        throw new SignatureDataNotFoundException('Signature data not found.');
×
352
                }
353
                $this->signatureData = $signatureData;
3✔
354

355
                $this->validateIfIssignedByLibresignAppCertificate($signatureData['hashes']);
3✔
356

357
                return $this->signatureData;
3✔
358
        }
359

360
        private function getHashesOfResource(): array {
361
                $signatureData = $this->getSignatureData();
3✔
362
                if (count($signatureData['hashes']) === 0) {
3✔
363
                        throw new EmptySignatureDataException('No signature files to ' . $this->resource);
×
364
                }
365
                return $signatureData;
3✔
366
        }
367

368
        private function getLibresignAppCertificate(): X509 {
369
                if ($this->x509 instanceof X509) {
3✔
370
                        return $this->x509;
3✔
371
                }
372
                $signatureData = $this->getSignatureData();
×
373
                $certificate = $signatureData['certificate'];
×
374

375
                // Check if certificate is signed by Nextcloud Root Authority
376
                $rootCertificatePublicKey = $this->getRootCertificatePublicKey();
×
377
                $this->x509 = new X509();
×
378

379
                $rootCerts = $this->splitCerts($rootCertificatePublicKey);
×
380
                foreach ($rootCerts as $rootCert) {
×
381
                        $this->x509->loadCA($rootCert);
×
382
                }
383
                $this->x509->loadX509($certificate);
×
384
                if (!$this->x509->validateSignature()) {
×
385
                        throw new InvalidSignatureException('Certificate is not valid.');
×
386
                }
387

388
                // Verify if certificate has proper CN. "core" CN is always trusted.
389
                if ($this->x509->getDN(X509::DN_OPENSSL)['CN'] !== Application::APP_ID && $this->x509->getDN(X509::DN_OPENSSL)['CN'] !== 'core') {
×
390
                        throw new InvalidSignatureException(
×
391
                                sprintf('Certificate is not valid for required scope. (Requested: %s, current: CN=%s)', Application::APP_ID, $this->x509->getDN(true)['CN'])
×
392
                        );
×
393
                }
394

395
                return $this->x509;
×
396
        }
397

398
        private function validateIfIssignedByLibresignAppCertificate(array $expectedHashes): void {
399
                $x509 = $this->getLibresignAppCertificate();
3✔
400

401
                // Check if the signature of the files is valid
402
                $rsa = $x509->getPublicKey()
3✔
403
                        ->withPadding(RSA::SIGNATURE_PSS);
3✔
404

405
                $signatureData = $this->getSignatureData();
3✔
406
                $signature = base64_decode((string)$signatureData['signature']);
3✔
407
                if (!$rsa->verify(json_encode($expectedHashes), $signature)) {
3✔
408
                        throw new InvalidSignatureException('Signature could not get verified.');
×
409
                }
410
        }
411

412
        public function verify(string $architecture, $resource): array {
413
                $this->signatureData = [];
3✔
414
                $this->architecture = $architecture;
3✔
415
                $this->resource = $resource;
3✔
416

417
                try {
418
                        $expectedHashes = $this->getHashesOfResource();
3✔
419
                        // Compare the list of files which are not identical
420
                        $currentInstanceHashes = $this->generateHashes($this->getFolderIterator($this->getInstallPath()));
3✔
421
                } catch (EmptySignatureDataException $th) {
×
422
                        return [
×
423
                                'EMPTY_SIGNATURE_DATA' => $th->getMessage(),
×
424
                        ];
×
425
                } catch (SignatureDataNotFoundException $th) {
×
426
                        return [
×
427
                                'SIGNATURE_DATA_NOT_FOUND' => $th->getMessage(),
×
428
                        ];
×
429
                } catch (\Throwable $th) {
×
430
                        return [
×
431
                                'HASH_FILE_ERROR' => $th->getMessage(),
×
432
                        ];
×
433
                }
434

435
                $differencesA = array_diff($expectedHashes['hashes'], $currentInstanceHashes);
3✔
436
                $differencesB = array_diff($currentInstanceHashes, $expectedHashes['hashes']);
3✔
437
                $differences = array_merge($differencesA, $differencesB);
3✔
438
                $differenceArray = [];
3✔
439
                foreach ($differences as $filename => $hash) {
3✔
440
                        // Check if file should not exist in the new signature table
441
                        if (!array_key_exists($filename, $expectedHashes['hashes'])) {
1✔
442
                                $differenceArray['EXTRA_FILE'][$filename]['expected'] = '';
1✔
443
                                $differenceArray['EXTRA_FILE'][$filename]['current'] = $hash;
1✔
444
                                continue;
1✔
445
                        }
446

447
                        // Check if file is missing
448
                        if (!array_key_exists($filename, $currentInstanceHashes)) {
1✔
449
                                $differenceArray['FILE_MISSING'][$filename]['expected'] = $expectedHashes['hashes'][$filename];
1✔
450
                                $differenceArray['FILE_MISSING'][$filename]['current'] = '';
1✔
451
                                continue;
1✔
452
                        }
453

454
                        // Check if hash does mismatch
455
                        if ($expectedHashes['hashes'][$filename] !== $currentInstanceHashes[$filename]) {
1✔
456
                                $differenceArray['INVALID_HASH'][$filename]['expected'] = $expectedHashes['hashes'][$filename];
1✔
457
                                $differenceArray['INVALID_HASH'][$filename]['current'] = $currentInstanceHashes[$filename];
1✔
458
                                continue;
1✔
459
                        }
460

461
                        // Should never happen.
462
                        throw new \Exception('Invalid behaviour in file hash comparison experienced. Please report this error to the developers.');
×
463
                }
464

465
                return $differenceArray;
3✔
466
        }
467

468
        /**
469
         * Enumerates all files belonging to the folder. Sensible defaults are excluded.
470
         *
471
         * @param string $folderToIterate
472
         * @param string $root
473
         * @return \RecursiveIteratorIterator
474
         * @throws \Exception
475
         */
476
        private function getFolderIterator(string $folderToIterate): \RecursiveIteratorIterator {
477
                if (!is_dir($folderToIterate)) {
3✔
478
                        throw new NotFoundException($folderToIterate);
×
479
                }
480
                $dirItr = new \RecursiveDirectoryIterator(
3✔
481
                        $folderToIterate,
3✔
482
                        \RecursiveDirectoryIterator::SKIP_DOTS
3✔
483
                );
3✔
484

485
                return new \RecursiveIteratorIterator(
3✔
486
                        $dirItr,
3✔
487
                        \RecursiveIteratorIterator::SELF_FIRST
3✔
488
                );
3✔
489
        }
490

491
        /**
492
         * Returns an array of ['filename' => 'SHA512-hash-of-file'] for all files found
493
         * in the iterator.
494
         *
495
         * @param \RecursiveIteratorIterator $iterator
496
         * @param string $path
497
         * @return array Array of hashes.
498
         */
499
        private function generateHashes(\RecursiveIteratorIterator $iterator): array {
500
                $hashes = [];
3✔
501

502
                $baseDirectoryLength = \strlen($this->getInstallPath());
3✔
503
                foreach ($iterator as $filename => $data) {
3✔
504
                        /** @var \DirectoryIterator $data */
505
                        if ($data->isDir()) {
3✔
506
                                continue;
×
507
                        }
508

509
                        $relativeFileName = substr((string)$filename, $baseDirectoryLength);
3✔
510
                        $relativeFileName = ltrim($relativeFileName, '/');
3✔
511

512
                        if ($this->isExcluded($relativeFileName)) {
3✔
513
                                continue;
×
514
                        }
515

516
                        $hashes[$relativeFileName] = hash_file('sha512', $filename);
3✔
517
                }
518

519
                return $hashes;
3✔
520
        }
521

522
        private function isExcluded(string $filename): bool {
523
                foreach ($this->exclude as $prefix) {
3✔
524
                        if (str_starts_with($filename, (string)$prefix)) {
3✔
525
                                return true;
×
526
                        }
527
                }
528
                return false;
3✔
529
        }
530

531
        /**
532
         * Creates the signature data
533
         *
534
         * @param array $hashes
535
         * @param X509 $certificate
536
         * @param RSA $privateKey
537
         * @return array
538
         */
539
        private function createSignatureData(array $hashes): array {
540
                ksort($hashes);
3✔
541

542
                $privateKey = $this->getPrivateKey()
3✔
543
                        ->withPadding(RSA::SIGNATURE_PSS);
3✔
544
                $signature = $privateKey->sign(json_encode($hashes));
3✔
545

546
                return [
3✔
547
                        'hashes' => $hashes,
3✔
548
                        'signature' => base64_encode((string)$signature),
3✔
549
                        'certificate' => $this->getCertificate()->saveX509($this->getCertificate()->getCurrentCert()),
3✔
550
                ];
3✔
551
        }
552

553
        private function getRootCertificatePublicKey(): string {
554
                if ($this->willUseLocalCert) {
×
555
                        $localCert = __DIR__ . '/../../../build/tools/certificates/local/root.crt';
×
556
                        if (file_exists($localCert)) {
×
557
                                return (string)file_get_contents($localCert);
×
558
                        }
559
                }
560
                return $this->fileAccessHelper->file_get_contents($this->environmentHelper->getServerRoot() . '/resources/codesigning/root.crt');
×
561
        }
562

563
        public function getDevelopCert(): array {
564
                $privateKey = openssl_pkey_new([
3✔
565
                        'private_key_bits' => 2048,
3✔
566
                        'private_key_type' => OPENSSL_KEYTYPE_RSA,
3✔
567
                ]);
3✔
568

569
                $csrNames = ['commonName' => 'libresign'];
3✔
570

571
                $csr = openssl_csr_new($csrNames, $privateKey, ['digest_alg' => 'sha256']);
3✔
572
                $x509 = openssl_csr_sign($csr, null, $privateKey, $days = 365, [
3✔
573
                        'digest_alg' => 'sha256',
3✔
574
                        'config' => $this->arrayToConfigFile([
3✔
575
                                'v3_user' => [
3✔
576
                                        'keyUsage' => 'digitalSignature',
3✔
577
                                        'extendedKeyUsage' => 'clientAuth',
3✔
578
                                        'authorityInfoAccess' => '@aia_section',
3✔
579
                                ],
3✔
580
                                'aia_section' => [
3✔
581
                                        'caIssuers;URI.0' => 'https://apps.nextcloud.com/apps/libresign',
3✔
582
                                ],
3✔
583
                        ]),
3✔
584
                ]);
3✔
585

586
                openssl_x509_export($x509, $rootCertificate);
3✔
587
                openssl_pkey_export($privateKey, $privateKeyCert);
3✔
588

589
                $this->privateKey = RSA::loadPrivateKey($privateKeyCert);
3✔
590
                $this->x509 = new X509();
3✔
591
                $this->x509->loadX509($rootCertificate);
3✔
592
                $this->x509->setPrivateKey($this->privateKey);
3✔
593

594
                $rootCertPath = __DIR__ . '/../../../build/tools/certificates/local/';
3✔
595
                if (!is_dir($rootCertPath)) {
3✔
596
                        mkdir($rootCertPath, 0777, true);
1✔
597
                }
598
                file_put_contents($rootCertPath . '/root.crt', $rootCertificate);
3✔
599
                file_put_contents($rootCertPath . '/libresign.crt', $rootCertificate);
3✔
600
                file_put_contents($rootCertPath . '/libresign.key', $privateKeyCert);
3✔
601

602
                $privateKeyInstance = openssl_pkey_new([
3✔
603
                        'private_key_bits' => 2048,
3✔
604
                        'private_key_type' => OPENSSL_KEYTYPE_RSA,
3✔
605
                ]);
3✔
606
                return [
3✔
607
                        'rootCertificate' => $rootCertificate,
3✔
608
                        'privateKeyInstance' => $privateKeyInstance,
3✔
609
                        'privateKeyCert' => $privateKeyCert,
3✔
610
                ];
3✔
611
        }
612

613
        private function arrayToConfigFile(array $config): string {
614
                $temporaryFile = $this->tempManager->getTemporaryFile('.cfg');
3✔
615
                if (!$temporaryFile) {
3✔
616
                        throw new LibresignException('Failure to create temporary file to OpenSSL .cfg file');
×
617
                }
618
                $ini = CertificateHelper::arrayToIni($config);
3✔
619
                file_put_contents($temporaryFile, $ini);
3✔
620
                return $temporaryFile;
3✔
621
        }
622
}
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