]> source.dussan.org Git - nextcloud-server.git/commitdiff
fix(ocm): simpler code
authorMaxence Lange <maxence@artificial-owl.com>
Thu, 21 Nov 2024 10:25:00 +0000 (09:25 -0100)
committerMaxence Lange <maxence@artificial-owl.com>
Thu, 21 Nov 2024 10:26:28 +0000 (09:26 -0100)
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
19 files changed:
apps/cloud_federation_api/lib/Controller/RequestHandlerController.php
lib/composer/composer/autoload_classmap.php
lib/composer/composer/autoload_static.php
lib/private/Federation/CloudFederationProviderManager.php
lib/private/OCM/OCMSignatoryManager.php
lib/private/Security/Signature/Model/IncomingSignedRequest.php
lib/private/Security/Signature/Model/OutgoingSignedRequest.php
lib/private/Security/Signature/Model/SignedRequest.php
lib/private/Security/Signature/SignatureManager.php
lib/private/Server.php
lib/unstable/Security/Signature/Exceptions/IncomingRequestNotFoundException.php [deleted file]
lib/unstable/Security/Signature/Exceptions/SignatureElementNotFoundException.php [new file with mode: 0644]
lib/unstable/Security/Signature/ISignatoryManager.php
lib/unstable/Security/Signature/ISignatureManager.php
lib/unstable/Security/Signature/Model/IIncomingSignedRequest.php
lib/unstable/Security/Signature/Model/IOutgoingSignedRequest.php
lib/unstable/Security/Signature/Model/ISignedRequest.php
lib/unstable/Security/Signature/Model/SignatoryStatus.php
version.php

index b1ab1be3f8849d8fd0384a4f3636ed74a53f79e3..e277b9b63896477e55e31e902c0576ff85b8bb7f 100644 (file)
@@ -336,8 +336,11 @@ class RequestHandlerController extends Controller {
         */
        private function getSignedRequest(): ?IIncomingSignedRequest {
                try {
-                       return $this->signatureManager->getIncomingSignedRequest($this->signatoryManager);
+                       $signedRequest = $this->signatureManager->getIncomingSignedRequest($this->signatoryManager);
+                       $this->logger->debug('signed request available', ['signedRequest' => $signedRequest]);
+                       return $signedRequest;
                } catch (SignatureNotFoundException|SignatoryNotFoundException $e) {
+                       $this->logger->debug('remote does not support signed request', ['exception' => $e]);
                        // remote does not support signed request.
                        // currently we still accept unsigned request until lazy appconfig
                        // core.enforce_signed_ocm_request is set to true (default: false)
@@ -346,7 +349,7 @@ class RequestHandlerController extends Controller {
                                throw new IncomingRequestException('Unsigned request');
                        }
                } catch (SignatureException $e) {
-                       $this->logger->notice('wrongly signed request', ['exception' => $e]);
+                       $this->logger->warning('wrongly signed request', ['exception' => $e]);
                        throw new IncomingRequestException('Invalid signature');
                }
                return null;
@@ -406,10 +409,17 @@ class RequestHandlerController extends Controller {
                $share = $provider->getShareByToken($token);
                try {
                        $this->confirmShareEntry($signedRequest, $share->getSharedWith());
-               } catch (IncomingRequestException) {
+               } catch (IncomingRequestException $e) {
                        // notification might come from the instance that owns the share
-                       $this->logger->debug('could not confirm origin on sharedWith (' . $share->getSharedWIth() . '); going with shareOwner (' . $share->getShareOwner() . ')');
-                       $this->confirmShareEntry($signedRequest, $share->getShareOwner());
+                       $this->logger->debug('could not confirm origin on sharedWith (' . $share->getSharedWIth() . '); going with shareOwner (' . $share->getShareOwner() . ')', ['exception' => $e]);
+                       try {
+                               $this->confirmShareEntry($signedRequest, $share->getShareOwner());
+                       } catch (IncomingRequestException $f) {
+                               // if both entry are failing, we log first exception as warning and second exception
+                               // will be logged as warning by the controller
+                               $this->logger->warning('could not confirm origin on sharedWith (' . $share->getSharedWIth() . '); going with shareOwner (' . $share->getShareOwner() . ')', ['exception' => $e]);
+                               throw $f;
+                       }
                }
        }
 
index 744f3b8b8c51ad94f085e2837f6efe9ac251e2ff..87e617ed4e6285e9f0e67ef432c6b4227b9eaf93 100644 (file)
@@ -12,6 +12,25 @@ return array(
     'NCU\\Config\\Exceptions\\UnknownKeyException' => $baseDir . '/lib/unstable/Config/Exceptions/UnknownKeyException.php',
     'NCU\\Config\\IUserConfig' => $baseDir . '/lib/unstable/Config/IUserConfig.php',
     'NCU\\Config\\ValueType' => $baseDir . '/lib/unstable/Config/ValueType.php',
+    'NCU\\Security\\Signature\\Exceptions\\IdentityNotFoundException' => $baseDir . '/lib/unstable/Security/Signature/Exceptions/IdentityNotFoundException.php',
+    'NCU\\Security\\Signature\\Exceptions\\IncomingRequestException' => $baseDir . '/lib/unstable/Security/Signature/Exceptions/IncomingRequestException.php',
+    'NCU\\Security\\Signature\\Exceptions\\InvalidKeyOriginException' => $baseDir . '/lib/unstable/Security/Signature/Exceptions/InvalidKeyOriginException.php',
+    'NCU\\Security\\Signature\\Exceptions\\InvalidSignatureException' => $baseDir . '/lib/unstable/Security/Signature/Exceptions/InvalidSignatureException.php',
+    'NCU\\Security\\Signature\\Exceptions\\SignatoryConflictException' => $baseDir . '/lib/unstable/Security/Signature/Exceptions/SignatoryConflictException.php',
+    'NCU\\Security\\Signature\\Exceptions\\SignatoryException' => $baseDir . '/lib/unstable/Security/Signature/Exceptions/SignatoryException.php',
+    'NCU\\Security\\Signature\\Exceptions\\SignatoryNotFoundException' => $baseDir . '/lib/unstable/Security/Signature/Exceptions/SignatoryNotFoundException.php',
+    'NCU\\Security\\Signature\\Exceptions\\SignatureElementNotFoundException' => $baseDir . '/lib/unstable/Security/Signature/Exceptions/SignatureElementNotFoundException.php',
+    'NCU\\Security\\Signature\\Exceptions\\SignatureException' => $baseDir . '/lib/unstable/Security/Signature/Exceptions/SignatureException.php',
+    'NCU\\Security\\Signature\\Exceptions\\SignatureNotFoundException' => $baseDir . '/lib/unstable/Security/Signature/Exceptions/SignatureNotFoundException.php',
+    'NCU\\Security\\Signature\\ISignatoryManager' => $baseDir . '/lib/unstable/Security/Signature/ISignatoryManager.php',
+    'NCU\\Security\\Signature\\ISignatureManager' => $baseDir . '/lib/unstable/Security/Signature/ISignatureManager.php',
+    'NCU\\Security\\Signature\\Model\\IIncomingSignedRequest' => $baseDir . '/lib/unstable/Security/Signature/Model/IIncomingSignedRequest.php',
+    'NCU\\Security\\Signature\\Model\\IOutgoingSignedRequest' => $baseDir . '/lib/unstable/Security/Signature/Model/IOutgoingSignedRequest.php',
+    'NCU\\Security\\Signature\\Model\\ISignatory' => $baseDir . '/lib/unstable/Security/Signature/Model/ISignatory.php',
+    'NCU\\Security\\Signature\\Model\\ISignedRequest' => $baseDir . '/lib/unstable/Security/Signature/Model/ISignedRequest.php',
+    'NCU\\Security\\Signature\\Model\\SignatoryStatus' => $baseDir . '/lib/unstable/Security/Signature/Model/SignatoryStatus.php',
+    'NCU\\Security\\Signature\\Model\\SignatoryType' => $baseDir . '/lib/unstable/Security/Signature/Model/SignatoryType.php',
+    'NCU\\Security\\Signature\\SignatureAlgorithm' => $baseDir . '/lib/unstable/Security/Signature/SignatureAlgorithm.php',
     'OCP\\Accounts\\IAccount' => $baseDir . '/lib/public/Accounts/IAccount.php',
     'OCP\\Accounts\\IAccountManager' => $baseDir . '/lib/public/Accounts/IAccountManager.php',
     'OCP\\Accounts\\IAccountProperty' => $baseDir . '/lib/public/Accounts/IAccountProperty.php',
@@ -1392,6 +1411,8 @@ return array(
     'OC\\Core\\Migrations\\Version30000Date20240814180800' => $baseDir . '/core/Migrations/Version30000Date20240814180800.php',
     'OC\\Core\\Migrations\\Version30000Date20240815080800' => $baseDir . '/core/Migrations/Version30000Date20240815080800.php',
     'OC\\Core\\Migrations\\Version30000Date20240906095113' => $baseDir . '/core/Migrations/Version30000Date20240906095113.php',
+    'OC\\Core\\Migrations\\Version31000Date20240101084401' => $baseDir . '/core/Migrations/Version31000Date20240101084401.php',
+    'OC\\Core\\Migrations\\Version31000Date20240814184402' => $baseDir . '/core/Migrations/Version31000Date20240814184402.php',
     'OC\\Core\\Migrations\\Version31000Date20241018063111' => $baseDir . '/core/Migrations/Version31000Date20241018063111.php',
     'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php',
     'OC\\Core\\ResponseDefinitions' => $baseDir . '/core/ResponseDefinitions.php',
index 70f9b3b3e6328a066ca5f5fea646935a8b0fcc0c..640924c6579ee9c60d13a909fd552532b81fdf50 100644 (file)
@@ -53,6 +53,25 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
         'NCU\\Config\\Exceptions\\UnknownKeyException' => __DIR__ . '/../../..' . '/lib/unstable/Config/Exceptions/UnknownKeyException.php',
         'NCU\\Config\\IUserConfig' => __DIR__ . '/../../..' . '/lib/unstable/Config/IUserConfig.php',
         'NCU\\Config\\ValueType' => __DIR__ . '/../../..' . '/lib/unstable/Config/ValueType.php',
+        'NCU\\Security\\Signature\\Exceptions\\IdentityNotFoundException' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Exceptions/IdentityNotFoundException.php',
+        'NCU\\Security\\Signature\\Exceptions\\IncomingRequestException' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Exceptions/IncomingRequestException.php',
+        'NCU\\Security\\Signature\\Exceptions\\InvalidKeyOriginException' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Exceptions/InvalidKeyOriginException.php',
+        'NCU\\Security\\Signature\\Exceptions\\InvalidSignatureException' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Exceptions/InvalidSignatureException.php',
+        'NCU\\Security\\Signature\\Exceptions\\SignatoryConflictException' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Exceptions/SignatoryConflictException.php',
+        'NCU\\Security\\Signature\\Exceptions\\SignatoryException' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Exceptions/SignatoryException.php',
+        'NCU\\Security\\Signature\\Exceptions\\SignatoryNotFoundException' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Exceptions/SignatoryNotFoundException.php',
+        'NCU\\Security\\Signature\\Exceptions\\SignatureElementNotFoundException' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Exceptions/SignatureElementNotFoundException.php',
+        'NCU\\Security\\Signature\\Exceptions\\SignatureException' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Exceptions/SignatureException.php',
+        'NCU\\Security\\Signature\\Exceptions\\SignatureNotFoundException' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Exceptions/SignatureNotFoundException.php',
+        'NCU\\Security\\Signature\\ISignatoryManager' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/ISignatoryManager.php',
+        'NCU\\Security\\Signature\\ISignatureManager' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/ISignatureManager.php',
+        'NCU\\Security\\Signature\\Model\\IIncomingSignedRequest' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Model/IIncomingSignedRequest.php',
+        'NCU\\Security\\Signature\\Model\\IOutgoingSignedRequest' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Model/IOutgoingSignedRequest.php',
+        'NCU\\Security\\Signature\\Model\\ISignatory' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Model/ISignatory.php',
+        'NCU\\Security\\Signature\\Model\\ISignedRequest' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Model/ISignedRequest.php',
+        'NCU\\Security\\Signature\\Model\\SignatoryStatus' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Model/SignatoryStatus.php',
+        'NCU\\Security\\Signature\\Model\\SignatoryType' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Model/SignatoryType.php',
+        'NCU\\Security\\Signature\\SignatureAlgorithm' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/SignatureAlgorithm.php',
         'OCP\\Accounts\\IAccount' => __DIR__ . '/../../..' . '/lib/public/Accounts/IAccount.php',
         'OCP\\Accounts\\IAccountManager' => __DIR__ . '/../../..' . '/lib/public/Accounts/IAccountManager.php',
         'OCP\\Accounts\\IAccountProperty' => __DIR__ . '/../../..' . '/lib/public/Accounts/IAccountProperty.php',
@@ -1433,6 +1452,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
         'OC\\Core\\Migrations\\Version30000Date20240814180800' => __DIR__ . '/../../..' . '/core/Migrations/Version30000Date20240814180800.php',
         'OC\\Core\\Migrations\\Version30000Date20240815080800' => __DIR__ . '/../../..' . '/core/Migrations/Version30000Date20240815080800.php',
         'OC\\Core\\Migrations\\Version30000Date20240906095113' => __DIR__ . '/../../..' . '/core/Migrations/Version30000Date20240906095113.php',
+        'OC\\Core\\Migrations\\Version31000Date20240101084401' => __DIR__ . '/../../..' . '/core/Migrations/Version31000Date20240101084401.php',
+        'OC\\Core\\Migrations\\Version31000Date20240814184402' => __DIR__ . '/../../..' . '/core/Migrations/Version31000Date20240814184402.php',
         'OC\\Core\\Migrations\\Version31000Date20241018063111' => __DIR__ . '/../../..' . '/core/Migrations/Version31000Date20241018063111.php',
         'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php',
         'OC\\Core\\ResponseDefinitions' => __DIR__ . '/../../..' . '/core/ResponseDefinitions.php',
index 74935ead40144ed6a066819cfa1d6eeb9a741cd1..e93542943510a73c9f42ea571bb7aa4659b94dba 100644 (file)
@@ -226,6 +226,12 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager
         */
        private function prepareOcmPayload(string $uri, string $payload): array {
                $payload = array_merge($this->getDefaultRequestOptions(), ['body' => $payload]);
+
+               if ($this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_ENFORCED, lazy: true) &&
+                       $this->signatoryManager->getRemoteSignatory($this->signatureManager->extractIdentityFromUri($uri)) === null) {
+                       return $payload;
+               }
+
                if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
                        $signedPayload = $this->signatureManager->signOutgoingRequestIClientPayload(
                                $this->signatoryManager,
index a90bb2c1f395ea725c7591533e1c6c25305e98ea..c7eb9ccda5aad8901f553d000c11a535a60f111e 100644 (file)
@@ -6,12 +6,12 @@ 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\Exceptions\IdentityNotFoundException;
 use NCU\Security\Signature\ISignatoryManager;
 use NCU\Security\Signature\ISignatureManager;
-use NCU\Security\Signature\Model\IIncomingSignedRequest;
 use NCU\Security\Signature\Model\ISignatory;
 use NCU\Security\Signature\Model\SignatoryType;
 use OC\Security\IdentityProof\Manager;
@@ -19,6 +19,7 @@ use OC\Security\Signature\Model\Signatory;
 use OCP\IAppConfig;
 use OCP\IURLGenerator;
 use OCP\OCM\Exceptions\OCMProviderException;
+use Psr\Log\LoggerInterface;
 
 /**
  * @inheritDoc
@@ -40,14 +41,15 @@ class OCMSignatoryManager implements ISignatoryManager {
                private readonly IURLGenerator $urlGenerator,
                private readonly Manager $identityProofManager,
                private readonly OCMDiscoveryService $ocmDiscoveryService,
+               private readonly LoggerInterface $logger,
        ) {
        }
 
        /**
         * @inheritDoc
         *
-        * @since 31.0.0
         * @return string
+        * @since 31.0.0
         */
        public function getProviderId(): string {
                return self::PROVIDER_ID;
@@ -56,8 +58,8 @@ class OCMSignatoryManager implements ISignatoryManager {
        /**
         * @inheritDoc
         *
-        * @since 31.0.0
         * @return array
+        * @since 31.0.0
         */
        public function getOptions(): array {
                return [];
@@ -121,14 +123,18 @@ class OCMSignatoryManager implements ISignatoryManager {
        /**
         * @inheritDoc
         *
-        * @param IIncomingSignedRequest $signedRequest
+        * @param string $remote
         *
         * @return ISignatory|null must be NULL if no signatory is found
-        * @throws OCMProviderException on fail to discover ocm services
         * @since 31.0.0
         */
-       public function getRemoteSignatory(IIncomingSignedRequest $signedRequest): ?ISignatory {
-               return $this->getRemoteSignatoryFromHost($signedRequest->getOrigin());
+       public function getRemoteSignatory(string $remote): ?ISignatory {
+               try {
+                       return $this->getRemoteSignatoryFromHost($remote);
+               } catch (OCMProviderException $e) {
+                       $this->logger->warning('fail to get remote signatory', ['exception' => $e, 'remote' => $remote]);
+                       return null;
+               }
        }
 
        /**
index 8fe83a7b09bd26e48d7adaf1582f9ab9a258aaee..77914d1e3b2dcceddd9804c171cc3a1114b451c0 100644 (file)
@@ -10,11 +10,14 @@ namespace OC\Security\Signature\Model;
 
 use JsonSerializable;
 use NCU\Security\Signature\Exceptions\IdentityNotFoundException;
-use NCU\Security\Signature\Exceptions\IncomingRequestNotFoundException;
+use NCU\Security\Signature\Exceptions\IncomingRequestException;
 use NCU\Security\Signature\Exceptions\SignatoryException;
+use NCU\Security\Signature\Exceptions\SignatureElementNotFoundException;
+use NCU\Security\Signature\Exceptions\SignatureNotFoundException;
 use NCU\Security\Signature\ISignatureManager;
 use NCU\Security\Signature\Model\IIncomingSignedRequest;
 use NCU\Security\Signature\Model\ISignatory;
+use OC\Security\Signature\SignatureManager;
 use OCP\IRequest;
 
 /**
@@ -26,77 +29,134 @@ use OCP\IRequest;
 class IncomingSignedRequest extends SignedRequest implements
        IIncomingSignedRequest,
        JsonSerializable {
-       private ?IRequest $request = null;
-       private int $time = 0;
        private string $origin = '';
-       private string $estimatedSignature = '';
 
        /**
-        * @inheritDoc
+        * @throws IncomingRequestException if incoming request is wrongly signed
+        * @throws SignatureNotFoundException if signature is not fully implemented
+        */
+       public function __construct(
+               string $body,
+               private readonly IRequest $request,
+               private readonly array $options = [],
+       ) {
+               parent::__construct($body);
+               $this->verifyHeadersFromRequest();
+               $this->extractSignatureHeaderFromRequest();
+       }
+
+       /**
+        * confirm that:
         *
-        * @param ISignatory $signatory
+        * - date is available in the header and its value is less than 5 minutes old
+        * - content-length is available and is the same as the payload size
+        * - digest is available and fit the checksum of the payload
         *
-        * @return $this
-        * @throws SignatoryException
-        * @throws IdentityNotFoundException
-        * @since 31.0.0
+        * @throws IncomingRequestException
+        * @throws SignatureNotFoundException
         */
-       public function setSignatory(ISignatory $signatory): self {
-               $identity = \OCP\Server::get(ISignatureManager::class)->extractIdentityFromUri($signatory->getKeyId());
-               if ($identity !== $this->getOrigin()) {
-                       throw new SignatoryException('keyId from provider is different from the one from signed request');
+       private function verifyHeadersFromRequest(): void {
+               // confirm presence of date, content-length, digest and Signature
+               $date = $this->getRequest()->getHeader('date');
+               if ($date === '') {
+                       throw new SignatureNotFoundException('missing date in header');
+               }
+               $contentLength = $this->getRequest()->getHeader('content-length');
+               if ($contentLength === '') {
+                       throw new SignatureNotFoundException('missing content-length in header');
+               }
+               $digest = $this->getRequest()->getHeader('digest');
+               if ($digest === '') {
+                       throw new SignatureNotFoundException('missing digest in header');
+               }
+               if ($this->getRequest()->getHeader('Signature') === '') {
+                       throw new SignatureNotFoundException('missing Signature in header');
                }
 
-               parent::setSignatory($signatory);
-               return $this;
+               // confirm date
+               try {
+                       $dTime = new \DateTime($date);
+                       $requestTime = $dTime->getTimestamp();
+               } catch (\Exception) {
+                       throw new IncomingRequestException('datetime exception');
+               }
+               if ($requestTime < (time() - ($this->options['ttl'] ?? SignatureManager::DATE_TTL))) {
+                       throw new IncomingRequestException('object is too old');
+               }
+
+               // confirm validity of content-length
+               if (strlen($this->getBody()) !== (int)$contentLength) {
+                       throw new IncomingRequestException('inexact content-length in header');
+               }
+
+               // confirm digest value, based on body
+               if ($digest !== $this->getDigest()) {
+                       throw new IncomingRequestException('invalid value for digest in header');
+               }
        }
 
        /**
-        * @inheritDoc
+        * extract data from the header entry 'Signature' and convert its content from string to an array
+        * also confirm that it contains the minimum mandatory information
         *
-        * @param IRequest $request
-        * @return IIncomingSignedRequest
-        * @since 31.0.0
+        * @throws IncomingRequestException
         */
-       public function setRequest(IRequest $request): IIncomingSignedRequest {
-               $this->request = $request;
-               return $this;
+       private function extractSignatureHeaderFromRequest(): void {
+               $sign = [];
+               foreach (explode(',', $this->getRequest()->getHeader('Signature')) as $entry) {
+                       if ($entry === '' || !strpos($entry, '=')) {
+                               continue;
+                       }
+
+                       [$k, $v] = explode('=', $entry, 2);
+                       preg_match('/"([^"]+)"/', $v, $var);
+                       if ($var[0] !== '') {
+                               $v = trim($var[0], '"');
+                       }
+                       $sign[$k] = $v;
+               }
+
+               $this->setSignatureElements($sign);
+
+               try {
+                       // confirm keys are in the Signature header
+                       $this->getSignatureElement('keyId');
+                       $this->getSignatureElement('headers');
+                       $this->setSignedSignature($this->getSignatureElement('signature'));
+               } catch (SignatureElementNotFoundException $e) {
+                       throw new IncomingRequestException($e->getMessage());
+               }
        }
 
        /**
         * @inheritDoc
         *
         * @return IRequest
-        * @throws IncomingRequestNotFoundException
         * @since 31.0.0
         */
        public function getRequest(): IRequest {
-               if ($this->request === null) {
-                       throw new IncomingRequestNotFoundException();
-               }
                return $this->request;
        }
 
        /**
         * @inheritDoc
         *
-        * @param int $time
-        * @return IIncomingSignedRequest
-        * @since 31.0.0
-        */
-       public function setTime(int $time): IIncomingSignedRequest {
-               $this->time = $time;
-               return $this;
-       }
-
-       /**
-        * @inheritDoc
+        * @param ISignatory $signatory
         *
-        * @return int
+        * @return $this
+        * @throws IdentityNotFoundException
+        * @throws IncomingRequestException
+        * @throws SignatoryException
         * @since 31.0.0
         */
-       public function getTime(): int {
-               return $this->time;
+       public function setSignatory(ISignatory $signatory): self {
+               $identity = \OCP\Server::get(ISignatureManager::class)->extractIdentityFromUri($signatory->getKeyId());
+               if ($identity !== $this->getOrigin()) {
+                       throw new SignatoryException('keyId from provider is different from the one from signed request');
+               }
+
+               parent::setSignatory($signatory);
+               return $this;
        }
 
        /**
@@ -115,9 +175,13 @@ class IncomingSignedRequest extends SignedRequest implements
         * @inheritDoc
         *
         * @return string
+        * @throws IncomingRequestException
         * @since 31.0.0
         */
        public function getOrigin(): string {
+               if ($this->origin === '') {
+                       throw new IncomingRequestException('empty origin');
+               }
                return $this->origin;
        }
 
@@ -126,44 +190,19 @@ class IncomingSignedRequest extends SignedRequest implements
         * keyId is a mandatory entry in the headers of a signed request.
         *
         * @return string
+        * @throws SignatureElementNotFoundException
         * @since 31.0.0
         */
        public function getKeyId(): string {
-               return $this->getSignatureHeader()['keyId'] ?? '';
-       }
-
-       /**
-        * @inheritDoc
-        *
-        * @param string $signature
-        * @return IIncomingSignedRequest
-        * @since 31.0.0
-        */
-       public function setEstimatedSignature(string $signature): IIncomingSignedRequest {
-               $this->estimatedSignature = $signature;
-               return $this;
-       }
-
-       /**
-        * @inheritDoc
-        *
-        * @return string
-        * @since 31.0.0
-        */
-       public function getEstimatedSignature(): string {
-               return $this->estimatedSignature;
+               return $this->getSignatureElement('keyId');
        }
 
        public function jsonSerialize(): array {
                return array_merge(
                        parent::jsonSerialize(),
                        [
-                               'body' => $this->getBody(),
-                               'time' => $this->getTime(),
-                               'incomingRequest' => $this->request ?? false,
-                               'origin' => $this->getOrigin(),
-                               'keyId' => $this->getKeyId(),
-                               'estimatedSignature' => $this->getEstimatedSignature(),
+                               'options' => $this->options,
+                               'origin' => $this->origin,
                        ]
                );
        }
index 04efcf8bfe188db41ea8d31d1ee330019deef80e..d2d5b95e7b64462cfc0e6f774f992d1f719d5305 100644 (file)
@@ -9,8 +9,11 @@ declare(strict_types=1);
 namespace OC\Security\Signature\Model;
 
 use JsonSerializable;
+use NCU\Security\Signature\ISignatoryManager;
 use NCU\Security\Signature\ISignatureManager;
 use NCU\Security\Signature\Model\IOutgoingSignedRequest;
+use NCU\Security\Signature\SignatureAlgorithm;
+use OC\Security\Signature\SignatureManager;
 
 /**
  * extends ISignedRequest to add info requested at the generation of the signature
@@ -23,8 +26,44 @@ class OutgoingSignedRequest extends SignedRequest implements
        JsonSerializable {
        private string $host = '';
        private array $headers = [];
-       private string $clearSignature = '';
-       private string $algorithm;
+       /** @var list<string> $headerList */
+       private array $headerList = [];
+       private SignatureAlgorithm $algorithm;
+       public function __construct(
+               string $body,
+               ISignatoryManager $signatoryManager,
+               private readonly string $identity,
+               private readonly string $method,
+               private readonly string $path,
+       ) {
+               parent::__construct($body);
+
+               $options = $signatoryManager->getOptions();
+               $this->setHost($identity)
+                       ->setAlgorithm(SignatureAlgorithm::from($options['algorithm'] ?? 'sha256'))
+                       ->setSignatory($signatoryManager->getLocalSignatory());
+
+               $headers = array_merge([
+                       '(request-target)' => strtolower($method) . ' ' . $path,
+                       'content-length' => strlen($this->getBody()),
+                       'date' => gmdate($options['dateHeader'] ?? SignatureManager::DATE_HEADER),
+                       'digest' => $this->getDigest(),
+                       'host' => $this->getHost()
+               ], $options['extraSignatureHeaders'] ?? []);
+
+               $signing = $headerList = [];
+               foreach ($headers as $element => $value) {
+                       $value = $headers[$element];
+                       $signing[] = $element . ': ' . $value;
+                       $headerList[] = $element;
+                       if ($element !== '(request-target)') {
+                               $this->addHeader($element, $value);
+                       }
+               }
+
+               $this->setHeaderList($headerList)
+                       ->setClearSignature(implode("\n", $signing));
+       }
 
        /**
         * @inheritDoc
@@ -52,12 +91,12 @@ class OutgoingSignedRequest extends SignedRequest implements
         * @inheritDoc
         *
         * @param string $key
-        * @param string|int|float|bool|array $value
+        * @param string|int|float $value
         *
         * @return IOutgoingSignedRequest
         * @since 31.0.0
         */
-       public function addHeader(string $key, string|int|float|bool|array $value): IOutgoingSignedRequest {
+       public function addHeader(string $key, string|int|float $value): IOutgoingSignedRequest {
                $this->headers[$key] = $value;
                return $this;
        }
@@ -73,37 +112,37 @@ class OutgoingSignedRequest extends SignedRequest implements
        }
 
        /**
-        * @inheritDoc
+        * set the ordered list of used headers in the Signature
         *
-        * @param string $estimated
+        * @param list<string> $list
         *
         * @return IOutgoingSignedRequest
         * @since 31.0.0
         */
-       public function setClearSignature(string $estimated): IOutgoingSignedRequest {
-               $this->clearSignature = $estimated;
+       public function setHeaderList(array $list): IOutgoingSignedRequest {
+               $this->headerList = $list;
                return $this;
        }
 
        /**
-        * @inheritDoc
+        * returns ordered list of used headers in the Signature
         *
-        * @return string
+        * @return list<string>
         * @since 31.0.0
         */
-       public function getClearSignature(): string {
-               return $this->clearSignature;
+       public function getHeaderList(): array {
+               return $this->headerList;
        }
 
        /**
         * @inheritDoc
         *
-        * @param string $algorithm
+        * @param SignatureAlgorithm $algorithm
         *
         * @return IOutgoingSignedRequest
         * @since 31.0.0
         */
-       public function setAlgorithm(string $algorithm): IOutgoingSignedRequest {
+       public function setAlgorithm(SignatureAlgorithm $algorithm): IOutgoingSignedRequest {
                $this->algorithm = $algorithm;
                return $this;
        }
@@ -111,10 +150,10 @@ class OutgoingSignedRequest extends SignedRequest implements
        /**
         * @inheritDoc
         *
-        * @return string
+        * @return SignatureAlgorithm
         * @since 31.0.0
         */
-       public function getAlgorithm(): string {
+       public function getAlgorithm(): SignatureAlgorithm {
                return $this->algorithm;
        }
 
@@ -122,9 +161,12 @@ class OutgoingSignedRequest extends SignedRequest implements
                return array_merge(
                        parent::jsonSerialize(),
                        [
+                               'host' => $this->host,
                                'headers' => $this->headers,
-                               'host' => $this->getHost(),
-                               'clearSignature' => $this->getClearSignature(),
+                               'algorithm' => $this->algorithm->value,
+                               'method' => $this->method,
+                               'identity' => $this->identity,
+                               'path' => $this->path,
                        ]
                );
        }
index 1587da9d6314909ec4e0bcb596b5dde8f717fa7a..56853ebade3c5fdbbc5b8205a138f4422317cf44 100644 (file)
@@ -10,6 +10,7 @@ namespace OC\Security\Signature\Model;
 
 use JsonSerializable;
 use NCU\Security\Signature\Exceptions\SignatoryNotFoundException;
+use NCU\Security\Signature\Exceptions\SignatureElementNotFoundException;
 use NCU\Security\Signature\Model\ISignatory;
 use NCU\Security\Signature\Model\ISignedRequest;
 
@@ -20,8 +21,9 @@ use NCU\Security\Signature\Model\ISignedRequest;
  */
 class SignedRequest implements ISignedRequest, JsonSerializable {
        private string $digest;
+       private array $signatureElements = [];
+       private string $clearSignature = '';
        private string $signedSignature = '';
-       private array $signatureHeader = [];
        private ?ISignatory $signatory = null;
 
        public function __construct(
@@ -54,12 +56,13 @@ class SignedRequest implements ISignedRequest, JsonSerializable {
        /**
         * @inheritDoc
         *
-        * @param array $signatureHeader
+        * @param array $elements
+        *
         * @return ISignedRequest
         * @since 31.0.0
         */
-       public function setSignatureHeader(array $signatureHeader): ISignedRequest {
-               $this->signatureHeader = $signatureHeader;
+       public function setSignatureElements(array $elements): ISignedRequest {
+               $this->signatureElements = $elements;
                return $this;
        }
 
@@ -69,8 +72,47 @@ class SignedRequest implements ISignedRequest, JsonSerializable {
         * @return array
         * @since 31.0.0
         */
-       public function getSignatureHeader(): array {
-               return $this->signatureHeader;
+       public function getSignatureElements(): array {
+               return $this->signatureElements;
+       }
+
+       /**
+        * @param string $key
+        *
+        * @return string
+        * @throws SignatureElementNotFoundException
+        * @since 31.0.0
+        *
+        */
+       public function getSignatureElement(string $key): string {
+               if (!array_key_exists($key, $this->signatureElements)) {
+                       throw new SignatureElementNotFoundException('missing element ' . $key . ' in Signature header');
+               }
+
+               return $this->signatureElements[$key];
+       }
+
+       /**
+        * @inheritDoc
+        *
+        * @param string $clearSignature
+        *
+        * @return ISignedRequest
+        * @since 31.0.0
+        */
+       public function setClearSignature(string $clearSignature): ISignedRequest {
+               $this->clearSignature = $clearSignature;
+               return $this;
+       }
+
+       /**
+        * @inheritDoc
+        *
+        * @return string
+        * @since 31.0.0
+        */
+       public function getClearSignature(): string {
+               return $this->clearSignature;
        }
 
        /**
@@ -134,9 +176,11 @@ class SignedRequest implements ISignedRequest, JsonSerializable {
 
        public function jsonSerialize(): array {
                return [
-                       'body' => $this->getBody(),
-                       'signatureHeader' => $this->getSignatureHeader(),
-                       'signedSignature' => $this->getSignedSignature(),
+                       'body' => $this->body,
+                       'digest' => $this->digest,
+                       'signatureElements' => $this->signatureElements,
+                       'clearSignature' => $this->clearSignature,
+                       'signedSignature' => $this->signedSignature,
                        'signatory' => $this->signatory ?? false,
                ];
        }
index 8717171f4b4159665c6374efd7d18efda657c495..2d895b465abebb81f3aee9bd295b051017ffc4b3 100644 (file)
@@ -1,7 +1,6 @@
 <?php
 
 declare(strict_types=1);
-
 /**
  * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
  * SPDX-License-Identifier: AGPL-3.0-or-later
@@ -16,6 +15,7 @@ use NCU\Security\Signature\Exceptions\InvalidSignatureException;
 use NCU\Security\Signature\Exceptions\SignatoryConflictException;
 use NCU\Security\Signature\Exceptions\SignatoryException;
 use NCU\Security\Signature\Exceptions\SignatoryNotFoundException;
+use NCU\Security\Signature\Exceptions\SignatureElementNotFoundException;
 use NCU\Security\Signature\Exceptions\SignatureException;
 use NCU\Security\Signature\Exceptions\SignatureNotFoundException;
 use NCU\Security\Signature\ISignatoryManager;
@@ -45,7 +45,7 @@ use Psr\Log\LoggerInterface;
  *     "date": "Mon, 08 Jul 2024 14:16:20 GMT",
  *     "digest": "SHA-256=U7gNVUQiixe5BRbp4Tg0xCZMTcSWXXUZI2\\/xtHM40S0=",
  *     "host": "hostname.of.the.recipient",
- *     "Signature": "keyId=\"https://author.hostname/key\",algorithm=\"ras-sha256\",headers=\"content-length
+ *     "Signature": "keyId=\"https://author.hostname/key\",algorithm=\"sha256\",headers=\"content-length
  * date digest host\",signature=\"DzN12OCS1rsA[...]o0VmxjQooRo6HHabg==\""
  * }
  *
@@ -66,11 +66,11 @@ use Psr\Log\LoggerInterface;
  * @since 31.0.0
  */
 class SignatureManager implements ISignatureManager {
-       private const DATE_HEADER = 'D, d M Y H:i:s T';
-       private const DATE_TTL = 300;
-       private const SIGNATORY_TTL = 86400 * 3;
-       private const TABLE_SIGNATORIES = 'sec_signatory';
-       private const BODY_MAXSIZE = 50000; // max size of the payload of the request
+       public const DATE_HEADER = 'D, d M Y H:i:s T';
+       public const DATE_TTL = 300;
+       public const SIGNATORY_TTL = 86400 * 3;
+       public const TABLE_SIGNATORIES = 'sec_signatory';
+       public const BODY_MAXSIZE = 50000; // max size of the payload of the request
        public const APPCONFIG_IDENTITY = 'security.signature.identity';
 
        public function __construct(
@@ -98,25 +98,29 @@ class SignatureManager implements ISignatureManager {
                ?string $body = null,
        ): IIncomingSignedRequest {
                $body = $body ?? file_get_contents('php://input');
-               if (strlen($body) > self::BODY_MAXSIZE) {
+               $options = $signatoryManager->getOptions();
+               if (strlen($body) > ($options['bodyMaxSize'] ?? self::BODY_MAXSIZE)) {
                        throw new IncomingRequestException('content of request is too big');
                }
 
-               $signedRequest = new IncomingSignedRequest($body);
-               $signedRequest->setRequest($this->request);
-               $options = $signatoryManager->getOptions();
+               // generate IncomingSignedRequest based on body and request
+               $signedRequest = new IncomingSignedRequest($body, $this->request, $options);
+               try {
+                       // we set origin based on the keyId defined in the Signature header of the request
+                       $signedRequest->setOrigin($this->extractIdentityFromUri($signedRequest->getSignatureElement('keyId')));
+               } catch (IdentityNotFoundException $e) {
+                       throw new IncomingRequestException($e->getMessage());
+               }
 
                try {
-                       $this->verifyIncomingRequestTime($signedRequest, $options['ttl'] ?? self::DATE_TTL);
-                       $this->verifyIncomingRequestContent($signedRequest);
-                       $this->prepIncomingSignatureHeader($signedRequest);
-                       $this->verifyIncomingSignatureHeader($signedRequest);
-                       $this->prepEstimatedSignature($signedRequest, $options['extraSignatureHeaders'] ?? []);
-                       $this->verifyIncomingRequestSignature($signedRequest, $signatoryManager, $options['ttlSignatory'] ?? self::SIGNATORY_TTL);
+                       // confirm the validity of content and identity of the incoming request
+                       $this->generateExpectedClearSignatureFromRequest($signedRequest, $options['extraSignatureHeaders'] ?? []);
+                       $this->confirmIncomingRequestSignature($signedRequest, $signatoryManager, $options['ttlSignatory'] ?? self::SIGNATORY_TTL);
                } catch (SignatureException $e) {
                        $this->logger->warning(
                                'signature could not be verified', [
-                                       'exception' => $e, 'signedRequest' => $signedRequest,
+                                       'exception' => $e,
+                                       'signedRequest' => $signedRequest,
                                        'signatoryManager' => get_class($signatoryManager)
                                ]
                        );
@@ -126,6 +130,95 @@ class SignatureManager implements ISignatureManager {
                return $signedRequest;
        }
 
+       /**
+        * generating the expected signature (clear version) sent by the remote instance
+        * based on the data available in the Signature header.
+        *
+        * @param IIncomingSignedRequest $signedRequest
+        * @param array $extraSignatureHeaders
+        *
+        * @throws SignatureException
+        */
+       private function generateExpectedClearSignatureFromRequest(
+               IIncomingSignedRequest $signedRequest,
+               array $extraSignatureHeaders = [],
+       ): void {
+               $request = $signedRequest->getRequest();
+               $usedHeaders = explode(' ', $signedRequest->getSignatureElement('headers'));
+               $neededHeaders = array_merge(['date', 'host', 'content-length', 'digest'], array_keys($extraSignatureHeaders));
+
+               $missingHeaders = array_diff($neededHeaders, $usedHeaders);
+               if ($missingHeaders !== []) {
+                       throw new SignatureException('missing entries in Signature.headers: ' . json_encode($missingHeaders));
+               }
+
+               $estimated = ['(request-target): ' . strtolower($request->getMethod()) . ' ' . $request->getRequestUri()];
+               foreach ($usedHeaders as $key) {
+                       if ($key === '(request-target)') {
+                               continue;
+                       }
+                       $value = (strtolower($key) === 'host') ? $request->getServerHost() : $request->getHeader($key);
+                       if ($value === '') {
+                               throw new SignatureException('missing header ' . $key . ' in request');
+                       }
+
+                       $estimated[] = $key . ': ' . $value;
+               }
+
+               $signedRequest->setClearSignature(implode("\n", $estimated));
+       }
+
+       /**
+        * confirm that the Signature is signed using the correct private key, using
+        * clear version of the Signature and the public key linked to the keyId
+        *
+        * @param IIncomingSignedRequest $signedRequest
+        * @param ISignatoryManager $signatoryManager
+        *
+        * @throws SignatoryNotFoundException
+        * @throws SignatureException
+        */
+       private function confirmIncomingRequestSignature(
+               IIncomingSignedRequest $signedRequest,
+               ISignatoryManager $signatoryManager,
+               int $ttlSignatory,
+       ): void {
+               $knownSignatory = null;
+               try {
+                       $knownSignatory = $this->getStoredSignatory($signedRequest->getKeyId());
+                       // refreshing ttl and compare with previous public key
+                       if ($ttlSignatory > 0 && $knownSignatory->getLastUpdated() < (time() - $ttlSignatory)) {
+                               $signatory = $this->getSaneRemoteSignatory($signatoryManager, $signedRequest);
+                               $this->updateSignatoryMetadata($signatory);
+                               $knownSignatory->setMetadata($signatory->getMetadata());
+                       }
+
+                       $signedRequest->setSignatory($knownSignatory);
+                       $this->verifySignedRequest($signedRequest);
+               } catch (InvalidKeyOriginException $e) {
+                       throw $e; // issue while requesting remote instance also means there is no 2nd try
+               } catch (SignatoryNotFoundException) {
+                       // if no signatory in cache, we retrieve the one from the remote instance (using
+                       // $signatoryManager), check its validity with current signature and store it
+                       $signatory = $this->getSaneRemoteSignatory($signatoryManager, $signedRequest);
+                       $signedRequest->setSignatory($signatory);
+                       $this->verifySignedRequest($signedRequest);
+                       $this->storeSignatory($signatory);
+               } catch (SignatureException) {
+                       // if public key (from cache) is not valid, we try to refresh it (based on SignatoryType)
+                       try {
+                               $signatory = $this->getSaneRemoteSignatory($signatoryManager, $signedRequest);
+                       } catch (SignatoryNotFoundException $e) {
+                               $this->manageDeprecatedSignatory($knownSignatory);
+                               throw $e;
+                       }
+
+                       $signedRequest->setSignatory($signatory);
+                       $this->verifySignedRequest($signedRequest);
+                       $this->storeSignatory($signatory);
+               }
+       }
+
        /**
         * @inheritDoc
         *
@@ -135,6 +228,9 @@ class SignatureManager implements ISignatureManager {
         * @param string $uri needed in the signature
         *
         * @return IOutgoingSignedRequest
+        * @throws IdentityNotFoundException
+        * @throws SignatoryException
+        * @throws SignatoryNotFoundException
         * @since 31.0.0
         */
        public function getOutgoingSignedRequest(
@@ -143,26 +239,43 @@ class SignatureManager implements ISignatureManager {
                string $method,
                string $uri,
        ): IOutgoingSignedRequest {
-               $signedRequest = new OutgoingSignedRequest($content);
-               $options = $signatoryManager->getOptions();
-
-               $signedRequest->setHost($this->getHostFromUri($uri))
-                       ->setAlgorithm($options['algorithm'] ?? 'sha256')
-                       ->setSignatory($signatoryManager->getLocalSignatory());
-
-               $this->setOutgoingSignatureHeader(
-                       $signedRequest,
-                       strtolower($method),
-                       parse_url($uri, PHP_URL_PATH) ?? '/',
-                       $options['dateHeader'] ?? self::DATE_HEADER
+               $signedRequest = new OutgoingSignedRequest(
+                       $content,
+                       $signatoryManager,
+                       $this->extractIdentityFromUri($uri),
+                       $method,
+                       parse_url($uri, PHP_URL_PATH) ?? '/'
                );
-               $this->setOutgoingClearSignature($signedRequest);
-               $this->setOutgoingSignedSignature($signedRequest);
-               $this->signingOutgoingRequest($signedRequest);
+
+               $this->signOutgoingRequest($signedRequest);
 
                return $signedRequest;
        }
 
+       /**
+        * signing clear version of the Signature header
+        *
+        * @param IOutgoingSignedRequest $signedRequest
+        *
+        * @throws SignatoryException
+        * @throws SignatoryNotFoundException
+        */
+       private function signOutgoingRequest(IOutgoingSignedRequest $signedRequest): void {
+               $clear = $signedRequest->getClearSignature();
+               $signed = $this->signString($clear, $signedRequest->getSignatory()->getPrivateKey(), $signedRequest->getAlgorithm());
+
+               $signatory = $signedRequest->getSignatory();
+               $signatureElements = [
+                       'keyId="' . $signatory->getKeyId() . '"',
+                       'algorithm="' . $signedRequest->getAlgorithm()->value . '"',
+                       'headers="' . implode(' ', $signedRequest->getHeaderList()) . '"',
+                       'signature="' . $signed . '"'
+               ];
+
+               $signedRequest->setSignedSignature($signed);
+               $signedRequest->addHeader('Signature', implode(',', $signatureElements));
+       }
+
        /**
         * @inheritDoc
         *
@@ -267,292 +380,36 @@ class SignatureManager implements ISignatureManager {
        }
 
        /**
-        * using the requested 'date' entry from header to confirm request is not older than ttl
-        *
-        * @param IIncomingSignedRequest $signedRequest
-        * @param int $ttl
-        *
-        * @throws IncomingRequestException
-        * @throws SignatureNotFoundException
-        */
-       private function verifyIncomingRequestTime(IIncomingSignedRequest $signedRequest, int $ttl): void {
-               $request = $signedRequest->getRequest();
-               $date = $request->getHeader('date');
-               if ($date === '') {
-                       throw new SignatureNotFoundException('missing date in header');
-               }
-
-               try {
-                       $dTime = new \DateTime($date);
-                       $signedRequest->setTime($dTime->getTimestamp());
-               } catch (\Exception $e) {
-                       $this->logger->warning(
-                               'datetime exception', ['exception' => $e, 'header' => $request->getHeader('date')]
-                       );
-                       throw new IncomingRequestException('datetime exception');
-               }
-
-               if ($signedRequest->getTime() < (time() - $ttl)) {
-                       throw new IncomingRequestException('object is too old');
-               }
-       }
-
-
-       /**
-        * confirm the values of 'content-length' and 'digest' from header
-        * is related to request content
-        *
-        * @param IIncomingSignedRequest $signedRequest
-        *
-        * @throws IncomingRequestException
-        * @throws SignatureNotFoundException
-        */
-       private function verifyIncomingRequestContent(IIncomingSignedRequest $signedRequest): void {
-               $request = $signedRequest->getRequest();
-               $contentLength = $request->getHeader('content-length');
-               if ($contentLength === '') {
-                       throw new SignatureNotFoundException('missing content-length in header');
-               }
-
-               if (strlen($signedRequest->getBody()) !== (int)$request->getHeader('content-length')) {
-                       throw new IncomingRequestException(
-                               'inexact content-length in header: ' . strlen($signedRequest->getBody()) . ' vs '
-                               . (int)$request->getHeader('content-length')
-                       );
-               }
-
-               $digest = $request->getHeader('digest');
-               if ($digest === '') {
-                       throw new SignatureNotFoundException('missing digest in header');
-               }
-
-               if ($digest !== $signedRequest->getDigest()) {
-                       throw new IncomingRequestException('invalid value for digest in header');
-               }
-       }
-
-       /**
-        * preparing a clear version of the signature based on list of metadata from the
-        * Signature entry in header
-        *
-        * @param IIncomingSignedRequest $signedRequest
-        *
-        * @throws SignatureNotFoundException
-        */
-       private function prepIncomingSignatureHeader(IIncomingSignedRequest $signedRequest): void {
-               $sign = [];
-               $request = $signedRequest->getRequest();
-               $signature = $request->getHeader('Signature');
-               if ($signature === '') {
-                       throw new SignatureNotFoundException('missing Signature in header');
-               }
-
-               foreach (explode(',', $signature) as $entry) {
-                       if ($entry === '' || !strpos($entry, '=')) {
-                               continue;
-                       }
-
-                       [$k, $v] = explode('=', $entry, 2);
-                       preg_match('/"([^"]+)"/', $v, $var);
-                       if ($var[0] !== '') {
-                               $v = trim($var[0], '"');
-                       }
-                       $sign[$k] = $v;
-               }
-
-               $signedRequest->setSignatureHeader($sign);
-       }
-
-
-       /**
-        * @param IIncomingSignedRequest $signedRequest
-        *
-        * @throws IncomingRequestException
-        * @throws InvalidKeyOriginException
-        */
-       private function verifyIncomingSignatureHeader(IIncomingSignedRequest $signedRequest): void {
-               $data = $signedRequest->getSignatureHeader();
-               if (!array_key_exists('keyId', $data) || !array_key_exists('headers', $data)
-                       || !array_key_exists('signature', $data)) {
-                       throw new IncomingRequestException('missing keys in signature headers: ' . json_encode($data));
-               }
-
-               try {
-                       $signedRequest->setOrigin($this->getHostFromUri($data['keyId']));
-               } catch (\Exception) {
-                       throw new InvalidKeyOriginException('cannot retrieve origin from ' . $data['keyId']);
-               }
-
-               $signedRequest->setSignedSignature($data['signature']);
-       }
-
-
-       /**
-        * @param IIncomingSignedRequest $signedRequest
-        * @param array $extraSignatureHeaders
-        *
-        * @throws IncomingRequestException
-        */
-       private function prepEstimatedSignature(
-               IIncomingSignedRequest $signedRequest,
-               array $extraSignatureHeaders = [],
-       ): void {
-               $request = $signedRequest->getRequest();
-               $headers = explode(' ', $signedRequest->getSignatureHeader()['headers'] ?? []);
-
-               $enforceHeaders = array_merge(
-                       ['date', 'host', 'content-length', 'digest'],
-                       $extraSignatureHeaders
-               );
-
-               $missingHeaders = array_diff($enforceHeaders, $headers);
-               if ($missingHeaders !== []) {
-                       throw new IncomingRequestException(
-                               'missing elements in headers: ' . json_encode($missingHeaders)
-                       );
-               }
-
-               $target = strtolower($request->getMethod()) . ' ' . $request->getRequestUri();
-               $estimated = ['(request-target): ' . $target];
-
-               foreach ($headers as $key) {
-                       $value = $request->getHeader($key);
-                       if (strtolower($key) === 'host') {
-                               $value = $request->getServerHost();
-                       }
-                       if ($value === '') {
-                               throw new IncomingRequestException('empty elements in header ' . $key);
-                       }
-
-                       $estimated[] = $key . ': ' . $value;
-               }
-
-               $signedRequest->setEstimatedSignature(implode("\n", $estimated));
-       }
-
-
-       /**
-        * @param IIncomingSignedRequest $signedRequest
-        * @param ISignatoryManager $signatoryManager
+        * get remote signatory using the ISignatoryManager
+        * and confirm the validity of the keyId
         *
-        * @throws SignatoryNotFoundException
-        * @throws SignatureException
-        */
-       private function verifyIncomingRequestSignature(
-               IIncomingSignedRequest $signedRequest,
-               ISignatoryManager $signatoryManager,
-               int $ttlSignatory,
-       ): void {
-               $knownSignatory = null;
-               try {
-                       $knownSignatory = $this->getStoredSignatory($signedRequest->getKeyId());
-                       if ($ttlSignatory > 0 && $knownSignatory->getLastUpdated() < (time() - $ttlSignatory)) {
-                               $signatory = $this->getSafeRemoteSignatory($signatoryManager, $signedRequest);
-                               $this->updateSignatoryMetadata($signatory);
-                               $knownSignatory->setMetadata($signatory->getMetadata());
-                       }
-
-                       $signedRequest->setSignatory($knownSignatory);
-                       $this->verifySignedRequest($signedRequest);
-               } catch (InvalidKeyOriginException $e) {
-                       throw $e; // issue while requesting remote instance also means there is no 2nd try
-               } catch (SignatoryNotFoundException|SignatureException) {
-                       try {
-                               $signatory = $this->getSafeRemoteSignatory($signatoryManager, $signedRequest);
-                       } catch (SignatoryNotFoundException $e) {
-                               $this->manageDeprecatedSignatory($knownSignatory);
-                               throw $e;
-                       }
-
-                       $signedRequest->setSignatory($signatory);
-                       $this->storeSignatory($signatory);
-                       $this->verifySignedRequest($signedRequest);
-               }
-       }
-
-
-       /**
         * @param ISignatoryManager $signatoryManager
         * @param IIncomingSignedRequest $signedRequest
         *
         * @return ISignatory
         * @throws InvalidKeyOriginException
         * @throws SignatoryNotFoundException
+        * @see ISignatoryManager::getRemoteSignatory
         */
-       private function getSafeRemoteSignatory(
+       private function getSaneRemoteSignatory(
                ISignatoryManager $signatoryManager,
                IIncomingSignedRequest $signedRequest,
        ): ISignatory {
-               $signatory = $signatoryManager->getRemoteSignatory($signedRequest);
+               $signatory = $signatoryManager->getRemoteSignatory($signedRequest->getOrigin());
                if ($signatory === null) {
                        throw new SignatoryNotFoundException('empty result from getRemoteSignatory');
                }
-               if ($signatory->getKeyId() !== $signedRequest->getKeyId()) {
-                       throw new InvalidKeyOriginException('keyId from signatory not related to the one from request');
-               }
-
-               return $signatory->setProviderId($signatoryManager->getProviderId());
-       }
-
-       private function setOutgoingSignatureHeader(
-               IOutgoingSignedRequest $signedRequest,
-               string $method,
-               string $path,
-               string $dateHeader,
-       ): void {
-               $header = [
-                       '(request-target)' => $method . ' ' . $path,
-                       'content-length' => strlen($signedRequest->getBody()),
-                       'date' => gmdate($dateHeader),
-                       'digest' => $signedRequest->getDigest(),
-                       'host' => $signedRequest->getHost()
-               ];
-
-               $signedRequest->setSignatureHeader($header);
-       }
-
-
-       /**
-        * @param IOutgoingSignedRequest $signedRequest
-        */
-       private function setOutgoingClearSignature(IOutgoingSignedRequest $signedRequest): void {
-               $signing = [];
-               $header = $signedRequest->getSignatureHeader();
-               foreach (array_keys($header) as $element) {
-                       $value = $header[$element];
-                       $signing[] = $element . ': ' . $value;
-                       if ($element !== '(request-target)') {
-                               $signedRequest->addHeader($element, $value);
+               try {
+                       if ($signatory->getKeyId() !== $signedRequest->getKeyId()) {
+                               throw new InvalidKeyOriginException('keyId from signatory not related to the one from request');
                        }
+               } catch (SignatureElementNotFoundException) {
+                       throw new InvalidKeyOriginException('missing keyId');
                }
 
-               $signedRequest->setClearSignature(implode("\n", $signing));
-       }
-
-
-       private function setOutgoingSignedSignature(IOutgoingSignedRequest $signedRequest): void {
-               $clear = $signedRequest->getClearSignature();
-               $signed = $this->signString(
-                       $clear, $signedRequest->getSignatory()->getPrivateKey(), $signedRequest->getAlgorithm()
-               );
-               $signedRequest->setSignedSignature($signed);
-       }
-
-       private function signingOutgoingRequest(IOutgoingSignedRequest $signedRequest): void {
-               $signatureHeader = $signedRequest->getSignatureHeader();
-               $headers = array_diff(array_keys($signatureHeader), ['(request-target)']);
-               $signatory = $signedRequest->getSignatory();
-               $signatureElements = [
-                       'keyId="' . $signatory->getKeyId() . '"',
-                       'algorithm="' . $this->getChosenEncryption($signedRequest->getAlgorithm()) . '"',
-                       'headers="' . implode(' ', $headers) . '"',
-                       'signature="' . $signedRequest->getSignedSignature() . '"'
-               ];
-
-               $signedRequest->addHeader('Signature', implode(',', $signatureElements));
+               return $signatory->setProviderId($signatoryManager->getProviderId());
        }
 
-
        /**
         * @param IIncomingSignedRequest $signedRequest
         *
@@ -568,10 +425,10 @@ class SignatureManager implements ISignatureManager {
 
                try {
                        $this->verifyString(
-                               $signedRequest->getEstimatedSignature(),
+                               $signedRequest->getClearSignature(),
                                $signedRequest->getSignedSignature(),
                                $publicKey,
-                               $this->getUsedEncryption($signedRequest)
+                               SignatureAlgorithm::tryFrom($signedRequest->getSignatureElement('algorithm')) ?? SignatureAlgorithm::SHA256
                        );
                } catch (InvalidSignatureException $e) {
                        $this->logger->debug('signature issue', ['signed' => $signedRequest, 'exception' => $e]);
@@ -579,45 +436,20 @@ class SignatureManager implements ISignatureManager {
                }
        }
 
-
-       private function getUsedEncryption(IIncomingSignedRequest $signedRequest): SignatureAlgorithm {
-               $data = $signedRequest->getSignatureHeader();
-
-               return match ($data['algorithm']) {
-                       'rsa-sha512' => SignatureAlgorithm::SHA512,
-                       default => SignatureAlgorithm::SHA256,
-               };
-       }
-
-       private function getChosenEncryption(string $algorithm): string {
-               return match ($algorithm) {
-                       'sha512' => 'ras-sha512',
-                       default => 'ras-sha256',
-               };
-       }
-
-       public function getOpenSSLAlgo(string $algorithm): int {
-               return match ($algorithm) {
-                       'sha512' => OPENSSL_ALGO_SHA512,
-                       default => OPENSSL_ALGO_SHA256,
-               };
-       }
-
-
        /**
         * @param string $clear
         * @param string $privateKey
-        * @param string $algorithm
+        * @param SignatureAlgorithm $algorithm
         *
         * @return string
         * @throws SignatoryException
         */
-       private function signString(string $clear, string $privateKey, string $algorithm): string {
+       private function signString(string $clear, string $privateKey, SignatureAlgorithm $algorithm): string {
                if ($privateKey === '') {
                        throw new SignatoryException('empty private key');
                }
 
-               openssl_sign($clear, $signed, $privateKey, $this->getOpenSSLAlgo($algorithm));
+               openssl_sign($clear, $signed, $privateKey, $algorithm->value);
 
                return base64_encode($signed);
        }
@@ -626,19 +458,18 @@ class SignatureManager implements ISignatureManager {
         * @param string $clear
         * @param string $encoded
         * @param string $publicKey
-        * @param SignatureAlgorithm $algo
+        * @param SignatureAlgorithm $algorithm
         *
-        * @return void
         * @throws InvalidSignatureException
         */
        private function verifyString(
                string $clear,
                string $encoded,
                string $publicKey,
-               SignatureAlgorithm $algo = SignatureAlgorithm::SHA256,
+               SignatureAlgorithm $algorithm = SignatureAlgorithm::SHA256,
        ): void {
                $signed = base64_decode($encoded);
-               if (openssl_verify($clear, $signed, $publicKey, $algo->value) !== 1) {
+               if (openssl_verify($clear, $signed, $publicKey, $algorithm->value) !== 1) {
                        throw new InvalidSignatureException('signature issue');
                }
        }
@@ -692,11 +523,15 @@ class SignatureManager implements ISignatureManager {
                }
        }
 
+       /**
+        * @param ISignatory $signatory
+        * @throws DBException
+        */
        private function insertSignatory(ISignatory $signatory): void {
                $qb = $this->connection->getQueryBuilder();
                $qb->insert(self::TABLE_SIGNATORIES)
                        ->setValue('provider_id', $qb->createNamedParameter($signatory->getProviderId()))
-                       ->setValue('host', $qb->createNamedParameter($this->getHostFromUri($signatory->getKeyId())))
+                       ->setValue('host', $qb->createNamedParameter($this->extractIdentityFromUri($signatory->getKeyId())))
                        ->setValue('account', $qb->createNamedParameter($signatory->getAccount()))
                        ->setValue('key_id', $qb->createNamedParameter($signatory->getKeyId()))
                        ->setValue('key_id_sum', $qb->createNamedParameter($this->hashKeyId($signatory->getKeyId())))
@@ -755,12 +590,12 @@ class SignatureManager implements ISignatureManager {
 
                        case SignatoryType::REFRESHABLE:
                                // TODO: send notice to admin
-                               throw new SignatoryConflictException();
+                               throw new SignatoryConflictException(); // while it can be refreshed, it must exist
 
                        case SignatoryType::TRUSTED:
                        case SignatoryType::STATIC:
                                // TODO: send warning to admin
-                               throw new SignatoryConflictException();
+                               throw new SignatoryConflictException(); // no way.
                }
        }
 
@@ -796,27 +631,6 @@ class SignatureManager implements ISignatureManager {
                $qb->executeStatement();
        }
 
-
-       /**
-        * @param string $uri
-        *
-        * @return string
-        * @throws InvalidKeyOriginException
-        */
-       private function getHostFromUri(string $uri): string {
-               $host = parse_url($uri, PHP_URL_HOST);
-               $port = parse_url($uri, PHP_URL_PORT);
-               if ($port !== null && $port !== false) {
-                       $host .= ':' . $port;
-               }
-
-               if (is_string($host) && $host !== '') {
-                       return $host;
-               }
-
-               throw new \Exception('invalid/empty uri');
-       }
-
        private function hashKeyId(string $keyId): string {
                return hash('sha256', $keyId);
        }
index 2167bccec8982d27041afad1956d22d1b20fb7ca..a20c37732a76ee47aa241ade27c036b891df828c 100644 (file)
@@ -8,7 +8,6 @@ namespace OC;
 
 use bantu\IniGetWrapper\IniGetWrapper;
 use NCU\Config\IUserConfig;
-use NCU\Security\PublicPrivateKeyPairs\IKeyPairManager;
 use NCU\Security\Signature\ISignatureManager;
 use OC\Accounts\AccountManager;
 use OC\App\AppManager;
diff --git a/lib/unstable/Security/Signature/Exceptions/IncomingRequestNotFoundException.php b/lib/unstable/Security/Signature/Exceptions/IncomingRequestNotFoundException.php
deleted file mode 100644 (file)
index 1953af3..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-namespace NCU\Security\Signature\Exceptions;
-
-/**
- * @since 31.0.0
- * @experimental 31.0.0
- */
-class IncomingRequestNotFoundException extends SignatureException {
-}
diff --git a/lib/unstable/Security/Signature/Exceptions/SignatureElementNotFoundException.php b/lib/unstable/Security/Signature/Exceptions/SignatureElementNotFoundException.php
new file mode 100644 (file)
index 0000000..f40f794
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace NCU\Security\Signature\Exceptions;
+
+/**
+ * @since 31.0.0
+ * @experimental 31.0.0
+ */
+class SignatureElementNotFoundException extends SignatureException {
+}
index 825ccac1ce92e7e85fdb8ad9b6944b94fae9816e..19ba83a4206dd7cd1f43424b926334dd2eb318af 100644 (file)
@@ -8,7 +8,6 @@ declare(strict_types=1);
  */
 namespace NCU\Security\Signature;
 
-use NCU\Security\Signature\Model\IIncomingSignedRequest;
 use NCU\Security\Signature\Model\ISignatory;
 
 /**
@@ -34,6 +33,7 @@ interface ISignatoryManager {
        /**
         * options that might affect the way the whole process is handled:
         * [
+        *   'bodyMaxSize' => 10000,
         *   'ttl' => 300,
         *   'ttlSignatory' => 86400*3,
         *   'extraSignatureHeaders' => [],
@@ -62,10 +62,10 @@ interface ISignatoryManager {
         *
         * Used to confirm authenticity of incoming request.
         *
-        * @param IIncomingSignedRequest $signedRequest
+        * @param string $remote
         *
         * @return ISignatory|null must be NULL if no signatory is found
         * @since 31.0.0
         */
-       public function getRemoteSignatory(IIncomingSignedRequest $signedRequest): ?ISignatory;
+       public function getRemoteSignatory(string $remote): ?ISignatory;
 }
index cc0297224dc58b513da9e7b7595e4fbc80ba68ba..1969b970aa68d152c2dc5ef29aad677929fc585d 100644 (file)
@@ -28,7 +28,7 @@ use NCU\Security\Signature\Model\ISignatory;
  *     "date": "Mon, 08 Jul 2024 14:16:20 GMT",
  *     "digest": "SHA-256=U7gNVUQiixe5BRbp4Tg0xCZMTcSWXXUZI2\\/xtHM40S0=",
  *     "host": "hostname.of.the.recipient",
- *     "Signature": "keyId=\"https://author.hostname/key\",algorithm=\"ras-sha256\",headers=\"content-length date digest host\",signature=\"DzN12OCS1rsA[...]o0VmxjQooRo6HHabg==\""
+ *     "Signature": "keyId=\"https://author.hostname/key\",algorithm=\"sha256\",headers=\"content-length date digest host\",signature=\"DzN12OCS1rsA[...]o0VmxjQooRo6HHabg==\""
  * }
  *
  * 'content-length' is the total length of the data/content
index a6682eff33c3cd970337dba900805d2899cd1661..3e2ebb22a5f6c56ed3a63b903300a7310ff0b13d 100644 (file)
@@ -8,6 +8,7 @@ declare(strict_types=1);
  */
 namespace NCU\Security\Signature\Model;
 
+use NCU\Security\Signature\Exceptions\SignatureElementNotFoundException;
 use NCU\Security\Signature\ISignatureManager;
 use OCP\IRequest;
 
@@ -20,15 +21,6 @@ use OCP\IRequest;
  * @since 31.0.0
  */
 interface IIncomingSignedRequest extends ISignedRequest {
-       /**
-        * set the core IRequest that might be signed
-        *
-        * @param IRequest $request
-        * @return IIncomingSignedRequest
-        * @since 31.0.0
-        */
-       public function setRequest(IRequest $request): IIncomingSignedRequest;
-
        /**
         * returns the base IRequest
         *
@@ -37,23 +29,6 @@ interface IIncomingSignedRequest extends ISignedRequest {
         */
        public function getRequest(): IRequest;
 
-       /**
-        * set the time, extracted from the base request headers
-        *
-        * @param int $time
-        * @return IIncomingSignedRequest
-        * @since 31.0.0
-        */
-       public function setTime(int $time): IIncomingSignedRequest;
-
-       /**
-        * get the time, extracted from the base request headers
-        *
-        * @return int
-        * @since 31.0.0
-        */
-       public function getTime(): int;
-
        /**
         * set the hostname at the source of the request,
         * based on the keyId defined in the signature header.
@@ -78,28 +53,8 @@ interface IIncomingSignedRequest extends ISignedRequest {
         * keyId is a mandatory entry in the headers of a signed request.
         *
         * @return string
+        * @throws SignatureElementNotFoundException
         * @since 31.0.0
         */
        public function getKeyId(): string;
-
-       /**
-        * store a clear and estimated version of the signature, based on payload and headers.
-        * This clear version will be compared with the real signature using
-        * the public key of remote instance at the origin of the request.
-        *
-        * @param string $signature
-        * @return IIncomingSignedRequest
-        * @since 31.0.0
-        */
-       public function setEstimatedSignature(string $signature): IIncomingSignedRequest;
-
-       /**
-        * returns a clear and estimated version of the signature, based on payload and headers.
-        * This clear version will be compared with the real signature using
-        * the public key of remote instance at the origin of the request.
-        *
-        * @return string
-        * @since 31.0.0
-        */
-       public function getEstimatedSignature(): string;
 }
index b2ca221e1267985e720afcff40f527d253a266ac..3c9445af74576c083ef8142f3488111214defe12 100644 (file)
@@ -9,6 +9,7 @@ declare(strict_types=1);
 namespace NCU\Security\Signature\Model;
 
 use NCU\Security\Signature\ISignatureManager;
+use NCU\Security\Signature\SignatureAlgorithm;
 
 /**
  * extends ISignedRequest to add info requested at the generation of the signature
@@ -41,12 +42,12 @@ interface IOutgoingSignedRequest extends ISignedRequest {
         * add a key/value pair to the headers of the request
         *
         * @param string $key
-        * @param string|int|float|bool|array $value
+        * @param string|int|float $value
         *
         * @return IOutgoingSignedRequest
         * @since 31.0.0
         */
-       public function addHeader(string $key, string|int|float|bool|array $value): IOutgoingSignedRequest;
+       public function addHeader(string $key, string|int|float $value): IOutgoingSignedRequest;
 
        /**
         * returns list of headers value that will be added to the base request
@@ -57,38 +58,38 @@ interface IOutgoingSignedRequest extends ISignedRequest {
        public function getHeaders(): array;
 
        /**
-        * store a clear version of the signature
+        * set the ordered list of used headers in the Signature
         *
-        * @param string $estimated
+        * @param list<string> $list
         *
         * @return IOutgoingSignedRequest
         * @since 31.0.0
         */
-       public function setClearSignature(string $estimated): IOutgoingSignedRequest;
+       public function setHeaderList(array $list): IOutgoingSignedRequest;
 
        /**
-        * returns the clear version of the signature
+        * returns ordered list of used headers in the Signature
         *
-        * @return string
+        * @return list<string>
         * @since 31.0.0
         */
-       public function getClearSignature(): string;
+       public function getHeaderList(): array;
 
        /**
         * set algorithm to be used to sign the signature
         *
-        * @param string $algorithm
+        * @param SignatureAlgorithm $algorithm
         *
         * @return IOutgoingSignedRequest
         * @since 31.0.0
         */
-       public function setAlgorithm(string $algorithm): IOutgoingSignedRequest;
+       public function setAlgorithm(SignatureAlgorithm $algorithm): IOutgoingSignedRequest;
 
        /**
         * returns the algorithm set to sign the signature
         *
-        * @return string
+        * @return SignatureAlgorithm
         * @since 31.0.0
         */
-       public function getAlgorithm(): string;
+       public function getAlgorithm(): SignatureAlgorithm;
 }
index ebb0e1c5b5856dced845ac6fc581b20fc0966172..76c033970fe8411c34ff009d06be3173615dfd2e 100644 (file)
@@ -9,6 +9,7 @@ declare(strict_types=1);
 namespace NCU\Security\Signature\Model;
 
 use NCU\Security\Signature\Exceptions\SignatoryNotFoundException;
+use NCU\Security\Signature\Exceptions\SignatureElementNotFoundException;
 
 /**
  * model that store data related to a possible signature.
@@ -39,19 +40,47 @@ interface ISignedRequest {
        /**
         * set the list of headers related to the signature of the request
         *
-        * @param array $signatureHeader
+        * @param array $elements
+        *
         * @return ISignedRequest
         * @since 31.0.0
         */
-       public function setSignatureHeader(array $signatureHeader): ISignedRequest;
+       public function setSignatureElements(array $elements): ISignedRequest;
 
        /**
-        * get the list of headers related to the signature of the request
+        * get the list of elements in the Signature header of the request
         *
         * @return array
         * @since 31.0.0
         */
-       public function getSignatureHeader(): array;
+       public function getSignatureElements(): array;
+
+       /**
+        * @param string $key
+        *
+        * @return string
+        * @throws SignatureElementNotFoundException
+        * @since 31.0.0
+        */
+       public function getSignatureElement(string $key): string;
+
+       /**
+        * store a clear version of the signature
+        *
+        * @param string $clearSignature
+        *
+        * @return ISignedRequest
+        * @since 31.0.0
+        */
+       public function setClearSignature(string $clearSignature): ISignedRequest;
+
+       /**
+        * returns the clear version of the signature
+        *
+        * @return string
+        * @since 31.0.0
+        */
+       public function getClearSignature(): string;
 
        /**
         * set the signed version of the signature
index 1c28f6580e7d0d518067ee64383f965442724a00..4174102beaec5606196786c3d93035bfde2d1498 100644 (file)
@@ -12,7 +12,7 @@ namespace NCU\Security\Signature\Model;
  * current status of signatory. is it trustable or not ?
  *
  * - SYNCED = the remote instance is trustable.
- * - BROKEN = the remote instance does not use the same key pairs
+ * - BROKEN = the remote instance does not use the same key pairs than previously
  *
  * @experimental 31.0.0
  * @since 31.0.0
index 1b1c154f4a6fecbc057e12317a69df43e69bfc90..e87981f0ec244fc219316dededd5676286cab8b0 100644 (file)
@@ -9,7 +9,7 @@
 // between betas, final and RCs. This is _not_ the public version number. Reset minor/patch level
 // when updating major/minor version number.
 
-$OC_Version = [31, 0, 0, 5];
+$OC_Version = [31, 0, 0, 6];
 
 // The human-readable string
 $OC_VersionString = '31.0.0 dev';