diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 89e53c98602cb..a30eccfd83804 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -12,6 +12,7 @@ '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\\DigestAlgorithm' => $baseDir . '/lib/unstable/Security/Signature/Enum/DigestAlgorithm.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', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index b868cd442130a..9ca1852a0712d 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -53,6 +53,7 @@ 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\\DigestAlgorithm' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Enum/DigestAlgorithm.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', diff --git a/lib/private/OCM/Model/OCMProvider.php b/lib/private/OCM/Model/OCMProvider.php index 32068efe3eb16..fb13b7c0f9356 100644 --- a/lib/private/OCM/Model/OCMProvider.php +++ b/lib/private/OCM/Model/OCMProvider.php @@ -183,7 +183,9 @@ public function import(array $data): static { $this->setResourceTypes($resources); // import details about the remote request signing public key, if available - $signatory = new Signatory($data['publicKey']['keyId'] ?? '', $data['publicKey']['publicKeyPem'] ?? ''); + $signatory = new Signatory(); + $signatory->setKeyId($data['publicKey']['keyId'] ?? ''); + $signatory->setPublicKey($data['publicKey']['publicKeyPem'] ?? ''); if ($signatory->getKeyId() !== '' && $signatory->getPublicKey() !== '') { $this->setSignatory($signatory); } diff --git a/lib/private/OCM/OCMSignatoryManager.php b/lib/private/OCM/OCMSignatoryManager.php index 909952a6b3738..6b6917bcd4b41 100644 --- a/lib/private/OCM/OCMSignatoryManager.php +++ b/lib/private/OCM/OCMSignatoryManager.php @@ -9,7 +9,9 @@ namespace OC\OCM; +use NCU\Security\Signature\Enum\DigestAlgorithm; use NCU\Security\Signature\Enum\SignatoryType; +use NCU\Security\Signature\Enum\SignatureAlgorithm; use NCU\Security\Signature\Exceptions\IdentityNotFoundException; use NCU\Security\Signature\ISignatoryManager; use NCU\Security\Signature\ISignatureManager; @@ -61,7 +63,15 @@ public function getProviderId(): string { * @since 31.0.0 */ public function getOptions(): array { - return []; + return [ + 'algorithm' => SignatureAlgorithm::RSA_SHA512, + 'digestAlgorithm' => DigestAlgorithm::SHA512, + 'extraSignatureHeaders' => [], + 'ttl' => 300, + 'dateHeader' => 'D, d M Y H:i:s T', + 'ttlSignatory' => 86400 * 3, + 'bodyMaxSize' => 50000, + ]; } /** @@ -92,7 +102,12 @@ public function getLocalSignatory(): Signatory { } $keyPair = $this->identityProofManager->getAppKey('core', 'ocm_external'); - return new Signatory($keyId, $keyPair->getPublic(), $keyPair->getPrivate(), local: true); + $signatory = new Signatory(true); + $signatory->setKeyId($keyId); + $signatory->setPublicKey($keyPair->getPublic()); + $signatory->setPrivateKey($keyPair->getPrivate()); + return $signatory; + } /** @@ -148,7 +163,7 @@ public function getRemoteSignatory(string $remote): ?Signatory { public function getRemoteSignatoryFromHost(string $host): ?Signatory { $ocmProvider = $this->ocmDiscoveryService->discover($host, true); $signatory = $ocmProvider->getSignatory(); - $signatory?->setType(SignatoryType::TRUSTED); + $signatory?->setSignatoryType(SignatoryType::TRUSTED); return $signatory; } } diff --git a/lib/private/Security/Signature/Model/IncomingSignedRequest.php b/lib/private/Security/Signature/Model/IncomingSignedRequest.php index fae8b897d5b77..2a1aa82ac50a5 100644 --- a/lib/private/Security/Signature/Model/IncomingSignedRequest.php +++ b/lib/private/Security/Signature/Model/IncomingSignedRequest.php @@ -36,8 +36,13 @@ class IncomingSignedRequest extends SignedRequest implements private string $origin = ''; /** + * @param string $body + * @param IRequest $request + * @param array $options + * * @throws IncomingRequestException if incoming request is wrongly signed - * @throws SignatureNotFoundException if signature is not fully implemented + * @throws SignatureException if signature is faulty + * @throws SignatureNotFoundException if signature is not implemented */ public function __construct( string $body, @@ -45,8 +50,9 @@ public function __construct( private readonly array $options = [], ) { parent::__construct($body); - $this->verifyHeadersFromRequest(); - $this->extractSignatureHeaderFromRequest(); + $this->verifyHeaders(); + $this->extractSignatureHeader(); + $this->reconstructSignatureData(); } /** @@ -59,7 +65,7 @@ public function __construct( * @throws IncomingRequestException * @throws SignatureNotFoundException */ - private function verifyHeadersFromRequest(): void { + private function verifyHeaders(): void { // confirm presence of date, content-length, digest and Signature $date = $this->getRequest()->getHeader('date'); if ($date === '') { @@ -105,7 +111,7 @@ private function verifyHeadersFromRequest(): void { * * @throws IncomingRequestException */ - private function extractSignatureHeaderFromRequest(): void { + private function extractSignatureHeader(): void { $details = []; foreach (explode(',', $this->getRequest()->getHeader('Signature')) as $entry) { if ($entry === '' || !strpos($entry, '=')) { @@ -132,6 +138,36 @@ private function extractSignatureHeaderFromRequest(): void { } } + /** + * @throws SignatureException + * @throws SignatureElementNotFoundException + */ + private function reconstructSignatureData(): void { + $usedHeaders = explode(' ', $this->getSigningElement('headers')); + $neededHeaders = array_merge(['date', 'host', 'content-length', 'digest'], + array_keys($this->options['extraSignatureHeaders'] ?? [])); + + $missingHeaders = array_diff($neededHeaders, $usedHeaders); + if ($missingHeaders !== []) { + throw new SignatureException('missing entries in Signature.headers: ' . json_encode($missingHeaders)); + } + + $estimated = ['(request-target): ' . strtolower($this->request->getMethod()) . ' ' . $this->request->getRequestUri()]; + foreach ($usedHeaders as $key) { + if ($key === '(request-target)') { + continue; + } + $value = (strtolower($key) === 'host') ? $this->request->getServerHost() : $this->request->getHeader($key); + if ($value === '') { + throw new SignatureException('missing header ' . $key . ' in request'); + } + + $estimated[] = $key . ': ' . $value; + } + + $this->setSignatureData($estimated); + } + /** * @inheritDoc * @@ -214,7 +250,7 @@ public function verify(): void { throw new SignatoryNotFoundException('empty public key'); } - $algorithm = SignatureAlgorithm::tryFrom($this->getSigningElement('algorithm')) ?? SignatureAlgorithm::SHA256; + $algorithm = SignatureAlgorithm::tryFrom($this->getSigningElement('algorithm')) ?? SignatureAlgorithm::RSA_SHA256; if (openssl_verify( implode("\n", $this->getSignatureData()), base64_decode($this->getSignature()), diff --git a/lib/private/Security/Signature/Model/OutgoingSignedRequest.php b/lib/private/Security/Signature/Model/OutgoingSignedRequest.php index 8879821a029d3..dbfac3bfd34e1 100644 --- a/lib/private/Security/Signature/Model/OutgoingSignedRequest.php +++ b/lib/private/Security/Signature/Model/OutgoingSignedRequest.php @@ -9,6 +9,7 @@ namespace OC\Security\Signature\Model; use JsonSerializable; +use NCU\Security\Signature\Enum\DigestAlgorithm; use NCU\Security\Signature\Enum\SignatureAlgorithm; use NCU\Security\Signature\Exceptions\SignatoryException; use NCU\Security\Signature\Exceptions\SignatoryNotFoundException; @@ -42,8 +43,9 @@ public function __construct( $options = $signatoryManager->getOptions(); $this->setHost($identity) - ->setAlgorithm(SignatureAlgorithm::from($options['algorithm'] ?? 'sha256')) - ->setSignatory($signatoryManager->getLocalSignatory()); + ->setAlgorithm($options['algorithm'] ?? SignatureAlgorithm::RSA_SHA256) + ->setSignatory($signatoryManager->getLocalSignatory()) + ->setDigestAlgorithm($options['digestAlgorithm'] ?? DigestAlgorithm::SHA256); $headers = array_merge([ '(request-target)' => strtolower($method) . ' ' . $path, diff --git a/lib/private/Security/Signature/Model/SignedRequest.php b/lib/private/Security/Signature/Model/SignedRequest.php index dd3c1de431dbf..214e43e8cb343 100644 --- a/lib/private/Security/Signature/Model/SignedRequest.php +++ b/lib/private/Security/Signature/Model/SignedRequest.php @@ -9,6 +9,7 @@ namespace OC\Security\Signature\Model; use JsonSerializable; +use NCU\Security\Signature\Enum\DigestAlgorithm; use NCU\Security\Signature\Exceptions\SignatoryNotFoundException; use NCU\Security\Signature\Exceptions\SignatureElementNotFoundException; use NCU\Security\Signature\ISignedRequest; @@ -20,7 +21,8 @@ * @since 31.0.0 */ class SignedRequest implements ISignedRequest, JsonSerializable { - private string $digest; + private string $digest = ''; + private DigestAlgorithm $digestAlgorithm = DigestAlgorithm::SHA256; private array $signingElements = []; private array $signatureData = []; private string $signature = ''; @@ -29,8 +31,6 @@ class SignedRequest implements ISignedRequest, JsonSerializable { public function __construct( private readonly string $body, ) { - // digest is created on the fly using $body - $this->digest = 'SHA-256=' . base64_encode(hash('sha256', mb_convert_encoding($body, 'UTF-8', mb_detect_encoding($body)), true)); } /** @@ -43,6 +43,28 @@ public function getBody(): string { return $this->body; } + /** + * @inheritDoc + * + * @param DigestAlgorithm $algorithm + * + * @return self + * @since 31.0.0 + */ + public function setDigestAlgorithm(DigestAlgorithm $algorithm): self { + return $this; + } + + /** + * @inheritDoc + * + * @return DigestAlgorithm + * @since 31.0.0 + */ + public function getDigestAlgorithm(): DigestAlgorithm { + return $this->digestAlgorithm; + } + /** * @inheritDoc * @@ -50,6 +72,10 @@ public function getBody(): string { * @since 31.0.0 */ public function getDigest(): string { + if ($this->digest === '') { + $this->digest = $this->digestAlgorithm->value . '=' . + base64_encode(hash($this->digestAlgorithm->getHashingAlgorithm(), $this->body, true)); + } return $this->digest; } @@ -178,10 +204,11 @@ public function hasSignatory(): bool { public function jsonSerialize(): array { return [ 'body' => $this->body, - 'digest' => $this->digest, - 'signatureElements' => $this->signingElements, - 'clearSignature' => $this->signatureData, - 'signedSignature' => $this->signature, + 'digest' => $this->getDigest(), + 'digestAlgorithm' => $this->getDigestAlgorithm()->value, + 'signingElements' => $this->signingElements, + 'signatureData' => $this->signatureData, + 'signature' => $this->signature, 'signatory' => $this->signatory ?? false, ]; } diff --git a/lib/private/Security/Signature/SignatureManager.php b/lib/private/Security/Signature/SignatureManager.php index 6247b7901fa4d..b04d683a3b9de 100644 --- a/lib/private/Security/Signature/SignatureManager.php +++ b/lib/private/Security/Signature/SignatureManager.php @@ -111,7 +111,6 @@ public function getIncomingSignedRequest( try { // confirm the validity of content and identity of the incoming request - $this->generateExpectedClearSignatureFromRequest($signedRequest, $options['extraSignatureHeaders'] ?? []); $this->confirmIncomingRequestSignature($signedRequest, $signatoryManager, $options['ttlSignatory'] ?? self::SIGNATORY_TTL); } catch (SignatureException $e) { $this->logger->warning( @@ -127,44 +126,6 @@ public function getIncomingSignedRequest( return $signedRequest; } - /** - * generating the expected signature (clear version) sent by the remote instance - * based on the data available in the Signature header. - * - * @param IIncomingSignedRequest $signedRequest - * @param array $extraSignatureHeaders - * - * @throws SignatureException - */ - private function generateExpectedClearSignatureFromRequest( - IIncomingSignedRequest $signedRequest, - array $extraSignatureHeaders = [], - ): void { - $request = $signedRequest->getRequest(); - $usedHeaders = explode(' ', $signedRequest->getSigningElement('headers')); - $neededHeaders = array_merge(['date', 'host', 'content-length', 'digest'], array_keys($extraSignatureHeaders)); - - $missingHeaders = array_diff($neededHeaders, $usedHeaders); - if ($missingHeaders !== []) { - throw new SignatureException('missing entries in Signature.headers: ' . json_encode($missingHeaders)); - } - - $estimated = ['(request-target): ' . strtolower($request->getMethod()) . ' ' . $request->getRequestUri()]; - foreach ($usedHeaders as $key) { - if ($key === '(request-target)') { - continue; - } - $value = (strtolower($key) === 'host') ? $request->getServerHost() : $request->getHeader($key); - if ($value === '') { - throw new SignatureException('missing header ' . $key . ' in request'); - } - - $estimated[] = $key . ': ' . $value; - } - - $signedRequest->setSignatureData($estimated); - } - /** * confirm that the Signature is signed using the correct private key, using * clear version of the Signature and the public key linked to the keyId @@ -326,17 +287,7 @@ public function generateKeyIdFromConfig(string $path): string { * @since 31.0.0 */ public function extractIdentityFromUri(string $uri): string { - $identity = parse_url($uri, PHP_URL_HOST); - $port = parse_url($uri, PHP_URL_PORT); - if ($identity === null || $identity === false) { - throw new IdentityNotFoundException('cannot extract identity from ' . $uri); - } - - if ($port !== null && $port !== false) { - $identity .= ':' . $port; - } - - return $identity; + return Signatory::extractIdentityFromUri($uri); } /** @@ -403,9 +354,11 @@ private function storeSignatory(Signatory $signatory): void { /** * @param Signatory $signatory - * @throws DBException */ private function insertSignatory(Signatory $signatory): void { + $time = time(); + $signatory->setCreation($time); + $signatory->setLastUpdated($time); $this->mapper->insert($signatory); } diff --git a/lib/unstable/Security/Signature/Enum/DigestAlgorithm.php b/lib/unstable/Security/Signature/Enum/DigestAlgorithm.php new file mode 100644 index 0000000000000..465f33fd2c355 --- /dev/null +++ b/lib/unstable/Security/Signature/Enum/DigestAlgorithm.php @@ -0,0 +1,34 @@ + 'sha256', + self::SHA512 => 'sha512', + }; + } +} diff --git a/lib/unstable/Security/Signature/Enum/SignatoryStatus.php b/lib/unstable/Security/Signature/Enum/SignatoryStatus.php index 9c77cf9bbc2b1..1e460aed449fd 100644 --- a/lib/unstable/Security/Signature/Enum/SignatoryStatus.php +++ b/lib/unstable/Security/Signature/Enum/SignatoryStatus.php @@ -15,11 +15,10 @@ * - BROKEN = the remote instance does not use the same key pairs than previously * * @experimental 31.0.0 - * @since 31.0.0 */ enum SignatoryStatus: int { - /** @since 31.0.0 */ + /** @experimental 31.0.0 */ case SYNCED = 1; - /** @since 31.0.0 */ + /** @experimental 31.0.0 */ case BROKEN = 9; } diff --git a/lib/unstable/Security/Signature/Enum/SignatoryType.php b/lib/unstable/Security/Signature/Enum/SignatoryType.php index 86a766d2aa084..de3e556847909 100644 --- a/lib/unstable/Security/Signature/Enum/SignatoryType.php +++ b/lib/unstable/Security/Signature/Enum/SignatoryType.php @@ -17,15 +17,14 @@ * - STATIC = error will be issued on conflict, assume keypair cannot be reset. * * @experimental 31.0.0 - * @since 31.0.0 */ enum SignatoryType: int { - /** @since 31.0.0 */ + /** @experimental 31.0.0 */ case FORGIVABLE = 1; // no notice on refresh - /** @since 31.0.0 */ + /** @experimental 31.0.0 */ case REFRESHABLE = 4; // notice on refresh - /** @since 31.0.0 */ + /** @experimental 31.0.0 */ case TRUSTED = 8; // warning on refresh - /** @since 31.0.0 */ + /** @experimental 31.0.0 */ case STATIC = 9; // error on refresh } diff --git a/lib/unstable/Security/Signature/Enum/SignatureAlgorithm.php b/lib/unstable/Security/Signature/Enum/SignatureAlgorithm.php index 94996d17bd5ed..5afa8a3f81003 100644 --- a/lib/unstable/Security/Signature/Enum/SignatureAlgorithm.php +++ b/lib/unstable/Security/Signature/Enum/SignatureAlgorithm.php @@ -12,11 +12,10 @@ * list of available algorithm when signing payload * * @experimental 31.0.0 - * @since 31.0.0 */ enum SignatureAlgorithm: string { - /** @since 31.0.0 */ - case SHA256 = 'sha256'; - /** @since 31.0.0 */ - case SHA512 = 'sha512'; + /** @experimental 31.0.0 */ + case RSA_SHA256 = 'rsa-sha256'; + /** @experimental 31.0.0 */ + case RSA_SHA512 = 'rsa-sha512'; } diff --git a/lib/unstable/Security/Signature/Exceptions/IdentityNotFoundException.php b/lib/unstable/Security/Signature/Exceptions/IdentityNotFoundException.php index 30c7f8e60a565..c8c700033e623 100644 --- a/lib/unstable/Security/Signature/Exceptions/IdentityNotFoundException.php +++ b/lib/unstable/Security/Signature/Exceptions/IdentityNotFoundException.php @@ -9,7 +9,6 @@ namespace NCU\Security\Signature\Exceptions; /** - * @since 31.0.0 * @experimental 31.0.0 */ class IdentityNotFoundException extends SignatureException { diff --git a/lib/unstable/Security/Signature/Exceptions/IncomingRequestException.php b/lib/unstable/Security/Signature/Exceptions/IncomingRequestException.php index d3b5c93849c42..c334090fdc340 100644 --- a/lib/unstable/Security/Signature/Exceptions/IncomingRequestException.php +++ b/lib/unstable/Security/Signature/Exceptions/IncomingRequestException.php @@ -9,7 +9,6 @@ namespace NCU\Security\Signature\Exceptions; /** - * @since 31.0.0 * @experimental 31.0.0 */ class IncomingRequestException extends SignatureException { diff --git a/lib/unstable/Security/Signature/Exceptions/InvalidKeyOriginException.php b/lib/unstable/Security/Signature/Exceptions/InvalidKeyOriginException.php index 6e170295419f4..3d8fa78077f8d 100644 --- a/lib/unstable/Security/Signature/Exceptions/InvalidKeyOriginException.php +++ b/lib/unstable/Security/Signature/Exceptions/InvalidKeyOriginException.php @@ -9,7 +9,6 @@ namespace NCU\Security\Signature\Exceptions; /** - * @since 31.0.0 * @experimental 31.0.0 */ class InvalidKeyOriginException extends SignatureException { diff --git a/lib/unstable/Security/Signature/Exceptions/InvalidSignatureException.php b/lib/unstable/Security/Signature/Exceptions/InvalidSignatureException.php index dc98d9ccb8413..351637ef201b9 100644 --- a/lib/unstable/Security/Signature/Exceptions/InvalidSignatureException.php +++ b/lib/unstable/Security/Signature/Exceptions/InvalidSignatureException.php @@ -9,7 +9,6 @@ namespace NCU\Security\Signature\Exceptions; /** - * @since 31.0.0 * @experimental 31.0.0 */ class InvalidSignatureException extends SignatureException { diff --git a/lib/unstable/Security/Signature/Exceptions/SignatoryConflictException.php b/lib/unstable/Security/Signature/Exceptions/SignatoryConflictException.php index c2c4d61e0de04..e078071e970a1 100644 --- a/lib/unstable/Security/Signature/Exceptions/SignatoryConflictException.php +++ b/lib/unstable/Security/Signature/Exceptions/SignatoryConflictException.php @@ -9,7 +9,6 @@ namespace NCU\Security\Signature\Exceptions; /** - * @since 31.0.0 * @experimental 31.0.0 */ class SignatoryConflictException extends SignatoryException { diff --git a/lib/unstable/Security/Signature/Exceptions/SignatoryException.php b/lib/unstable/Security/Signature/Exceptions/SignatoryException.php index 0645e7b394415..92409ab3d988b 100644 --- a/lib/unstable/Security/Signature/Exceptions/SignatoryException.php +++ b/lib/unstable/Security/Signature/Exceptions/SignatoryException.php @@ -9,7 +9,6 @@ namespace NCU\Security\Signature\Exceptions; /** - * @since 31.0.0 * @experimental 31.0.0 */ class SignatoryException extends SignatureException { diff --git a/lib/unstable/Security/Signature/Exceptions/SignatoryNotFoundException.php b/lib/unstable/Security/Signature/Exceptions/SignatoryNotFoundException.php index e956264b62346..0234b3e7d5c8a 100644 --- a/lib/unstable/Security/Signature/Exceptions/SignatoryNotFoundException.php +++ b/lib/unstable/Security/Signature/Exceptions/SignatoryNotFoundException.php @@ -9,7 +9,6 @@ namespace NCU\Security\Signature\Exceptions; /** - * @since 31.0.0 * @experimental 31.0.0 */ class SignatoryNotFoundException extends SignatoryException { diff --git a/lib/unstable/Security/Signature/Exceptions/SignatureElementNotFoundException.php b/lib/unstable/Security/Signature/Exceptions/SignatureElementNotFoundException.php index f40f79410aef4..ca0fa1c2194b2 100644 --- a/lib/unstable/Security/Signature/Exceptions/SignatureElementNotFoundException.php +++ b/lib/unstable/Security/Signature/Exceptions/SignatureElementNotFoundException.php @@ -9,7 +9,6 @@ namespace NCU\Security\Signature\Exceptions; /** - * @since 31.0.0 * @experimental 31.0.0 */ class SignatureElementNotFoundException extends SignatureException { diff --git a/lib/unstable/Security/Signature/Exceptions/SignatureException.php b/lib/unstable/Security/Signature/Exceptions/SignatureException.php index bcd21c9f02301..12353a8e61b51 100644 --- a/lib/unstable/Security/Signature/Exceptions/SignatureException.php +++ b/lib/unstable/Security/Signature/Exceptions/SignatureException.php @@ -11,7 +11,6 @@ use Exception; /** - * @since 31.0.0 * @experimental 31.0.0 */ class SignatureException extends Exception { diff --git a/lib/unstable/Security/Signature/Exceptions/SignatureNotFoundException.php b/lib/unstable/Security/Signature/Exceptions/SignatureNotFoundException.php index a1bf23710ce28..f015b07673b1f 100644 --- a/lib/unstable/Security/Signature/Exceptions/SignatureNotFoundException.php +++ b/lib/unstable/Security/Signature/Exceptions/SignatureNotFoundException.php @@ -9,7 +9,6 @@ namespace NCU\Security\Signature\Exceptions; /** - * @since 31.0.0 * @experimental 31.0.0 */ class SignatureNotFoundException extends SignatureException { diff --git a/lib/unstable/Security/Signature/IIncomingSignedRequest.php b/lib/unstable/Security/Signature/IIncomingSignedRequest.php index 7f37570533f07..11a2cdde86865 100644 --- a/lib/unstable/Security/Signature/IIncomingSignedRequest.php +++ b/lib/unstable/Security/Signature/IIncomingSignedRequest.php @@ -19,14 +19,13 @@ * * @see ISignatureManager for details on signature * @experimental 31.0.0 - * @since 31.0.0 */ interface IIncomingSignedRequest extends ISignedRequest { /** * returns the base IRequest * * @return IRequest - * @since 31.0.0 + * @experimental 31.0.0 */ public function getRequest(): IRequest; @@ -36,7 +35,7 @@ public function getRequest(): IRequest; * * @param string $origin * @return IIncomingSignedRequest - * @since 31.0.0 + * @experimental 31.0.0 */ public function setOrigin(string $origin): IIncomingSignedRequest; @@ -45,7 +44,7 @@ public function setOrigin(string $origin): IIncomingSignedRequest; * based on the keyId defined in the signature header. * * @return string - * @since 31.0.0 + * @experimental 31.0.0 */ public function getOrigin(): string; @@ -55,7 +54,7 @@ public function getOrigin(): string; * * @return string * @throws SignatureElementNotFoundException - * @since 31.0.0 + * @experimental 31.0.0 */ public function getKeyId(): string; @@ -64,7 +63,7 @@ public function getKeyId(): string; * * @throws SignatureException * @throws SignatoryNotFoundException - * @since 31.0.0 + * @experimental 31.0.0 */ public function verify(): void; } diff --git a/lib/unstable/Security/Signature/IOutgoingSignedRequest.php b/lib/unstable/Security/Signature/IOutgoingSignedRequest.php index de2ab7e276d90..3901c9e555c02 100644 --- a/lib/unstable/Security/Signature/IOutgoingSignedRequest.php +++ b/lib/unstable/Security/Signature/IOutgoingSignedRequest.php @@ -17,7 +17,6 @@ * * @see ISignatureManager for details on signature * @experimental 31.0.0 - * @since 31.0.0 */ interface IOutgoingSignedRequest extends ISignedRequest { /** @@ -25,7 +24,7 @@ interface IOutgoingSignedRequest extends ISignedRequest { * * @param string $host * @return self - * @since 31.0.0 + * @experimental 31.0.0 */ public function setHost(string $host): self; @@ -35,7 +34,7 @@ public function setHost(string $host): self; * - on outgoing request, this is the remote instance. * * @return string - * @since 31.0.0 + * @experimental 31.0.0 */ public function getHost(): string; @@ -46,7 +45,7 @@ public function getHost(): string; * @param string|int|float $value * * @return self - * @since 31.0.0 + * @experimental 31.0.0 */ public function addHeader(string $key, string|int|float $value): self; @@ -54,7 +53,7 @@ public function addHeader(string $key, string|int|float $value): self; * returns list of headers value that will be added to the base request * * @return array - * @since 31.0.0 + * @experimental 31.0.0 */ public function getHeaders(): array; @@ -64,7 +63,7 @@ public function getHeaders(): array; * @param list $list * * @return self - * @since 31.0.0 + * @experimental 31.0.0 */ public function setHeaderList(array $list): self; @@ -72,7 +71,7 @@ public function setHeaderList(array $list): self; * returns ordered list of used headers in the Signature * * @return list - * @since 31.0.0 + * @experimental 31.0.0 */ public function getHeaderList(): array; @@ -82,7 +81,7 @@ public function getHeaderList(): array; * @param SignatureAlgorithm $algorithm * * @return self - * @since 31.0.0 + * @experimental 31.0.0 */ public function setAlgorithm(SignatureAlgorithm $algorithm): self; @@ -90,7 +89,7 @@ public function setAlgorithm(SignatureAlgorithm $algorithm): self; * returns the algorithm set to sign the signature * * @return SignatureAlgorithm - * @since 31.0.0 + * @experimental 31.0.0 */ public function getAlgorithm(): SignatureAlgorithm; @@ -100,7 +99,7 @@ public function getAlgorithm(): SignatureAlgorithm; * @return self * @throws SignatoryException * @throws SignatoryNotFoundException - * @since 31.0.0 + * @experimental 31.0.0 */ public function sign(): self; } diff --git a/lib/unstable/Security/Signature/ISignatoryManager.php b/lib/unstable/Security/Signature/ISignatoryManager.php index 20133de4c9ce3..e265b52f75588 100644 --- a/lib/unstable/Security/Signature/ISignatoryManager.php +++ b/lib/unstable/Security/Signature/ISignatoryManager.php @@ -16,7 +16,6 @@ * - confirm the authenticity of incoming signed request. * * @experimental 31.0.0 - * @since 31.0.0 */ interface ISignatoryManager { /** @@ -26,7 +25,7 @@ interface ISignatoryManager { * Must be unique. * * @return string - * @since 31.0.0 + * @experimental 31.0.0 */ public function getProviderId(): string; @@ -42,7 +41,7 @@ public function getProviderId(): string; * ] * * @return array - * @since 31.0.0 + * @experimental 31.0.0 */ public function getOptions(): array; @@ -52,7 +51,7 @@ public function getOptions(): array; * Used to sign outgoing request * * @return Signatory - * @since 31.0.0 + * @experimental 31.0.0 */ public function getLocalSignatory(): Signatory; @@ -65,7 +64,7 @@ public function getLocalSignatory(): Signatory; * @param string $remote * * @return Signatory|null must be NULL if no signatory is found - * @since 31.0.0 + * @experimental 31.0.0 */ public function getRemoteSignatory(string $remote): ?Signatory; } diff --git a/lib/unstable/Security/Signature/ISignatureManager.php b/lib/unstable/Security/Signature/ISignatureManager.php index c614a16cd92aa..b7a738d95ade2 100644 --- a/lib/unstable/Security/Signature/ISignatureManager.php +++ b/lib/unstable/Security/Signature/ISignatureManager.php @@ -42,7 +42,6 @@ * to ensure authenticity override protection. * * @experimental 31.0.0 - * @since 31.0.0 */ interface ISignatureManager { /** @@ -59,7 +58,7 @@ interface ISignatureManager { * @throws IncomingRequestException if anything looks wrong with the incoming request * @throws SignatureNotFoundException if incoming request is not signed * @throws SignatureException if signature could not be confirmed - * @since 31.0.0 + * @experimental 31.0.0 */ public function getIncomingSignedRequest(ISignatoryManager $signatoryManager, ?string $body = null): IIncomingSignedRequest; @@ -73,7 +72,7 @@ public function getIncomingSignedRequest(ISignatoryManager $signatoryManager, ?s * @param string $uri needed in the signature * * @return IOutgoingSignedRequest - * @since 31.0.0 + * @experimental 31.0.0 */ public function getOutgoingSignedRequest(ISignatoryManager $signatoryManager, string $content, string $method, string $uri): IOutgoingSignedRequest; @@ -87,7 +86,7 @@ public function getOutgoingSignedRequest(ISignatoryManager $signatoryManager, st * @param string $uri needed in the signature * * @return array new payload to be sent, including original payload and signature elements in headers - * @since 31.0.0 + * @experimental 31.0.0 */ public function signOutgoingRequestIClientPayload(ISignatoryManager $signatoryManager, array $payload, string $method, string $uri): array; @@ -99,7 +98,7 @@ public function signOutgoingRequestIClientPayload(ISignatoryManager $signatoryMa * * @return Signatory * @throws SignatoryNotFoundException if entry does not exist in local database - * @since 31.0.0 + * @experimental 31.0.0 */ public function getSignatory(string $host, string $account = ''): Signatory; @@ -110,7 +109,7 @@ public function getSignatory(string $host, string $account = ''): Signatory; * * @return string * @throws IdentityNotFoundException if hostname is not set - * @since 31.0.0 + * @experimental 31.0.0 */ public function generateKeyIdFromConfig(string $path): string; @@ -121,7 +120,7 @@ public function generateKeyIdFromConfig(string $path): string; * * @return string * @throws IdentityNotFoundException if identity cannot be extracted - * @since 31.0.0 + * @experimental 31.0.0 */ public function extractIdentityFromUri(string $uri): string; } diff --git a/lib/unstable/Security/Signature/ISignedRequest.php b/lib/unstable/Security/Signature/ISignedRequest.php index 6f9e143c579f5..e3c77c9767a81 100644 --- a/lib/unstable/Security/Signature/ISignedRequest.php +++ b/lib/unstable/Security/Signature/ISignedRequest.php @@ -8,6 +8,7 @@ */ namespace NCU\Security\Signature; +use NCU\Security\Signature\Enum\DigestAlgorithm; use NCU\Security\Signature\Exceptions\SignatoryNotFoundException; use NCU\Security\Signature\Exceptions\SignatureElementNotFoundException; use NCU\Security\Signature\Model\Signatory; @@ -19,22 +20,39 @@ * - to sign an outgoing request * * @experimental 31.0.0 - * @since 31.0.0 */ interface ISignedRequest { /** * payload of the request * * @return string - * @since 31.0.0 + * @experimental 31.0.0 */ public function getBody(): string; + /** + * set algorithm used to generate digest + * + * @param DigestAlgorithm $algorithm + * + * @return self + * @experimental 31.0.0 + */ + public function setDigestAlgorithm(DigestAlgorithm $algorithm): self; + + /** + * get algorithm used to generate digest + * + * @return DigestAlgorithm + * @experimental 31.0.0 + */ + public function getDigestAlgorithm(): DigestAlgorithm; + /** * checksum of the payload of the request * * @return string - * @since 31.0.0 + * @experimental 31.0.0 */ public function getDigest(): string; @@ -44,7 +62,7 @@ public function getDigest(): string; * @param array $elements * * @return self - * @since 31.0.0 + * @experimental 31.0.0 */ public function setSigningElements(array $elements): self; @@ -52,7 +70,7 @@ public function setSigningElements(array $elements): self; * get the list of elements in the Signature header of the request * * @return array - * @since 31.0.0 + * @experimental 31.0.0 */ public function getSigningElements(): array; @@ -61,7 +79,7 @@ public function getSigningElements(): array; * * @return string * @throws SignatureElementNotFoundException - * @since 31.0.0 + * @experimental 31.0.0 */ public function getSigningElement(string $key): string; @@ -71,7 +89,7 @@ public function getSigningElement(string $key): string; * @param array $data * * @return self - * @since 31.0.0 + * @experimental 31.0.0 */ public function setSignatureData(array $data): self; @@ -79,7 +97,7 @@ public function setSignatureData(array $data): self; * returns data used to generate signature * * @return array - * @since 31.0.0 + * @experimental 31.0.0 */ public function getSignatureData(): array; @@ -89,7 +107,7 @@ public function getSignatureData(): array; * @param string $signature * * @return self - * @since 31.0.0 + * @experimental 31.0.0 */ public function setSignature(string $signature): self; @@ -97,7 +115,7 @@ public function setSignature(string $signature): self; * get the signed version of the signature * * @return string - * @since 31.0.0 + * @experimental 31.0.0 */ public function getSignature(): string; @@ -106,7 +124,7 @@ public function getSignature(): string; * * @param Signatory $signatory * @return self - * @since 31.0.0 + * @experimental 31.0.0 */ public function setSignatory(Signatory $signatory): self; @@ -115,7 +133,7 @@ public function setSignatory(Signatory $signatory): self; * * @return Signatory * @throws SignatoryNotFoundException - * @since 31.0.0 + * @experimental 31.0.0 */ public function getSignatory(): Signatory; @@ -123,7 +141,7 @@ public function getSignatory(): Signatory; * returns if a signatory related to this request have been found and defined * * @return bool - * @since 31.0.0 + * @experimental 31.0.0 */ public function hasSignatory(): bool; } diff --git a/lib/unstable/Security/Signature/Model/Signatory.php b/lib/unstable/Security/Signature/Model/Signatory.php index 621cd5ac7ee63..7d11a90d24c6a 100644 --- a/lib/unstable/Security/Signature/Model/Signatory.php +++ b/lib/unstable/Security/Signature/Model/Signatory.php @@ -11,6 +11,7 @@ use JsonSerializable; use NCU\Security\Signature\Enum\SignatoryStatus; use NCU\Security\Signature\Enum\SignatoryType; +use NCU\Security\Signature\Exceptions\IdentityNotFoundException; use OCP\AppFramework\Db\Entity; /** @@ -21,18 +22,23 @@ * 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 setKeyIdSum(string $keyIdSum) + * @method string getKeyIdSum() * @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 int getType() + * @method void setType(int $type) + * @method int getStatus() + * @method void setStatus(int $status) * @method void setAccount(string $account) * @method string getAccount() * @method void setMetadata(array $metadata) @@ -41,12 +47,15 @@ * @method int getCreation() * @method void setLastUpdated(int $creation) * @method int getLastUpdated() + * @psalm-suppress PropertyNotSetInConstructor */ class Signatory extends Entity implements JsonSerializable { protected string $keyId = ''; protected string $keyIdSum = ''; protected string $providerId = ''; protected string $host = ''; + protected string $publicKey = ''; + protected string $privateKey = ''; protected string $account = ''; protected int $type = 9; protected int $status = 1; @@ -55,17 +64,11 @@ class Signatory extends Entity implements JsonSerializable { protected int $lastUpdated = 0; /** - * @param string $keyId - * @param string $publicKey - * @param string $privateKey - * @param bool $local + * @param bool $local only set to TRUE when managing local signatory * - * @since 31.0.0 + * @experimental 31.0.0 */ public function __construct( - string $keyId = '', - protected string $publicKey = '', - protected string $privateKey = '', private readonly bool $local = false, ) { $this->addType('providerId', 'string'); @@ -79,14 +82,13 @@ public function __construct( $this->addType('status', 'integer'); $this->addType('creation', 'integer'); $this->addType('lastUpdated', 'integer'); - - $this->setKeyId($keyId); } /** * @param string $keyId * - * @since 31.0.0 + * @experimental 31.0.0 + * @throws IdentityNotFoundException if identity cannot be extracted from keyId */ public function setKeyId(string $keyId): void { // if set as local (for current instance), we apply some filters. @@ -105,40 +107,42 @@ public function setKeyId(string $keyId): void { } } } - $this->keyId = $keyId; - $this->keyIdSum = hash('sha256', $keyId); + $this->setter('keyId', [$keyId]); // needed to trigger the update in database + $this->setKeyIdSum(hash('sha256', $keyId)); + + $this->setHost(self::extractIdentityFromUri($this->getKeyId())); } /** * @param SignatoryType $type - * @since 31.0.0 + * @experimental 31.0.0 */ - public function setType(SignatoryType $type): void { - $this->type = $type->value; + public function setSignatoryType(SignatoryType $type): void { + $this->setType($type->value); } /** * @return SignatoryType - * @since 31.0.0 + * @experimental 31.0.0 */ - public function getType(): SignatoryType { - return SignatoryType::from($this->type); + public function getSignatoryType(): SignatoryType { + return SignatoryType::from($this->getType()); } /** * @param SignatoryStatus $status - * @since 31.0.0 + * @experimental 31.0.0 */ - public function setStatus(SignatoryStatus $status): void { - $this->status = $status->value; + public function setSignatoryStatus(SignatoryStatus $status): void { + $this->setStatus($status->value); } /** * @return SignatoryStatus - * @since 31.0.0 + * @experimental 31.0.0 */ - public function getStatus(): SignatoryStatus { - return SignatoryStatus::from($this->status); + public function getSignatoryStatus(): SignatoryStatus { + return SignatoryStatus::from($this->getStatus()); } /** @@ -146,7 +150,7 @@ public function getStatus(): SignatoryStatus { * * @param string $key * @param string|int|float|bool|array $value - * @since 31.0.0 + * @experimental 31.0.0 */ public function setMetaValue(string $key, string|int|float|bool|array $value): void { $this->metadata[$key] = $value; @@ -154,7 +158,7 @@ public function setMetaValue(string $key, string|int|float|bool|array $value): v /** * @return array - * @since 31.0.0 + * @experimental 31.0.0 */ public function jsonSerialize(): array { return [ @@ -162,4 +166,28 @@ public function jsonSerialize(): array { 'publicKeyPem' => $this->getPublicKey() ]; } + + /** + * static is needed to make this easily callable from outside the model + * + * @param string $uri + * + * @return string + * @throws IdentityNotFoundException if identity cannot be extracted + * @since 31.0.0 + */ + public static function extractIdentityFromUri(string $uri): string { + $identity = parse_url($uri, PHP_URL_HOST); + $port = parse_url($uri, PHP_URL_PORT); + if ($identity === null || $identity === false) { + throw new IdentityNotFoundException('cannot extract identity from ' . $uri); + } + + if ($port !== null && $port !== false) { + $identity .= ':' . $port; + } + + return $identity; + } + }