libresign/lib/Handler/CertificateEngine/OpenSslHandler.php
Vitor Mattos 45cd3c374d
chore: Add SPDX header
Signed-off-by: Vitor Mattos <vitor@php.rio>
2024-05-14 12:32:04 -03:00

156 lines
5 KiB
PHP

<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2020-2024 LibreCode coop and contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Libresign\Handler\CertificateEngine;
use OCA\Libresign\Exception\LibresignException;
use OCA\Libresign\Helper\ConfigureCheckHelper;
use OCP\AppFramework\Services\IAppConfig;
use OCP\Files\AppData\IAppDataFactory;
use OCP\IConfig;
use OCP\IDateTimeFormatter;
use OCP\ITempManager;
/**
* Class FileMapper
*
* @package OCA\Libresign\Handler
*
* @method CfsslHandler setClient(Client $client)
*/
class OpenSslHandler extends AEngineHandler implements IEngineHandler {
public function __construct(
protected IConfig $config,
protected IAppConfig $appConfig,
protected IAppDataFactory $appDataFactory,
protected IDateTimeFormatter $dateTimeFormatter,
protected ITempManager $tempManager,
) {
parent::__construct($config, $appConfig, $appDataFactory, $dateTimeFormatter, $tempManager);
}
public function generateRootCert(
string $commonName,
array $names = [],
): string {
$privateKey = openssl_pkey_new([
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
]);
$csr = openssl_csr_new($this->getCsrNames(), $privateKey, ['digest_alg' => 'sha256']);
$x509 = openssl_csr_sign($csr, null, $privateKey, $days = 365 * 5, ['digest_alg' => 'sha256']);
openssl_csr_export($csr, $csrout);
openssl_x509_export($x509, $certout);
openssl_pkey_export($privateKey, $pkeyout);
$this->saveFile('ca.csr', $csrout);
$this->saveFile('ca.pem', $certout);
$this->saveFile('ca-key.pem', $pkeyout);
return $pkeyout;
}
public function generateCertificate(): string {
$configPath = $this->getConfigPath();
$rootCertificate = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca.pem');
$rootPrivateKey = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem');
if (empty($rootCertificate) || empty($rootPrivateKey)) {
throw new LibresignException('Invalid root certificate');
}
$privateKey = openssl_pkey_new([
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
]);
$temporaryFile = $this->tempManager->getTemporaryFile('.cfg');
// More information about x509v3: https://www.openssl.org/docs/manmaster/man5/x509v3_config.html
file_put_contents($temporaryFile, <<<CONFIG
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment, keyCertSign
extendedKeyUsage = clientAuth, emailProtection
subjectAltName = {$this->getSubjectAltNames()}
authorityKeyIdentifier = keyid
subjectKeyIdentifier = hash
# certificatePolicies = <policyOID> CPS: http://url/with/policy/informations.pdf
CONFIG);
$csr = openssl_csr_new($this->getCsrNames(), $privateKey);
$x509 = openssl_csr_sign($csr, $rootCertificate, $rootPrivateKey, 365, [
'config' => $temporaryFile,
// This will set "basicConstraints" to CA:FALSE, the default is CA:TRUE
// The signer certificate is not a Certificate Authority
'x509_extensions' => 'v3_req',
]);
return parent::exportToPkcs12($x509, $privateKey);
}
private function getSubjectAltNames(): string {
$hosts = $this->getHosts();
$altNames = [];
foreach ($hosts as $email) {
$altNames[] = 'email:' . $email;
}
return implode(', ', $altNames);
}
/**
* Convert to names as necessary to OpenSSL
*
* Read more here: https://www.php.net/manual/en/function.openssl-csr-new.php
*/
private function getCsrNames(): array {
$distinguishedNames = [];
$names = parent::getNames();
foreach ($names as $name => $value) {
if ($name === 'ST') {
$distinguishedNames['stateOrProvinceName'] = $value;
continue;
}
$longName = $this->translateToLong($name);
$longName = lcfirst($longName) . 'Name';
$distinguishedNames[$longName] = $value;
}
if ($this->getCommonName()) {
$distinguishedNames['commonName'] = $this->getCommonName();
}
return $distinguishedNames;
}
private function saveFile(string $filename, string $content): void {
$configPath = $this->getConfigPath();
$success = file_put_contents($configPath . DIRECTORY_SEPARATOR . $filename, $content);
if ($success === false) {
throw new LibresignException('Failure to save file. Check permission: ' . $configPath . DIRECTORY_SEPARATOR . $filename);
}
}
public function isSetupOk(): bool {
$ok = parent::isSetupOk();
if (!$ok) {
return false;
}
$configPath = $this->getConfigPath();
$certificate = file_exists($configPath . DIRECTORY_SEPARATOR . 'ca.pem');
$privateKey = file_exists($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem');
return $certificate && $privateKey;
}
public function configureCheck(): array {
if ($this->isSetupOk()) {
return [(new ConfigureCheckHelper())
->setSuccessMessage('Root certificate setup is working fine.')
->setResource('openssl-configure')];
}
return [(new ConfigureCheckHelper())
->setErrorMessage('OpenSSL (root certificate) not configured.')
->setResource('openssl-configure')
->setTip('Run occ libresign:configure:openssl --help')];
}
}