aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/OCM/OCMSignatoryManager.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/OCM/OCMSignatoryManager.php')
-rw-r--r--lib/private/OCM/OCMSignatoryManager.php160
1 files changed, 160 insertions, 0 deletions
diff --git a/lib/private/OCM/OCMSignatoryManager.php b/lib/private/OCM/OCMSignatoryManager.php
new file mode 100644
index 00000000000..3b2cc595507
--- /dev/null
+++ b/lib/private/OCM/OCMSignatoryManager.php
@@ -0,0 +1,160 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\OCM;
+
+use NCU\Security\Signature\Enum\DigestAlgorithm;
+use NCU\Security\Signature\Enum\SignatoryType;
+use NCU\Security\Signature\Enum\SignatureAlgorithm;
+use NCU\Security\Signature\Exceptions\IdentityNotFoundException;
+use NCU\Security\Signature\ISignatoryManager;
+use NCU\Security\Signature\ISignatureManager;
+use NCU\Security\Signature\Model\Signatory;
+use OC\Security\IdentityProof\Manager;
+use OCP\IAppConfig;
+use OCP\IURLGenerator;
+use OCP\OCM\Exceptions\OCMProviderException;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @inheritDoc
+ *
+ * returns local signatory using IKeyPairManager
+ * extract optional signatory (keyId+public key) from ocm discovery service on remote instance
+ *
+ * @since 31.0.0
+ */
+class OCMSignatoryManager implements ISignatoryManager {
+ public const PROVIDER_ID = 'ocm';
+ public const APPCONFIG_SIGN_IDENTITY_EXTERNAL = 'ocm_signed_request_identity_external';
+ public const APPCONFIG_SIGN_DISABLED = 'ocm_signed_request_disabled';
+ public const APPCONFIG_SIGN_ENFORCED = 'ocm_signed_request_enforced';
+
+ public function __construct(
+ private readonly IAppConfig $appConfig,
+ private readonly ISignatureManager $signatureManager,
+ private readonly IURLGenerator $urlGenerator,
+ private readonly Manager $identityProofManager,
+ private readonly OCMDiscoveryService $ocmDiscoveryService,
+ private readonly LoggerInterface $logger,
+ ) {
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @return string
+ * @since 31.0.0
+ */
+ public function getProviderId(): string {
+ return self::PROVIDER_ID;
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @return array
+ * @since 31.0.0
+ */
+ public function getOptions(): array {
+ return [
+ 'algorithm' => SignatureAlgorithm::RSA_SHA512,
+ 'digestAlgorithm' => DigestAlgorithm::SHA512,
+ 'extraSignatureHeaders' => [],
+ 'ttl' => 300,
+ 'dateHeader' => 'D, d M Y H:i:s T',
+ 'ttlSignatory' => 86400 * 3,
+ 'bodyMaxSize' => 50000,
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @return Signatory
+ * @throws IdentityNotFoundException
+ * @since 31.0.0
+ */
+ public function getLocalSignatory(): Signatory {
+ /**
+ * TODO: manage multiple identity (external, internal, ...) to allow a limitation
+ * based on the requested interface (ie. only accept shares from globalscale)
+ */
+ if ($this->appConfig->hasKey('core', self::APPCONFIG_SIGN_IDENTITY_EXTERNAL, true)) {
+ $identity = $this->appConfig->getValueString('core', self::APPCONFIG_SIGN_IDENTITY_EXTERNAL, lazy: true);
+ $keyId = 'https://' . $identity . '/ocm#signature';
+ } else {
+ $keyId = $this->generateKeyId();
+ }
+
+ if (!$this->identityProofManager->hasAppKey('core', 'ocm_external')) {
+ $this->identityProofManager->generateAppKey('core', 'ocm_external', [
+ 'algorithm' => 'rsa',
+ 'private_key_bits' => 2048,
+ 'private_key_type' => OPENSSL_KEYTYPE_RSA,
+ ]);
+ }
+ $keyPair = $this->identityProofManager->getAppKey('core', 'ocm_external');
+
+ $signatory = new Signatory(true);
+ $signatory->setKeyId($keyId);
+ $signatory->setPublicKey($keyPair->getPublic());
+ $signatory->setPrivateKey($keyPair->getPrivate());
+ return $signatory;
+
+ }
+
+ /**
+ * - tries to generate a keyId using global configuration (from signature manager) if available
+ * - generate a keyId using the current route to ocm shares
+ *
+ * @return string
+ * @throws IdentityNotFoundException
+ */
+ private function generateKeyId(): string {
+ try {
+ return $this->signatureManager->generateKeyIdFromConfig('/ocm#signature');
+ } catch (IdentityNotFoundException) {
+ }
+
+ $url = $this->urlGenerator->linkToRouteAbsolute('cloud_federation_api.requesthandlercontroller.addShare');
+ $identity = $this->signatureManager->extractIdentityFromUri($url);
+
+ // catching possible subfolder to create a keyId like 'https://hostname/subfolder/ocm#signature
+ $path = parse_url($url, PHP_URL_PATH);
+ $pos = strpos($path, '/ocm/shares');
+ $sub = ($pos) ? substr($path, 0, $pos) : '';
+
+ return 'https://' . $identity . $sub . '/ocm#signature';
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $remote
+ *
+ * @return Signatory|null must be NULL if no signatory is found
+ * @since 31.0.0
+ */
+ public function getRemoteSignatory(string $remote): ?Signatory {
+ try {
+ $ocmProvider = $this->ocmDiscoveryService->discover($remote, true);
+ /**
+ * @experimental 31.0.0
+ * @psalm-suppress UndefinedInterfaceMethod
+ */
+ $signatory = $ocmProvider->getSignatory();
+ $signatory?->setSignatoryType(SignatoryType::TRUSTED);
+ return $signatory;
+ } catch (OCMProviderException $e) {
+ $this->logger->warning('fail to get remote signatory', ['exception' => $e, 'remote' => $remote]);
+ return null;
+ }
+ }
+}