diff options
author | Maxence Lange <maxence@artificial-owl.com> | 2024-11-28 14:21:34 -0100 |
---|---|---|
committer | Maxence Lange <maxence@artificial-owl.com> | 2024-12-04 09:30:55 -0100 |
commit | 4b0662005582e7a502b0de8e5e7e52f1675f3809 (patch) | |
tree | b58732f585e5c33384f487e8067f59862771f198 | |
parent | 862a41111855314a9bf0d186ed02688386b70d73 (diff) | |
download | nextcloud-server-4b0662005582e7a502b0de8e5e7e52f1675f3809.tar.gz nextcloud-server-4b0662005582e7a502b0de8e5e7e52f1675f3809.zip |
feat(signatory): switch to qbmapper
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
23 files changed, 579 insertions, 665 deletions
diff --git a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php index e277b9b6389..a243d286c71 100644 --- a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php +++ b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php @@ -10,8 +10,8 @@ use NCU\Security\Signature\Exceptions\IncomingRequestException; use NCU\Security\Signature\Exceptions\SignatoryNotFoundException; use NCU\Security\Signature\Exceptions\SignatureException; use NCU\Security\Signature\Exceptions\SignatureNotFoundException; +use NCU\Security\Signature\IIncomingSignedRequest; use NCU\Security\Signature\ISignatureManager; -use NCU\Security\Signature\Model\IIncomingSignedRequest; use OC\OCM\OCMSignatoryManager; use OCA\CloudFederationAPI\Config; use OCA\CloudFederationAPI\ResponseDefinitions; @@ -373,7 +373,7 @@ class RequestHandlerController extends Controller { if ($signedRequest === null) { $instance = $this->getHostFromFederationId($value); try { - $this->signatureManager->searchSignatory($instance); + $this->signatureManager->getSignatory($instance); throw new IncomingRequestException('instance is supposed to sign its request'); } catch (SignatoryNotFoundException) { return; @@ -434,7 +434,7 @@ class RequestHandlerController extends Controller { $instance = $this->getHostFromFederationId($entry); if ($signedRequest === null) { try { - $this->signatureManager->searchSignatory($instance); + $this->signatureManager->getSignatory($instance); throw new IncomingRequestException('instance is supposed to sign its request'); } catch (SignatoryNotFoundException) { return; diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index bcffae9aff3..89e53c98602 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -12,6 +12,9 @@ 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\\Enum\\SignatoryStatus' => $baseDir . '/lib/unstable/Security/Signature/Enum/SignatoryStatus.php', + 'NCU\\Security\\Signature\\Enum\\SignatoryType' => $baseDir . '/lib/unstable/Security/Signature/Enum/SignatoryType.php', + 'NCU\\Security\\Signature\\Enum\\SignatureAlgorithm' => $baseDir . '/lib/unstable/Security/Signature/Enum/SignatureAlgorithm.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', @@ -22,15 +25,12 @@ return array( '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\\IIncomingSignedRequest' => $baseDir . '/lib/unstable/Security/Signature/IIncomingSignedRequest.php', + 'NCU\\Security\\Signature\\IOutgoingSignedRequest' => $baseDir . '/lib/unstable/Security/Signature/IOutgoingSignedRequest.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', + 'NCU\\Security\\Signature\\ISignedRequest' => $baseDir . '/lib/unstable/Security/Signature/ISignedRequest.php', + 'NCU\\Security\\Signature\\Model\\Signatory' => $baseDir . '/lib/unstable/Security/Signature/Model/Signatory.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', @@ -1930,9 +1930,9 @@ return array( 'OC\\Security\\RateLimiting\\Limiter' => $baseDir . '/lib/private/Security/RateLimiting/Limiter.php', 'OC\\Security\\RemoteHostValidator' => $baseDir . '/lib/private/Security/RemoteHostValidator.php', 'OC\\Security\\SecureRandom' => $baseDir . '/lib/private/Security/SecureRandom.php', + 'OC\\Security\\Signature\\Db\\SignatoryMapper' => $baseDir . '/lib/private/Security/Signature/Db/SignatoryMapper.php', 'OC\\Security\\Signature\\Model\\IncomingSignedRequest' => $baseDir . '/lib/private/Security/Signature/Model/IncomingSignedRequest.php', 'OC\\Security\\Signature\\Model\\OutgoingSignedRequest' => $baseDir . '/lib/private/Security/Signature/Model/OutgoingSignedRequest.php', - 'OC\\Security\\Signature\\Model\\Signatory' => $baseDir . '/lib/private/Security/Signature/Model/Signatory.php', 'OC\\Security\\Signature\\Model\\SignedRequest' => $baseDir . '/lib/private/Security/Signature/Model/SignedRequest.php', 'OC\\Security\\Signature\\SignatureManager' => $baseDir . '/lib/private/Security/Signature/SignatureManager.php', 'OC\\Security\\TrustedDomainHelper' => $baseDir . '/lib/private/Security/TrustedDomainHelper.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 8a5d2d3fee6..b868cd44213 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -53,6 +53,9 @@ 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\\Enum\\SignatoryStatus' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Enum/SignatoryStatus.php', + 'NCU\\Security\\Signature\\Enum\\SignatoryType' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Enum/SignatoryType.php', + 'NCU\\Security\\Signature\\Enum\\SignatureAlgorithm' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Enum/SignatureAlgorithm.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', @@ -63,15 +66,12 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 '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\\IIncomingSignedRequest' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/IIncomingSignedRequest.php', + 'NCU\\Security\\Signature\\IOutgoingSignedRequest' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/IOutgoingSignedRequest.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', + 'NCU\\Security\\Signature\\ISignedRequest' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/ISignedRequest.php', + 'NCU\\Security\\Signature\\Model\\Signatory' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Model/Signatory.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', @@ -1971,9 +1971,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Security\\RateLimiting\\Limiter' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Limiter.php', 'OC\\Security\\RemoteHostValidator' => __DIR__ . '/../../..' . '/lib/private/Security/RemoteHostValidator.php', 'OC\\Security\\SecureRandom' => __DIR__ . '/../../..' . '/lib/private/Security/SecureRandom.php', + 'OC\\Security\\Signature\\Db\\SignatoryMapper' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/Db/SignatoryMapper.php', 'OC\\Security\\Signature\\Model\\IncomingSignedRequest' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/Model/IncomingSignedRequest.php', 'OC\\Security\\Signature\\Model\\OutgoingSignedRequest' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/Model/OutgoingSignedRequest.php', - 'OC\\Security\\Signature\\Model\\Signatory' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/Model/Signatory.php', 'OC\\Security\\Signature\\Model\\SignedRequest' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/Model/SignedRequest.php', 'OC\\Security\\Signature\\SignatureManager' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/SignatureManager.php', 'OC\\Security\\TrustedDomainHelper' => __DIR__ . '/../../..' . '/lib/private/Security/TrustedDomainHelper.php', diff --git a/lib/private/OCM/Model/OCMProvider.php b/lib/private/OCM/Model/OCMProvider.php index 95ba83882f2..32068efe3eb 100644 --- a/lib/private/OCM/Model/OCMProvider.php +++ b/lib/private/OCM/Model/OCMProvider.php @@ -9,8 +9,7 @@ declare(strict_types=1); namespace OC\OCM\Model; -use NCU\Security\Signature\Model\ISignatory; -use OC\Security\Signature\Model\Signatory; +use NCU\Security\Signature\Model\Signatory; use OCP\EventDispatcher\IEventDispatcher; use OCP\OCM\Events\ResourceTypeRegisterEvent; use OCP\OCM\Exceptions\OCMArgumentException; @@ -27,7 +26,7 @@ class OCMProvider implements IOCMProvider { private string $endPoint = ''; /** @var IOCMResource[] */ private array $resourceTypes = []; - private ?ISignatory $signatory = null; + private ?Signatory $signatory = null; private bool $emittedEvent = false; public function __construct( @@ -154,11 +153,11 @@ class OCMProvider implements IOCMProvider { throw new OCMArgumentException('resource not found'); } - public function setSignatory(ISignatory $signatory): void { + public function setSignatory(Signatory $signatory): void { $this->signatory = $signatory; } - public function getSignatory(): ?ISignatory { + public function getSignatory(): ?Signatory { return $this->signatory; } @@ -209,7 +208,7 @@ class OCMProvider implements IOCMProvider { * enabled: bool, * apiVersion: '1.0-proposal1', * endPoint: string, - * publicKey: ISignatory|null, + * publicKey: Signatory|null, * resourceTypes: list<array{ * name: string, * shareTypes: list<string>, diff --git a/lib/private/OCM/OCMSignatoryManager.php b/lib/private/OCM/OCMSignatoryManager.php index c7eb9ccda5a..909952a6b37 100644 --- a/lib/private/OCM/OCMSignatoryManager.php +++ b/lib/private/OCM/OCMSignatoryManager.php @@ -9,13 +9,12 @@ declare(strict_types=1); namespace OC\OCM; +use NCU\Security\Signature\Enum\SignatoryType; use NCU\Security\Signature\Exceptions\IdentityNotFoundException; use NCU\Security\Signature\ISignatoryManager; use NCU\Security\Signature\ISignatureManager; -use NCU\Security\Signature\Model\ISignatory; -use NCU\Security\Signature\Model\SignatoryType; +use NCU\Security\Signature\Model\Signatory; use OC\Security\IdentityProof\Manager; -use OC\Security\Signature\Model\Signatory; use OCP\IAppConfig; use OCP\IURLGenerator; use OCP\OCM\Exceptions\OCMProviderException; @@ -68,11 +67,11 @@ class OCMSignatoryManager implements ISignatoryManager { /** * @inheritDoc * - * @return ISignatory + * @return Signatory * @throws IdentityNotFoundException * @since 31.0.0 */ - public function getLocalSignatory(): ISignatory { + 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) @@ -125,10 +124,10 @@ class OCMSignatoryManager implements ISignatoryManager { * * @param string $remote * - * @return ISignatory|null must be NULL if no signatory is found + * @return Signatory|null must be NULL if no signatory is found * @since 31.0.0 */ - public function getRemoteSignatory(string $remote): ?ISignatory { + public function getRemoteSignatory(string $remote): ?Signatory { try { return $this->getRemoteSignatoryFromHost($remote); } catch (OCMProviderException $e) { @@ -142,14 +141,14 @@ class OCMSignatoryManager implements ISignatoryManager { * * @param string $host * - * @return ISignatory|null + * @return Signatory|null * @throws OCMProviderException on fail to discover ocm services * @since 31.0.0 */ - public function getRemoteSignatoryFromHost(string $host): ?ISignatory { + public function getRemoteSignatoryFromHost(string $host): ?Signatory { $ocmProvider = $this->ocmDiscoveryService->discover($host, true); $signatory = $ocmProvider->getSignatory(); - - return $signatory?->setType(SignatoryType::TRUSTED); + $signatory?->setType(SignatoryType::TRUSTED); + return $signatory; } } diff --git a/lib/private/Security/IdentityProof/Manager.php b/lib/private/Security/IdentityProof/Manager.php index de0b3fe6bd1..935c18bb81d 100644 --- a/lib/private/Security/IdentityProof/Manager.php +++ b/lib/private/Security/IdentityProof/Manager.php @@ -133,8 +133,8 @@ class Manager { public function hasAppKey(string $app, string $name): bool { $id = $this->generateAppKeyId($app, $name); try { - $this->appData->getFolder($id); - return true; + $folder = $this->appData->getFolder($id); + return ($folder->fileExists('public') && $folder->fileExists('private')); } catch (NotFoundException) { return false; } @@ -151,11 +151,11 @@ class Manager { public function deleteAppKey(string $app, string $name): bool { try { $folder = $this->appData->getFolder($this->generateAppKeyId($app, $name)); + $folder->delete(); + return true; } catch (NotFoundException) { return false; } - $folder->delete(); - return true; } private function generateAppKeyId(string $app, string $name): string { diff --git a/lib/private/Security/Signature/Db/SignatoryMapper.php b/lib/private/Security/Signature/Db/SignatoryMapper.php new file mode 100644 index 00000000000..47b79320548 --- /dev/null +++ b/lib/private/Security/Signature/Db/SignatoryMapper.php @@ -0,0 +1,114 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OC\Security\Signature\Db; + +use NCU\Security\Signature\Exceptions\SignatoryNotFoundException; +use NCU\Security\Signature\Model\Signatory; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\QBMapper; +use OCP\DB\Exception; +use OCP\IDBConnection; + +/** + * @template-extends QBMapper<Signatory> + */ +class SignatoryMapper extends QBMapper { + public const TABLE = 'sec_signatory'; + + public function __construct( + IDBConnection $db, + ) { + parent::__construct($db, self::TABLE, Signatory::class); + } + + /** + * + */ + public function getByHost(string $host, string $account = ''): Signatory { + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from($this->getTableName()) + ->where($qb->expr()->eq('host', $qb->createNamedParameter($host))) + ->andWhere($qb->expr()->eq('account', $qb->createNamedParameter($account))); + + try { + return $this->findEntity($qb); + } catch (DoesNotExistException) { + throw new SignatoryNotFoundException('no signatory found'); + } + } + + /** + */ + public function getByKeyId(string $keyId): Signatory { + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from($this->getTableName()) + ->where($qb->expr()->eq('key_id_sum', $qb->createNamedParameter($this->hashKeyId($keyId)))); + + try { + return $this->findEntity($qb); + } catch (DoesNotExistException) { + throw new SignatoryNotFoundException('no signatory found'); + } + } + + /** + * @param string $keyId + * + * @return int + * @throws Exception + */ + public function deleteByKeyId(string $keyId): int { + $qb = $this->db->getQueryBuilder(); + $qb->delete($this->getTableName()) + ->where($qb->expr()->eq('key_id_sum', $qb->createNamedParameter($this->hashKeyId($keyId)))); + + return $qb->executeStatement(); + } + + /** + * @param Signatory $signatory + * + * @return int + */ + public function updateMetadata(Signatory $signatory): int { + $qb = $this->db->getQueryBuilder(); + $qb->update($this->getTableName()) + ->set('metadata', $qb->createNamedParameter(json_encode($signatory->getMetadata()))) + ->set('last_updated', $qb->createNamedParameter(time())); + $qb->where($qb->expr()->eq('key_id_sum', $qb->createNamedParameter($this->hashKeyId($signatory->getKeyId())))); + + return $qb->executeStatement(); + } + + /** + * @param Signatory $signator + */ + public function updatePublicKey(Signatory $signatory): int { + $qb = $this->db->getQueryBuilder(); + $qb->update($this->getTableName()) + ->set('signatory', $qb->createNamedParameter($signatory->getPublicKey())) + ->set('last_updated', $qb->createNamedParameter(time())); + $qb->where($qb->expr()->eq('key_id_sum', $qb->createNamedParameter($this->hashKeyId($signatory->getKeyId())))); + + return $qb->executeStatement(); + } + + /** + * returns a hash version for keyId for better index in the database + * + * @param string $keyId + * + * @return string + */ + private function hashKeyId(string $keyId): string { + return hash('sha256', $keyId); + } +} diff --git a/lib/private/Security/Signature/Model/IncomingSignedRequest.php b/lib/private/Security/Signature/Model/IncomingSignedRequest.php index 77914d1e3b2..fae8b897d5b 100644 --- a/lib/private/Security/Signature/Model/IncomingSignedRequest.php +++ b/lib/private/Security/Signature/Model/IncomingSignedRequest.php @@ -9,14 +9,18 @@ declare(strict_types=1); namespace OC\Security\Signature\Model; use JsonSerializable; +use NCU\Security\Signature\Enum\SignatureAlgorithm; use NCU\Security\Signature\Exceptions\IdentityNotFoundException; use NCU\Security\Signature\Exceptions\IncomingRequestException; +use NCU\Security\Signature\Exceptions\InvalidSignatureException; 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\IIncomingSignedRequest; use NCU\Security\Signature\ISignatureManager; -use NCU\Security\Signature\Model\IIncomingSignedRequest; -use NCU\Security\Signature\Model\ISignatory; +use NCU\Security\Signature\Model\Signatory; use OC\Security\Signature\SignatureManager; use OCP\IRequest; @@ -102,27 +106,27 @@ class IncomingSignedRequest extends SignedRequest implements * @throws IncomingRequestException */ private function extractSignatureHeaderFromRequest(): void { - $sign = []; + $details = []; foreach (explode(',', $this->getRequest()->getHeader('Signature')) as $entry) { if ($entry === '' || !strpos($entry, '=')) { continue; } [$k, $v] = explode('=', $entry, 2); - preg_match('/"([^"]+)"/', $v, $var); + preg_match('/^"([^"]+)"$/', $v, $var); if ($var[0] !== '') { $v = trim($var[0], '"'); } - $sign[$k] = $v; + $details[$k] = $v; } - $this->setSignatureElements($sign); + $this->setSigningElements($details); try { // confirm keys are in the Signature header - $this->getSignatureElement('keyId'); - $this->getSignatureElement('headers'); - $this->setSignedSignature($this->getSignatureElement('signature')); + $this->getSigningElement('keyId'); + $this->getSigningElement('headers'); + $this->setSignature($this->getSigningElement('signature')); } catch (SignatureElementNotFoundException $e) { throw new IncomingRequestException($e->getMessage()); } @@ -141,7 +145,7 @@ class IncomingSignedRequest extends SignedRequest implements /** * @inheritDoc * - * @param ISignatory $signatory + * @param Signatory $signatory * * @return $this * @throws IdentityNotFoundException @@ -149,7 +153,7 @@ class IncomingSignedRequest extends SignedRequest implements * @throws SignatoryException * @since 31.0.0 */ - public function setSignatory(ISignatory $signatory): self { + public function setSignatory(Signatory $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'); @@ -194,7 +198,31 @@ class IncomingSignedRequest extends SignedRequest implements * @since 31.0.0 */ public function getKeyId(): string { - return $this->getSignatureElement('keyId'); + return $this->getSigningElement('keyId'); + } + + /** + * @inheritDoc + * + * @throws SignatureException + * @throws SignatoryNotFoundException + * @since 31.0.0 + */ + public function verify(): void { + $publicKey = $this->getSignatory()->getPublicKey(); + if ($publicKey === '') { + throw new SignatoryNotFoundException('empty public key'); + } + + $algorithm = SignatureAlgorithm::tryFrom($this->getSigningElement('algorithm')) ?? SignatureAlgorithm::SHA256; + if (openssl_verify( + implode("\n", $this->getSignatureData()), + base64_decode($this->getSignature()), + $publicKey, + $algorithm->value + ) !== 1) { + throw new InvalidSignatureException('signature issue'); + } } public function jsonSerialize(): array { diff --git a/lib/private/Security/Signature/Model/OutgoingSignedRequest.php b/lib/private/Security/Signature/Model/OutgoingSignedRequest.php index d2d5b95e7b6..8879821a029 100644 --- a/lib/private/Security/Signature/Model/OutgoingSignedRequest.php +++ b/lib/private/Security/Signature/Model/OutgoingSignedRequest.php @@ -9,10 +9,12 @@ declare(strict_types=1); namespace OC\Security\Signature\Model; use JsonSerializable; +use NCU\Security\Signature\Enum\SignatureAlgorithm; +use NCU\Security\Signature\Exceptions\SignatoryException; +use NCU\Security\Signature\Exceptions\SignatoryNotFoundException; +use NCU\Security\Signature\IOutgoingSignedRequest; 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; /** @@ -53,7 +55,6 @@ class OutgoingSignedRequest extends SignedRequest implements $signing = $headerList = []; foreach ($headers as $element => $value) { - $value = $headers[$element]; $signing[] = $element . ': ' . $value; $headerList[] = $element; if ($element !== '(request-target)') { @@ -62,17 +63,17 @@ class OutgoingSignedRequest extends SignedRequest implements } $this->setHeaderList($headerList) - ->setClearSignature(implode("\n", $signing)); + ->setSignatureData($signing); } /** * @inheritDoc * * @param string $host - * @return IOutgoingSignedRequest + * @return $this * @since 31.0.0 */ - public function setHost(string $host): IOutgoingSignedRequest { + public function setHost(string $host): self { $this->host = $host; return $this; } @@ -93,10 +94,10 @@ class OutgoingSignedRequest extends SignedRequest implements * @param string $key * @param string|int|float $value * - * @return IOutgoingSignedRequest + * @return self * @since 31.0.0 */ - public function addHeader(string $key, string|int|float $value): IOutgoingSignedRequest { + public function addHeader(string $key, string|int|float $value): self { $this->headers[$key] = $value; return $this; } @@ -116,10 +117,10 @@ class OutgoingSignedRequest extends SignedRequest implements * * @param list<string> $list * - * @return IOutgoingSignedRequest + * @return self * @since 31.0.0 */ - public function setHeaderList(array $list): IOutgoingSignedRequest { + public function setHeaderList(array $list): self { $this->headerList = $list; return $this; } @@ -139,10 +140,10 @@ class OutgoingSignedRequest extends SignedRequest implements * * @param SignatureAlgorithm $algorithm * - * @return IOutgoingSignedRequest + * @return self * @since 31.0.0 */ - public function setAlgorithm(SignatureAlgorithm $algorithm): IOutgoingSignedRequest { + public function setAlgorithm(SignatureAlgorithm $algorithm): self { $this->algorithm = $algorithm; return $this; } @@ -157,6 +158,59 @@ class OutgoingSignedRequest extends SignedRequest implements return $this->algorithm; } + /** + * @inheritDoc + * + * @return self + * @throws SignatoryException + * @throws SignatoryNotFoundException + * @since 31.0.0 + */ + public function sign(): self { + $privateKey = $this->getSignatory()->getPrivateKey(); + if ($privateKey === '') { + throw new SignatoryException('empty private key'); + } + + openssl_sign( + implode("\n", $this->getSignatureData()), + $signed, + $privateKey, + $this->getAlgorithm()->value + ); + + $this->setSignature(base64_encode($signed)); + $this->setSigningElements( + [ + 'keyId="' . $this->getSignatory()->getKeyId() . '"', + 'algorithm="' . $this->getAlgorithm()->value . '"', + 'headers="' . implode(' ', $this->getHeaderList()) . '"', + 'signature="' . $this->getSignature() . '"' + ] + ); + $this->addHeader('Signature', implode(',', $this->getSigningElements())); + + return $this; + } + + /** + * @param string $clear + * @param string $privateKey + * @param SignatureAlgorithm $algorithm + * + * @return string + * @throws SignatoryException + */ + private function signString(string $clear, string $privateKey, SignatureAlgorithm $algorithm): string { + if ($privateKey === '') { + throw new SignatoryException('empty private key'); + } + + openssl_sign($clear, $signed, $privateKey, $algorithm->value); + + return base64_encode($signed); + } + public function jsonSerialize(): array { return array_merge( parent::jsonSerialize(), diff --git a/lib/private/Security/Signature/Model/Signatory.php b/lib/private/Security/Signature/Model/Signatory.php deleted file mode 100644 index b28d2c0415f..00000000000 --- a/lib/private/Security/Signature/Model/Signatory.php +++ /dev/null @@ -1,147 +0,0 @@ -<?php - -declare(strict_types=1); - -/** - * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -namespace OC\Security\Signature\Model; - -use JsonSerializable; -use NCU\Security\Signature\Model\ISignatory; -use NCU\Security\Signature\Model\SignatoryStatus; -use NCU\Security\Signature\Model\SignatoryType; - -class Signatory implements ISignatory, JsonSerializable { - private string $providerId = ''; - private string $account = ''; - private SignatoryType $type = SignatoryType::STATIC; - private SignatoryStatus $status = SignatoryStatus::SYNCED; - private array $metadata = []; - private int $creation = 0; - private int $lastUpdated = 0; - - public function __construct( - private string $keyId, - private readonly string $publicKey, - private readonly string $privateKey = '', - readonly bool $local = false, - ) { - // if set as local (for current instance), we apply some filters. - if ($local) { - // to avoid conflict with duplicate key pairs (ie generated url from the occ command), we enforce https as prefix - if (str_starts_with($keyId, 'http://')) { - $keyId = 'https://' . substr($keyId, 7); - } - - // removing /index.php from generated url - $path = parse_url($keyId, PHP_URL_PATH); - if (str_starts_with($path, '/index.php/')) { - $pos = strpos($keyId, '/index.php'); - if ($pos !== false) { - $keyId = substr_replace($keyId, '', $pos, 10); - } - } - - $this->keyId = $keyId; - } - } - - public function setProviderId(string $providerId): self { - $this->providerId = $providerId; - return $this; - } - - public function getProviderId(): string { - return $this->providerId; - } - - public function setAccount(string $account): self { - $this->account = $account; - return $this; - } - - public function getAccount(): string { - return $this->account; - } - - public function getKeyId(): string { - return $this->keyId; - } - - public function getPublicKey(): string { - return $this->publicKey; - } - - public function getPrivateKey(): string { - return $this->privateKey; - } - - public function setMetadata(array $metadata): self { - $this->metadata = $metadata; - return $this; - } - - public function getMetadata(): array { - return $this->metadata; - } - - public function setMetaValue(string $key, string|int $value): self { - $this->metadata[$key] = $value; - return $this; - } - - public function setType(SignatoryType $type): self { - $this->type = $type; - return $this; - } - public function getType(): SignatoryType { - return $this->type; - } - - public function setStatus(SignatoryStatus $status): self { - $this->status = $status; - return $this; - } - - public function getStatus(): SignatoryStatus { - return $this->status; - } - - public function setCreation(int $creation): self { - $this->creation = $creation; - return $this; - } - - public function getCreation(): int { - return $this->creation; - } - - public function setLastUpdated(int $lastUpdated): self { - $this->lastUpdated = $lastUpdated; - return $this; - } - - public function getLastUpdated(): int { - return $this->lastUpdated; - } - - public function importFromDatabase(array $row): self { - $this->setProviderId($row['provider_id'] ?? '') - ->setAccount($row['account'] ?? '') - ->setMetadata(json_decode($row['metadata'], true) ?? []) - ->setType(SignatoryType::from($row['type'] ?? 9)) - ->setStatus(SignatoryStatus::from($row['status'] ?? 1)) - ->setCreation($row['creation'] ?? 0) - ->setLastUpdated($row['last_updated'] ?? 0); - return $this; - } - - public function jsonSerialize(): array { - return [ - 'keyId' => $this->getKeyId(), - 'publicKeyPem' => $this->getPublicKey() - ]; - } -} diff --git a/lib/private/Security/Signature/Model/SignedRequest.php b/lib/private/Security/Signature/Model/SignedRequest.php index 56853ebade3..dd3c1de431d 100644 --- a/lib/private/Security/Signature/Model/SignedRequest.php +++ b/lib/private/Security/Signature/Model/SignedRequest.php @@ -11,8 +11,8 @@ 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; +use NCU\Security\Signature\ISignedRequest; +use NCU\Security\Signature\Model\Signatory; /** * @inheritDoc @@ -21,16 +21,16 @@ use NCU\Security\Signature\Model\ISignedRequest; */ class SignedRequest implements ISignedRequest, JsonSerializable { private string $digest; - private array $signatureElements = []; - private string $clearSignature = ''; - private string $signedSignature = ''; - private ?ISignatory $signatory = null; + private array $signingElements = []; + private array $signatureData = []; + private string $signature = ''; + private ?Signatory $signatory = null; public function __construct( private readonly string $body, ) { // digest is created on the fly using $body - $this->digest = 'SHA-256=' . base64_encode(hash('sha256', utf8_encode($body), true)); + $this->digest = 'SHA-256=' . base64_encode(hash('sha256', mb_convert_encoding($body, 'UTF-8', mb_detect_encoding($body)), true)); } /** @@ -58,11 +58,11 @@ class SignedRequest implements ISignedRequest, JsonSerializable { * * @param array $elements * - * @return ISignedRequest + * @return self * @since 31.0.0 */ - public function setSignatureElements(array $elements): ISignedRequest { - $this->signatureElements = $elements; + public function setSigningElements(array $elements): self { + $this->signingElements = $elements; return $this; } @@ -72,8 +72,8 @@ class SignedRequest implements ISignedRequest, JsonSerializable { * @return array * @since 31.0.0 */ - public function getSignatureElements(): array { - return $this->signatureElements; + public function getSigningElements(): array { + return $this->signingElements; } /** @@ -84,46 +84,47 @@ class SignedRequest implements ISignedRequest, JsonSerializable { * @since 31.0.0 * */ - public function getSignatureElement(string $key): string { - if (!array_key_exists($key, $this->signatureElements)) { + public function getSigningElement(string $key): string { // getSignatureDetail / getSignatureEntry() ? + if (!array_key_exists($key, $this->signingElements)) { throw new SignatureElementNotFoundException('missing element ' . $key . ' in Signature header'); } - return $this->signatureElements[$key]; + return $this->signingElements[$key]; } /** * @inheritDoc * - * @param string $clearSignature + * @param array $data * - * @return ISignedRequest + * @return self * @since 31.0.0 */ - public function setClearSignature(string $clearSignature): ISignedRequest { - $this->clearSignature = $clearSignature; + public function setSignatureData(array $data): self { + $this->signatureData = $data; return $this; } /** * @inheritDoc * - * @return string + * @return array * @since 31.0.0 */ - public function getClearSignature(): string { - return $this->clearSignature; + public function getSignatureData(): array { + return $this->signatureData; } /** * @inheritDoc * - * @param string $signedSignature - * @return ISignedRequest + * @param string $signature + * + * @return self * @since 31.0.0 */ - public function setSignedSignature(string $signedSignature): ISignedRequest { - $this->signedSignature = $signedSignature; + public function setSignature(string $signature): self { + $this->signature = $signature; return $this; } @@ -133,18 +134,18 @@ class SignedRequest implements ISignedRequest, JsonSerializable { * @return string * @since 31.0.0 */ - public function getSignedSignature(): string { - return $this->signedSignature; + public function getSignature(): string { + return $this->signature; } /** * @inheritDoc * - * @param ISignatory $signatory - * @return ISignedRequest + * @param Signatory $signatory + * @return self * @since 31.0.0 */ - public function setSignatory(ISignatory $signatory): ISignedRequest { + public function setSignatory(Signatory $signatory): self { $this->signatory = $signatory; return $this; } @@ -152,11 +153,11 @@ class SignedRequest implements ISignedRequest, JsonSerializable { /** * @inheritDoc * - * @return ISignatory + * @return Signatory * @throws SignatoryNotFoundException * @since 31.0.0 */ - public function getSignatory(): ISignatory { + public function getSignatory(): Signatory { if ($this->signatory === null) { throw new SignatoryNotFoundException(); } @@ -178,9 +179,9 @@ class SignedRequest implements ISignedRequest, JsonSerializable { return [ 'body' => $this->body, 'digest' => $this->digest, - 'signatureElements' => $this->signatureElements, - 'clearSignature' => $this->clearSignature, - 'signedSignature' => $this->signedSignature, + 'signatureElements' => $this->signingElements, + 'clearSignature' => $this->signatureData, + 'signedSignature' => $this->signature, 'signatory' => $this->signatory ?? false, ]; } diff --git a/lib/private/Security/Signature/SignatureManager.php b/lib/private/Security/Signature/SignatureManager.php index 2d895b465ab..6247b7901fa 100644 --- a/lib/private/Security/Signature/SignatureManager.php +++ b/lib/private/Security/Signature/SignatureManager.php @@ -8,6 +8,7 @@ declare(strict_types=1); namespace OC\Security\Signature; +use NCU\Security\Signature\Enum\SignatoryType; use NCU\Security\Signature\Exceptions\IdentityNotFoundException; use NCU\Security\Signature\Exceptions\IncomingRequestException; use NCU\Security\Signature\Exceptions\InvalidKeyOriginException; @@ -18,19 +19,16 @@ 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\IIncomingSignedRequest; +use NCU\Security\Signature\IOutgoingSignedRequest; use NCU\Security\Signature\ISignatoryManager; use NCU\Security\Signature\ISignatureManager; -use NCU\Security\Signature\Model\IIncomingSignedRequest; -use NCU\Security\Signature\Model\IOutgoingSignedRequest; -use NCU\Security\Signature\Model\ISignatory; -use NCU\Security\Signature\Model\SignatoryType; -use NCU\Security\Signature\SignatureAlgorithm; +use NCU\Security\Signature\Model\Signatory; +use OC\Security\Signature\Db\SignatoryMapper; use OC\Security\Signature\Model\IncomingSignedRequest; use OC\Security\Signature\Model\OutgoingSignedRequest; -use OC\Security\Signature\Model\Signatory; use OCP\DB\Exception as DBException; use OCP\IAppConfig; -use OCP\IDBConnection; use OCP\IRequest; use Psr\Log\LoggerInterface; @@ -69,13 +67,12 @@ class SignatureManager implements ISignatureManager { 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( private readonly IRequest $request, - private readonly IDBConnection $connection, + private readonly SignatoryMapper $mapper, private readonly IAppConfig $appConfig, private readonly LoggerInterface $logger, ) { @@ -107,7 +104,7 @@ class SignatureManager implements ISignatureManager { $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'))); + $signedRequest->setOrigin($this->extractIdentityFromUri($signedRequest->getSigningElement('keyId'))); } catch (IdentityNotFoundException $e) { throw new IncomingRequestException($e->getMessage()); } @@ -144,7 +141,7 @@ class SignatureManager implements ISignatureManager { array $extraSignatureHeaders = [], ): void { $request = $signedRequest->getRequest(); - $usedHeaders = explode(' ', $signedRequest->getSignatureElement('headers')); + $usedHeaders = explode(' ', $signedRequest->getSigningElement('headers')); $neededHeaders = array_merge(['date', 'host', 'content-length', 'digest'], array_keys($extraSignatureHeaders)); $missingHeaders = array_diff($neededHeaders, $usedHeaders); @@ -165,7 +162,7 @@ class SignatureManager implements ISignatureManager { $estimated[] = $key . ': ' . $value; } - $signedRequest->setClearSignature(implode("\n", $estimated)); + $signedRequest->setSignatureData($estimated); } /** @@ -194,7 +191,7 @@ class SignatureManager implements ISignatureManager { } $signedRequest->setSignatory($knownSignatory); - $this->verifySignedRequest($signedRequest); + $signedRequest->verify(); } catch (InvalidKeyOriginException $e) { throw $e; // issue while requesting remote instance also means there is no 2nd try } catch (SignatoryNotFoundException) { @@ -202,7 +199,7 @@ class SignatureManager implements ISignatureManager { // $signatoryManager), check its validity with current signature and store it $signatory = $this->getSaneRemoteSignatory($signatoryManager, $signedRequest); $signedRequest->setSignatory($signatory); - $this->verifySignedRequest($signedRequest); + $signedRequest->verify(); $this->storeSignatory($signatory); } catch (SignatureException) { // if public key (from cache) is not valid, we try to refresh it (based on SignatoryType) @@ -214,7 +211,13 @@ class SignatureManager implements ISignatureManager { } $signedRequest->setSignatory($signatory); - $this->verifySignedRequest($signedRequest); + try { + $signedRequest->verify(); + } catch (InvalidSignatureException $e) { + $this->logger->debug('signature issue', ['signed' => $signedRequest, 'exception' => $e]); + throw $e; + } + $this->storeSignatory($signatory); } } @@ -247,36 +250,12 @@ class SignatureManager implements ISignatureManager { parse_url($uri, PHP_URL_PATH) ?? '/' ); - $this->signOutgoingRequest($signedRequest); + $signedRequest->sign(); 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 * * @param ISignatoryManager $signatoryManager @@ -307,31 +286,12 @@ class SignatureManager implements ISignatureManager { * @param string $account linked account, should be used when multiple signature can exist for the same * host * - * @return ISignatory + * @return Signatory * @throws SignatoryNotFoundException if entry does not exist in local database * @since 31.0.0 */ - public function searchSignatory(string $host, string $account = ''): ISignatory { - $qb = $this->connection->getQueryBuilder(); - $qb->select( - 'id', 'provider_id', 'host', 'account', 'key_id', 'key_id_sum', 'public_key', 'metadata', 'type', - 'status', 'creation', 'last_updated' - ); - $qb->from(self::TABLE_SIGNATORIES); - $qb->where($qb->expr()->eq('host', $qb->createNamedParameter($host))); - $qb->andWhere($qb->expr()->eq('account', $qb->createNamedParameter($account))); - - $result = $qb->executeQuery(); - $row = $result->fetch(); - $result->closeCursor(); - - if (!$row) { - throw new SignatoryNotFoundException('no signatory found'); - } - - $signature = new Signatory($row['key_id'], $row['public_key']); - - return $signature->importFromDatabase($row); + public function getSignatory(string $host, string $account = ''): Signatory { + return $this->mapper->getByHost($host, $account); } @@ -386,7 +346,7 @@ class SignatureManager implements ISignatureManager { * @param ISignatoryManager $signatoryManager * @param IIncomingSignedRequest $signedRequest * - * @return ISignatory + * @return Signatory * @throws InvalidKeyOriginException * @throws SignatoryNotFoundException * @see ISignatoryManager::getRemoteSignatory @@ -394,7 +354,7 @@ class SignatureManager implements ISignatureManager { private function getSaneRemoteSignatory( ISignatoryManager $signatoryManager, IIncomingSignedRequest $signedRequest, - ): ISignatory { + ): Signatory { $signatory = $signatoryManager->getRemoteSignatory($signedRequest->getOrigin()); if ($signatory === null) { throw new SignatoryNotFoundException('empty result from getRemoteSignatory'); @@ -406,107 +366,25 @@ class SignatureManager implements ISignatureManager { } catch (SignatureElementNotFoundException) { throw new InvalidKeyOriginException('missing keyId'); } + $signatory->setProviderId($signatoryManager->getProviderId()); - return $signatory->setProviderId($signatoryManager->getProviderId()); - } - - /** - * @param IIncomingSignedRequest $signedRequest - * - * @return void - * @throws SignatureException - * @throws SignatoryNotFoundException - */ - private function verifySignedRequest(IIncomingSignedRequest $signedRequest): void { - $publicKey = $signedRequest->getSignatory()->getPublicKey(); - if ($publicKey === '') { - throw new SignatoryNotFoundException('empty public key'); - } - - try { - $this->verifyString( - $signedRequest->getClearSignature(), - $signedRequest->getSignedSignature(), - $publicKey, - SignatureAlgorithm::tryFrom($signedRequest->getSignatureElement('algorithm')) ?? SignatureAlgorithm::SHA256 - ); - } catch (InvalidSignatureException $e) { - $this->logger->debug('signature issue', ['signed' => $signedRequest, 'exception' => $e]); - throw $e; - } - } - - /** - * @param string $clear - * @param string $privateKey - * @param SignatureAlgorithm $algorithm - * - * @return string - * @throws SignatoryException - */ - private function signString(string $clear, string $privateKey, SignatureAlgorithm $algorithm): string { - if ($privateKey === '') { - throw new SignatoryException('empty private key'); - } - - openssl_sign($clear, $signed, $privateKey, $algorithm->value); - - return base64_encode($signed); - } - - /** - * @param string $clear - * @param string $encoded - * @param string $publicKey - * @param SignatureAlgorithm $algorithm - * - * @throws InvalidSignatureException - */ - private function verifyString( - string $clear, - string $encoded, - string $publicKey, - SignatureAlgorithm $algorithm = SignatureAlgorithm::SHA256, - ): void { - $signed = base64_decode($encoded); - if (openssl_verify($clear, $signed, $publicKey, $algorithm->value) !== 1) { - throw new InvalidSignatureException('signature issue'); - } + return $signatory; } /** * @param string $keyId * - * @return ISignatory + * @return Signatory * @throws SignatoryNotFoundException */ - private function getStoredSignatory(string $keyId): ISignatory { - $qb = $this->connection->getQueryBuilder(); - $qb->select( - 'id', 'provider_id', 'host', 'account', 'key_id', 'key_id_sum', 'public_key', 'metadata', 'type', - 'status', 'creation', 'last_updated' - ); - $qb->from(self::TABLE_SIGNATORIES); - $qb->where($qb->expr()->eq('key_id_sum', $qb->createNamedParameter($this->hashKeyId($keyId)))); - - $result = $qb->executeQuery(); - $row = $result->fetch(); - $result->closeCursor(); - - if (!$row) { - throw new SignatoryNotFoundException('no signatory found in local'); - } - - $signature = new Signatory($row['key_id'], $row['public_key']); - $signature->importFromDatabase($row); - - return $signature; + private function getStoredSignatory(string $keyId): Signatory { + return $this->mapper->getByKeyId($keyId); } /** - * @param ISignatory $signatory + * @param Signatory $signatory */ - private function storeSignatory(ISignatory $signatory): void { + private function storeSignatory(Signatory $signatory): void { try { $this->insertSignatory($signatory); } catch (DBException $e) { @@ -524,34 +402,20 @@ class SignatureManager implements ISignatureManager { } /** - * @param ISignatory $signatory + * @param Signatory $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->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()))) - ->setValue('public_key', $qb->createNamedParameter($signatory->getPublicKey())) - ->setValue('metadata', $qb->createNamedParameter(json_encode($signatory->getMetadata()))) - ->setValue('type', $qb->createNamedParameter($signatory->getType()->value)) - ->setValue('status', $qb->createNamedParameter($signatory->getStatus()->value)) - ->setValue('creation', $qb->createNamedParameter(time())) - ->setValue('last_updated', $qb->createNamedParameter(time())); - - $qb->executeStatement(); + private function insertSignatory(Signatory $signatory): void { + $this->mapper->insert($signatory); } /** - * @param ISignatory $signatory + * @param Signatory $signatory * * @throws SignatoryNotFoundException * @throws SignatoryConflictException */ - private function updateKnownSignatory(ISignatory $signatory): void { + private function updateKnownSignatory(Signatory $signatory): void { $knownSignatory = $this->getStoredSignatory($signatory->getKeyId()); switch ($signatory->getType()) { case SignatoryType::FORGIVABLE: @@ -577,12 +441,12 @@ class SignatureManager implements ISignatureManager { /** * This is called when a remote signatory does not exist anymore * - * @param ISignatory|null $knownSignatory NULL is not known + * @param Signatory|null $knownSignatory NULL is not known * * @throws SignatoryConflictException * @throws SignatoryNotFoundException */ - private function manageDeprecatedSignatory(?ISignatory $knownSignatory): void { + private function manageDeprecatedSignatory(?Signatory $knownSignatory): void { switch ($knownSignatory?->getType()) { case null: // unknown in local database case SignatoryType::FORGIVABLE: // who cares ? @@ -600,38 +464,15 @@ class SignatureManager implements ISignatureManager { } - private function updateSignatoryPublicKey(ISignatory $signatory): void { - $qb = $this->connection->getQueryBuilder(); - $qb->update(self::TABLE_SIGNATORIES) - ->set('signatory', $qb->createNamedParameter($signatory->getPublicKey())) - ->set('last_updated', $qb->createNamedParameter(time())); - - $qb->where( - $qb->expr()->eq('key_id_sum', $qb->createNamedParameter($this->hashKeyId($signatory->getKeyId()))) - ); - $qb->executeStatement(); + private function updateSignatoryPublicKey(Signatory $signatory): void { + $this->mapper->updatePublicKey($signatory); } - private function updateSignatoryMetadata(ISignatory $signatory): void { - $qb = $this->connection->getQueryBuilder(); - $qb->update(self::TABLE_SIGNATORIES) - ->set('metadata', $qb->createNamedParameter(json_encode($signatory->getMetadata()))) - ->set('last_updated', $qb->createNamedParameter(time())); - - $qb->where( - $qb->expr()->eq('key_id_sum', $qb->createNamedParameter($this->hashKeyId($signatory->getKeyId()))) - ); - $qb->executeStatement(); + private function updateSignatoryMetadata(Signatory $signatory): void { + $this->mapper->updateMetadata($signatory); } private function deleteSignatory(string $keyId): void { - $qb = $this->connection->getQueryBuilder(); - $qb->delete(self::TABLE_SIGNATORIES) - ->where($qb->expr()->eq('key_id_sum', $qb->createNamedParameter($this->hashKeyId($keyId)))); - $qb->executeStatement(); - } - - private function hashKeyId(string $keyId): string { - return hash('sha256', $keyId); + $this->mapper->deleteByKeyId($keyId); } } diff --git a/lib/public/OCM/IOCMProvider.php b/lib/public/OCM/IOCMProvider.php index dd36a1c6057..cd2a59ebd5e 100644 --- a/lib/public/OCM/IOCMProvider.php +++ b/lib/public/OCM/IOCMProvider.php @@ -6,11 +6,10 @@ declare(strict_types=1); * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ - namespace OCP\OCM; use JsonSerializable; -use NCU\Security\Signature\Model\ISignatory; +use NCU\Security\Signature\Model\Signatory; use OCP\OCM\Exceptions\OCMArgumentException; use OCP\OCM\Exceptions\OCMProviderException; @@ -124,18 +123,18 @@ interface IOCMProvider extends JsonSerializable { /** * store signatory (public/private key pair) to sign outgoing/incoming request * - * @param ISignatory $signatory + * @param Signatory $signatory * @since 31.0.0 */ - public function setSignatory(ISignatory $signatory): void; + public function setSignatory(Signatory $signatory): void; /** * signatory (public/private key pair) used to sign outgoing/incoming request * - * @return ISignatory|null returns null if no ISignatory available + * @return Signatory|null returns null if no Signatory available * @since 31.0.0 */ - public function getSignatory(): ?ISignatory; + public function getSignatory(): ?Signatory; /** * import data from an array @@ -153,7 +152,7 @@ interface IOCMProvider extends JsonSerializable { * enabled: bool, * apiVersion: '1.0-proposal1', * endPoint: string, - * publicKey: ISignatory|null, + * publicKey: Signatory|null, * resourceTypes: list<array{ * name: string, * shareTypes: list<string>, diff --git a/lib/unstable/Security/Signature/Model/SignatoryStatus.php b/lib/unstable/Security/Signature/Enum/SignatoryStatus.php index 4174102beae..9c77cf9bbc2 100644 --- a/lib/unstable/Security/Signature/Model/SignatoryStatus.php +++ b/lib/unstable/Security/Signature/Enum/SignatoryStatus.php @@ -6,7 +6,7 @@ declare(strict_types=1); * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace NCU\Security\Signature\Model; +namespace NCU\Security\Signature\Enum; /** * current status of signatory. is it trustable or not ? diff --git a/lib/unstable/Security/Signature/Model/SignatoryType.php b/lib/unstable/Security/Signature/Enum/SignatoryType.php index 652bee2a970..86a766d2aa0 100644 --- a/lib/unstable/Security/Signature/Model/SignatoryType.php +++ b/lib/unstable/Security/Signature/Enum/SignatoryType.php @@ -3,15 +3,15 @@ declare(strict_types=1); /** - * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace NCU\Security\Signature\Model; +namespace NCU\Security\Signature\Enum; /** * type of link between local and remote instance * - * - FORGIVABLE = the keypair can be deleted and refreshed anytime and silently + * - FORGIVABLE = the keypair can be deleted and refreshed anytime; silently * - REFRESHABLE = the keypair can be refreshed but a notice will be generated * - TRUSTED = any changes of keypair will require human interaction, warning will be issued * - STATIC = error will be issued on conflict, assume keypair cannot be reset. diff --git a/lib/unstable/Security/Signature/SignatureAlgorithm.php b/lib/unstable/Security/Signature/Enum/SignatureAlgorithm.php index c0a5a0c6c7a..94996d17bd5 100644 --- a/lib/unstable/Security/Signature/SignatureAlgorithm.php +++ b/lib/unstable/Security/Signature/Enum/SignatureAlgorithm.php @@ -6,7 +6,7 @@ declare(strict_types=1); * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace NCU\Security\Signature; +namespace NCU\Security\Signature\Enum; /** * list of available algorithm when signing payload diff --git a/lib/unstable/Security/Signature/Model/IIncomingSignedRequest.php b/lib/unstable/Security/Signature/IIncomingSignedRequest.php index 3e2ebb22a5f..7f37570533f 100644 --- a/lib/unstable/Security/Signature/Model/IIncomingSignedRequest.php +++ b/lib/unstable/Security/Signature/IIncomingSignedRequest.php @@ -6,10 +6,11 @@ declare(strict_types=1); * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace NCU\Security\Signature\Model; +namespace NCU\Security\Signature; +use NCU\Security\Signature\Exceptions\SignatoryNotFoundException; use NCU\Security\Signature\Exceptions\SignatureElementNotFoundException; -use NCU\Security\Signature\ISignatureManager; +use NCU\Security\Signature\Exceptions\SignatureException; use OCP\IRequest; /** @@ -57,4 +58,13 @@ interface IIncomingSignedRequest extends ISignedRequest { * @since 31.0.0 */ public function getKeyId(): string; + + /** + * confirm the current signed request's identity is correct + * + * @throws SignatureException + * @throws SignatoryNotFoundException + * @since 31.0.0 + */ + public function verify(): void; } diff --git a/lib/unstable/Security/Signature/Model/IOutgoingSignedRequest.php b/lib/unstable/Security/Signature/IOutgoingSignedRequest.php index 3c9445af745..de2ab7e276d 100644 --- a/lib/unstable/Security/Signature/Model/IOutgoingSignedRequest.php +++ b/lib/unstable/Security/Signature/IOutgoingSignedRequest.php @@ -6,10 +6,11 @@ declare(strict_types=1); * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace NCU\Security\Signature\Model; +namespace NCU\Security\Signature; -use NCU\Security\Signature\ISignatureManager; -use NCU\Security\Signature\SignatureAlgorithm; +use NCU\Security\Signature\Enum\SignatureAlgorithm; +use NCU\Security\Signature\Exceptions\SignatoryException; +use NCU\Security\Signature\Exceptions\SignatoryNotFoundException; /** * extends ISignedRequest to add info requested at the generation of the signature @@ -23,10 +24,10 @@ interface IOutgoingSignedRequest extends ISignedRequest { * set the host of the recipient of the request. * * @param string $host - * @return IOutgoingSignedRequest + * @return self * @since 31.0.0 */ - public function setHost(string $host): IOutgoingSignedRequest; + public function setHost(string $host): self; /** * get the host of the recipient of the request. @@ -44,10 +45,10 @@ interface IOutgoingSignedRequest extends ISignedRequest { * @param string $key * @param string|int|float $value * - * @return IOutgoingSignedRequest + * @return self * @since 31.0.0 */ - public function addHeader(string $key, string|int|float $value): IOutgoingSignedRequest; + public function addHeader(string $key, string|int|float $value): self; /** * returns list of headers value that will be added to the base request @@ -62,10 +63,10 @@ interface IOutgoingSignedRequest extends ISignedRequest { * * @param list<string> $list * - * @return IOutgoingSignedRequest + * @return self * @since 31.0.0 */ - public function setHeaderList(array $list): IOutgoingSignedRequest; + public function setHeaderList(array $list): self; /** * returns ordered list of used headers in the Signature @@ -80,10 +81,10 @@ interface IOutgoingSignedRequest extends ISignedRequest { * * @param SignatureAlgorithm $algorithm * - * @return IOutgoingSignedRequest + * @return self * @since 31.0.0 */ - public function setAlgorithm(SignatureAlgorithm $algorithm): IOutgoingSignedRequest; + public function setAlgorithm(SignatureAlgorithm $algorithm): self; /** * returns the algorithm set to sign the signature @@ -92,4 +93,14 @@ interface IOutgoingSignedRequest extends ISignedRequest { * @since 31.0.0 */ public function getAlgorithm(): SignatureAlgorithm; + + /** + * sign outgoing request providing a certificate that it emanate from this instance + * + * @return self + * @throws SignatoryException + * @throws SignatoryNotFoundException + * @since 31.0.0 + */ + public function sign(): self; } diff --git a/lib/unstable/Security/Signature/ISignatoryManager.php b/lib/unstable/Security/Signature/ISignatoryManager.php index 19ba83a4206..20133de4c9c 100644 --- a/lib/unstable/Security/Signature/ISignatoryManager.php +++ b/lib/unstable/Security/Signature/ISignatoryManager.php @@ -8,7 +8,7 @@ declare(strict_types=1); */ namespace NCU\Security\Signature; -use NCU\Security\Signature\Model\ISignatory; +use NCU\Security\Signature\Model\Signatory; /** * ISignatoryManager contains a group of method that will help @@ -51,10 +51,10 @@ interface ISignatoryManager { * * Used to sign outgoing request * - * @return ISignatory + * @return Signatory * @since 31.0.0 */ - public function getLocalSignatory(): ISignatory; + public function getLocalSignatory(): Signatory; /** * retrieve details and generate signatory from remote instance. @@ -64,8 +64,8 @@ interface ISignatoryManager { * * @param string $remote * - * @return ISignatory|null must be NULL if no signatory is found + * @return Signatory|null must be NULL if no signatory is found * @since 31.0.0 */ - public function getRemoteSignatory(string $remote): ?ISignatory; + public function getRemoteSignatory(string $remote): ?Signatory; } diff --git a/lib/unstable/Security/Signature/ISignatureManager.php b/lib/unstable/Security/Signature/ISignatureManager.php index 1969b970aa6..c614a16cd92 100644 --- a/lib/unstable/Security/Signature/ISignatureManager.php +++ b/lib/unstable/Security/Signature/ISignatureManager.php @@ -13,9 +13,7 @@ use NCU\Security\Signature\Exceptions\IncomingRequestException; use NCU\Security\Signature\Exceptions\SignatoryNotFoundException; use NCU\Security\Signature\Exceptions\SignatureException; use NCU\Security\Signature\Exceptions\SignatureNotFoundException; -use NCU\Security\Signature\Model\IIncomingSignedRequest; -use NCU\Security\Signature\Model\IOutgoingSignedRequest; -use NCU\Security\Signature\Model\ISignatory; +use NCU\Security\Signature\Model\Signatory; /** * ISignatureManager is a service integrated to core that provide tools @@ -99,11 +97,11 @@ interface ISignatureManager { * @param string $host remote host * @param string $account linked account, should be used when multiple signature can exist for the same host * - * @return ISignatory + * @return Signatory * @throws SignatoryNotFoundException if entry does not exist in local database * @since 31.0.0 */ - public function searchSignatory(string $host, string $account = ''): ISignatory; + public function getSignatory(string $host, string $account = ''): Signatory; /** * returns a fully formatted keyId, based on a fix hostname and path diff --git a/lib/unstable/Security/Signature/Model/ISignedRequest.php b/lib/unstable/Security/Signature/ISignedRequest.php index 76c033970fe..6f9e143c579 100644 --- a/lib/unstable/Security/Signature/Model/ISignedRequest.php +++ b/lib/unstable/Security/Signature/ISignedRequest.php @@ -6,10 +6,11 @@ declare(strict_types=1); * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace NCU\Security\Signature\Model; +namespace NCU\Security\Signature; use NCU\Security\Signature\Exceptions\SignatoryNotFoundException; use NCU\Security\Signature\Exceptions\SignatureElementNotFoundException; +use NCU\Security\Signature\Model\Signatory; /** * model that store data related to a possible signature. @@ -42,10 +43,10 @@ interface ISignedRequest { * * @param array $elements * - * @return ISignedRequest + * @return self * @since 31.0.0 */ - public function setSignatureElements(array $elements): ISignedRequest; + public function setSigningElements(array $elements): self; /** * get the list of elements in the Signature header of the request @@ -53,7 +54,7 @@ interface ISignedRequest { * @return array * @since 31.0.0 */ - public function getSignatureElements(): array; + public function getSigningElements(): array; /** * @param string $key @@ -62,34 +63,35 @@ interface ISignedRequest { * @throws SignatureElementNotFoundException * @since 31.0.0 */ - public function getSignatureElement(string $key): string; + public function getSigningElement(string $key): string; /** - * store a clear version of the signature + * store data used to generate signature * - * @param string $clearSignature + * @param array $data * - * @return ISignedRequest + * @return self * @since 31.0.0 */ - public function setClearSignature(string $clearSignature): ISignedRequest; + public function setSignatureData(array $data): self; /** - * returns the clear version of the signature + * returns data used to generate signature * - * @return string + * @return array * @since 31.0.0 */ - public function getClearSignature(): string; + public function getSignatureData(): array; /** * set the signed version of the signature * - * @param string $signedSignature - * @return ISignedRequest + * @param string $signature + * + * @return self * @since 31.0.0 */ - public function setSignedSignature(string $signedSignature): ISignedRequest; + public function setSignature(string $signature): self; /** * get the signed version of the signature @@ -97,25 +99,25 @@ interface ISignedRequest { * @return string * @since 31.0.0 */ - public function getSignedSignature(): string; + public function getSignature(): string; /** * set the signatory, containing keys and details, related to this request * - * @param ISignatory $signatory - * @return ISignedRequest + * @param Signatory $signatory + * @return self * @since 31.0.0 */ - public function setSignatory(ISignatory $signatory): ISignedRequest; + public function setSignatory(Signatory $signatory): self; /** * get the signatory, containing keys and details, related to this request * - * @return ISignatory + * @return Signatory * @throws SignatoryNotFoundException * @since 31.0.0 */ - public function getSignatory(): ISignatory; + public function getSignatory(): Signatory; /** * returns if a signatory related to this request have been found and defined diff --git a/lib/unstable/Security/Signature/Model/ISignatory.php b/lib/unstable/Security/Signature/Model/ISignatory.php deleted file mode 100644 index e77b77e66e5..00000000000 --- a/lib/unstable/Security/Signature/Model/ISignatory.php +++ /dev/null @@ -1,160 +0,0 @@ -<?php - -declare(strict_types=1); - -/** - * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -namespace NCU\Security\Signature\Model; - -use NCU\Security\Signature\ISignatoryManager; - -/** - * model that store keys and details related to host and in use protocol - * mandatory details are providerId, host, keyId and public key. - * private key is only used for local signatory, used to sign outgoing request - * - * the pair providerId+host is unique, meaning only one signatory can exist for each host - * and protocol - * - * @since 31.0.0 - * @experimental 31.0.0 - */ -interface ISignatory { - /** - * unique string, related to the ISignatoryManager - * - * @see ISignatoryManager::getProviderId - * @param string $providerId - * - * @return ISignatory - * @since 31.0.0 - */ - public function setProviderId(string $providerId): ISignatory; - - /** - * returns the provider id, unique string related to the ISignatoryManager - * - * @return string - * @since 31.0.0 - */ - public function getProviderId(): string; - - /** - * set account, in case your ISignatoryManager needs to manage multiple keys from same host - * - * @param string $account - * - * @return ISignatory - * @since 31.0.0 - */ - public function setAccount(string $account): ISignatory; - - /** - * return account name, empty string if not set - * - * @return string - * @since 31.0.0 - */ - public function getAccount(): string; - - /** - * returns key id - * - * @return string - * @since 31.0.0 - */ - public function getKeyId(): string; - - /** - * returns public key - * - * @return string - * @since 31.0.0 - */ - public function getPublicKey(): string; - - /** - * returns private key, if available - * - * @return string - * @since 31.0.0 - */ - public function getPrivateKey(): string; - - /** - * set metadata - * - * @param array $metadata - * - * @return ISignatory - * @since 31.0.0 - */ - public function setMetadata(array $metadata): ISignatory; - - /** - * returns metadata - * - * @return array - * @since 31.0.0 - */ - public function getMetadata(): array; - - /** - * update an entry in metadata - * - * @param string $key - * @param string|int $value - * - * @return ISignatory - * @since 31.0.0 - */ - public function setMetaValue(string $key, string|int $value): ISignatory; - - /** - * set SignatoryType - * - * @param SignatoryType $type - * - * @return ISignatory - * @since 31.0.0 - */ - public function setType(SignatoryType $type): ISignatory; - - /** - * returns SignatoryType - * - * @return SignatoryType - * @since 31.0.0 - */ - public function getType(): SignatoryType; - - /** - * set SignatoryStatus - * - * @param SignatoryStatus $status - * - * @see SignatoryStatus - * @return ISignatory - * @since 31.0.0 - */ - public function setStatus(SignatoryStatus $status): ISignatory; - - /** - * get SignatoryStatus - * - * @see SignatoryStatus - * @return SignatoryStatus - * @since 31.0.0 - */ - public function getStatus(): SignatoryStatus; - - /** - * get last timestamp this entry has been updated - * - * @return int - * @since 31.0.0 - */ - public function getLastUpdated(): int; -} diff --git a/lib/unstable/Security/Signature/Model/Signatory.php b/lib/unstable/Security/Signature/Model/Signatory.php new file mode 100644 index 00000000000..621cd5ac7ee --- /dev/null +++ b/lib/unstable/Security/Signature/Model/Signatory.php @@ -0,0 +1,165 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace NCU\Security\Signature\Model; + +use JsonSerializable; +use NCU\Security\Signature\Enum\SignatoryStatus; +use NCU\Security\Signature\Enum\SignatoryType; +use OCP\AppFramework\Db\Entity; + +/** + * model that store keys and details related to host and in use protocol + * mandatory details are providerId, host, keyId and public key. + * private key is only used for local signatory, used to sign outgoing request + * + * the pair providerId+host is unique, meaning only one signatory can exist for each host + * and protocol + * + * @since 31.0.0 + * @experimental 31.0.0 + * + * @method void setProviderId(string $providerId) + * @method string getProviderId() + * @method string getKeyId() + * @method void setPublicKey(string $publicKey) + * @method string getPublicKey() + * @method void setPrivateKey(string $privateKey) + * @method string getPrivateKey() + * @method void setHost(string $host) + * @method string getHost() + * @method void setAccount(string $account) + * @method string getAccount() + * @method void setMetadata(array $metadata) + * @method array getMetadata() + * @method void setCreation(int $creation) + * @method int getCreation() + * @method void setLastUpdated(int $creation) + * @method int getLastUpdated() + */ +class Signatory extends Entity implements JsonSerializable { + protected string $keyId = ''; + protected string $keyIdSum = ''; + protected string $providerId = ''; + protected string $host = ''; + protected string $account = ''; + protected int $type = 9; + protected int $status = 1; + protected array $metadata = []; + protected int $creation = 0; + protected int $lastUpdated = 0; + + /** + * @param string $keyId + * @param string $publicKey + * @param string $privateKey + * @param bool $local + * + * @since 31.0.0 + */ + public function __construct( + string $keyId = '', + protected string $publicKey = '', + protected string $privateKey = '', + private readonly bool $local = false, + ) { + $this->addType('providerId', 'string'); + $this->addType('host', 'string'); + $this->addType('account', 'string'); + $this->addType('keyId', 'string'); + $this->addType('keyIdSum', 'string'); + $this->addType('publicKey', 'string'); + $this->addType('metadata', 'json'); + $this->addType('type', 'integer'); + $this->addType('status', 'integer'); + $this->addType('creation', 'integer'); + $this->addType('lastUpdated', 'integer'); + + $this->setKeyId($keyId); + } + + /** + * @param string $keyId + * + * @since 31.0.0 + */ + public function setKeyId(string $keyId): void { + // if set as local (for current instance), we apply some filters. + if ($this->local) { + // to avoid conflict with duplicate key pairs (ie generated url from the occ command), we enforce https as prefix + if (str_starts_with($keyId, 'http://')) { + $keyId = 'https://' . substr($keyId, 7); + } + + // removing /index.php from generated url + $path = parse_url($keyId, PHP_URL_PATH); + if (str_starts_with($path, '/index.php/')) { + $pos = strpos($keyId, '/index.php'); + if ($pos !== false) { + $keyId = substr_replace($keyId, '', $pos, 10); + } + } + } + $this->keyId = $keyId; + $this->keyIdSum = hash('sha256', $keyId); + } + + /** + * @param SignatoryType $type + * @since 31.0.0 + */ + public function setType(SignatoryType $type): void { + $this->type = $type->value; + } + + /** + * @return SignatoryType + * @since 31.0.0 + */ + public function getType(): SignatoryType { + return SignatoryType::from($this->type); + } + + /** + * @param SignatoryStatus $status + * @since 31.0.0 + */ + public function setStatus(SignatoryStatus $status): void { + $this->status = $status->value; + } + + /** + * @return SignatoryStatus + * @since 31.0.0 + */ + public function getStatus(): SignatoryStatus { + return SignatoryStatus::from($this->status); + } + + /** + * update an entry in metadata + * + * @param string $key + * @param string|int|float|bool|array $value + * @since 31.0.0 + */ + public function setMetaValue(string $key, string|int|float|bool|array $value): void { + $this->metadata[$key] = $value; + } + + /** + * @return array + * @since 31.0.0 + */ + public function jsonSerialize(): array { + return [ + 'keyId' => $this->getKeyId(), + 'publicKeyPem' => $this->getPublicKey() + ]; + } +} |