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 /lib/private | |
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>
Diffstat (limited to 'lib/private')
-rw-r--r-- | lib/private/OCM/Model/OCMProvider.php | 11 | ||||
-rw-r--r-- | lib/private/OCM/OCMSignatoryManager.php | 21 | ||||
-rw-r--r-- | lib/private/Security/IdentityProof/Manager.php | 8 | ||||
-rw-r--r-- | lib/private/Security/Signature/Db/SignatoryMapper.php | 114 | ||||
-rw-r--r-- | lib/private/Security/Signature/Model/IncomingSignedRequest.php | 52 | ||||
-rw-r--r-- | lib/private/Security/Signature/Model/OutgoingSignedRequest.php | 78 | ||||
-rw-r--r-- | lib/private/Security/Signature/Model/Signatory.php | 147 | ||||
-rw-r--r-- | lib/private/Security/Signature/Model/SignedRequest.php | 73 | ||||
-rw-r--r-- | lib/private/Security/Signature/SignatureManager.php | 245 |
9 files changed, 319 insertions, 430 deletions
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); } } |