appData = $appDataFactory->get('libresign'); } protected function exportToPkcs12( OpenSSLCertificate|string $certificate, OpenSSLAsymmetricKey|OpenSSLCertificate|string $privateKey ): string { if (empty($certificate) || empty($privateKey)) { throw new EmptyCertificateException(); } $certContent = null; try { openssl_pkcs12_export( $certificate, $certContent, $privateKey, $this->getPassword(), ['friendly_name' => $this->getFriendlyName()], ); if (!$certContent) { throw new \Exception(); } } catch (\Throwable $th) { throw new LibresignException('Error while creating certificate file', 500); } return $certContent; } public function updatePassword(string $certificate, string $currentPrivateKey, string $newPrivateKey): string { if (empty($certificate) || empty($currentPrivateKey) || empty($newPrivateKey)) { throw new EmptyCertificateException(); } $certContent = $this->opensslPkcs12Read($certificate, $currentPrivateKey); $this->setPassword($newPrivateKey); $certContent = self::exportToPkcs12($certContent['cert'], $certContent['pkey']); return $certContent; } public function readCertificate(string $certificate, string $privateKey): array { if (empty($certificate) || empty($privateKey)) { throw new EmptyCertificateException(); } $certContent = $this->opensslPkcs12Read($certificate, $privateKey); $parsed = openssl_x509_parse(openssl_x509_read($certContent['cert'])); $return['name'] = $parsed['name']; $return['subject'] = $parsed['subject']; $return['issuer'] = $parsed['issuer']; $return['extensions'] = $parsed['extensions']; $return['validate'] = [ 'from' => $this->dateTimeFormatter->formatDateTime($parsed['validFrom_time_t']), 'to' => $this->dateTimeFormatter->formatDateTime($parsed['validTo_time_t']), ]; return $return; } public function opensslPkcs12Read(string &$certificate, string $privateKey): array { openssl_pkcs12_read($certificate, $certContent, $privateKey); if (!empty($certContent)) { return $certContent; } /** * Reference: * * https://github.com/php/php-src/issues/12128 * https://www.php.net/manual/en/function.openssl-pkcs12-read.php#128992 */ $msg = openssl_error_string(); if ($msg === 'error:0308010C:digital envelope routines::unsupported') { $tempPassword = $this->tempManager->getTemporaryFile(); $tempEncriptedOriginal = $this->tempManager->getTemporaryFile(); $tempEncriptedRepacked = $this->tempManager->getTemporaryFile(); $tempDecrypted = $this->tempManager->getTemporaryFile(); file_put_contents($tempPassword, $privateKey); file_put_contents($tempEncriptedOriginal, $certificate); shell_exec(<<tempManager->clean(); openssl_pkcs12_read($certificateRepacked, $certContent, $privateKey); if (!empty($certContent)) { $certificate = $certificateRepacked; return $certContent; } } throw new InvalidPasswordException(); } public function translateToLong($name): string { switch ($name) { case 'CN': return 'CommonName'; case 'C': return 'Country'; case 'ST': return 'State'; case 'L': return 'Locality'; case 'O': return 'Organization'; case 'OU': return 'OrganizationalUnit'; } return ''; } public function setEngine(string $engine): void { $this->appConfig->setAppValue('certificate_engine', $engine); $this->engine = $engine; } public function getEngine(): string { $this->engine = $this->appConfig->getAppValue('certificate_engine', 'openssl'); return $this->engine; } public function populateInstance(array $rootCert): self { if (empty($rootCert)) { $rootCert = $this->appConfig->getAppValue('root_cert'); $rootCert = json_decode($rootCert, true); } if (!$rootCert) { return $this; } if (!empty($rootCert['names'])) { foreach ($rootCert['names'] as $id => $customName) { $longCustomName = $this->translateToLong($id); $this->{'set' . ucfirst($longCustomName)}($customName['value']); } } if (!$this->getCommonName()) { $this->setCommonName($rootCert['commonName']); } return $this; } public function getConfigPath(): string { if ($this->configPath) { return $this->configPath; } $this->configPath = $this->appConfig->getAppValue('config_path'); if ($this->configPath && str_ends_with($this->configPath, $this->getName() . '_config')) { return $this->configPath; } try { $folder = $this->appData->getFolder($this->getName() . '_config'); if (!$folder->fileExists('/')) { throw new \Exception(); } } catch (\Throwable $th) { $folder = $this->appData->newFolder($this->getName() . '_config'); } $dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/'); $this->configPath = $dataDir . '/' . $this->getInternalPathOfFolder($folder); if (!is_dir($this->configPath)) { $currentFile = realpath(__DIR__); $owner = posix_getpwuid(fileowner($currentFile)); $fullCommand = 'mkdir -p "' . $this->configPath . '"'; if (posix_getuid() !== $owner['uid']) { $fullCommand = 'runuser -u ' . $owner['name'] . ' -- ' . $fullCommand; } exec($fullCommand); } return $this->configPath; } /** * @todo check a best solution to don't use reflection */ private function getInternalPathOfFolder(ISimpleFolder $node): string { $reflection = new \ReflectionClass($node); $reflectionProperty = $reflection->getProperty('folder'); $reflectionProperty->setAccessible(true); $folder = $reflectionProperty->getValue($node); $path = $folder->getInternalPath(); return $path; } public function setConfigPath(string $configPath): void { if (!$configPath) { $this->appConfig->deleteAppValue('config_path'); } else { $this->appConfig->setAppValue('config_path', $configPath); } $this->configPath = $configPath; } public function getName(): string { $reflect = new ReflectionClass($this); $className = $reflect->getShortName(); $name = strtolower(substr($className, 0, -7)); return $name; } protected function getNames(): array { $names = [ 'C' => $this->getCountry(), 'ST' => $this->getState(), 'L' => $this->getLocality(), 'O' => $this->getOrganization(), 'OU' => $this->getOrganizationalUnit(), ]; $names = array_filter($names, function ($v) { return !empty($v); }); return $names; } public function isSetupOk(): bool { return $this->appConfig->getAppValue('authkey') ? true : false; } public function toArray(): array { $return = [ 'configPath' => $this->getConfigPath(), 'rootCert' => [ 'commonName' => $this->getCommonName(), 'names' => [], ], ]; $names = $this->getNames(); foreach ($names as $name => $value) { $return['rootCert']['names'][] = [ 'id' => $name, 'value' => $value, ]; } return $return; } }