diff options
author | Maxence Lange <maxence@artificial-owl.com> | 2024-12-02 11:30:37 -0100 |
---|---|---|
committer | Maxence Lange <maxence@artificial-owl.com> | 2024-12-04 09:30:55 -0100 |
commit | 948547bd5dbd181122333b8636f094638b036b39 (patch) | |
tree | 60c4f94ecf46f8805120c17064b0211aed01e8d0 /lib/private | |
parent | 4b0662005582e7a502b0de8e5e7e52f1675f3809 (diff) | |
download | nextcloud-server-948547bd5dbd181122333b8636f094638b036b39.tar.gz nextcloud-server-948547bd5dbd181122333b8636f094638b036b39.zip |
fix(ocm): signatory mapper
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
Diffstat (limited to 'lib/private')
6 files changed, 105 insertions, 70 deletions
diff --git a/lib/private/OCM/Model/OCMProvider.php b/lib/private/OCM/Model/OCMProvider.php index 32068efe3eb..fb13b7c0f93 100644 --- a/lib/private/OCM/Model/OCMProvider.php +++ b/lib/private/OCM/Model/OCMProvider.php @@ -183,7 +183,9 @@ class OCMProvider implements IOCMProvider { $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 909952a6b37..6b6917bcd4b 100644 --- a/lib/private/OCM/OCMSignatoryManager.php +++ b/lib/private/OCM/OCMSignatoryManager.php @@ -9,7 +9,9 @@ declare(strict_types=1); 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 @@ class OCMSignatoryManager implements ISignatoryManager { * @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 @@ class OCMSignatoryManager implements ISignatoryManager { } $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 @@ class OCMSignatoryManager implements ISignatoryManager { 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 fae8b897d5b..2a1aa82ac50 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 @@ class IncomingSignedRequest extends SignedRequest implements private readonly array $options = [], ) { parent::__construct($body); - $this->verifyHeadersFromRequest(); - $this->extractSignatureHeaderFromRequest(); + $this->verifyHeaders(); + $this->extractSignatureHeader(); + $this->reconstructSignatureData(); } /** @@ -59,7 +65,7 @@ class IncomingSignedRequest extends SignedRequest implements * @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 @@ class IncomingSignedRequest extends SignedRequest implements * * @throws IncomingRequestException */ - private function extractSignatureHeaderFromRequest(): void { + private function extractSignatureHeader(): void { $details = []; foreach (explode(',', $this->getRequest()->getHeader('Signature')) as $entry) { if ($entry === '' || !strpos($entry, '=')) { @@ -133,6 +139,36 @@ class IncomingSignedRequest extends SignedRequest implements } /** + * @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 * * @return IRequest @@ -214,7 +250,7 @@ class IncomingSignedRequest extends SignedRequest implements 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 8879821a029..dbfac3bfd34 100644 --- a/lib/private/Security/Signature/Model/OutgoingSignedRequest.php +++ b/lib/private/Security/Signature/Model/OutgoingSignedRequest.php @@ -9,6 +9,7 @@ declare(strict_types=1); 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 @@ class OutgoingSignedRequest extends SignedRequest implements $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 dd3c1de431d..214e43e8cb3 100644 --- a/lib/private/Security/Signature/Model/SignedRequest.php +++ b/lib/private/Security/Signature/Model/SignedRequest.php @@ -9,6 +9,7 @@ declare(strict_types=1); 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 @@ use NCU\Security\Signature\Model\Signatory; * @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)); } /** @@ -46,10 +46,36 @@ class SignedRequest implements ISignedRequest, JsonSerializable { /** * @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 + * * @return 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 @@ class SignedRequest implements ISignedRequest, JsonSerializable { 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 6247b7901fa..b04d683a3b9 100644 --- a/lib/private/Security/Signature/SignatureManager.php +++ b/lib/private/Security/Signature/SignatureManager.php @@ -111,7 +111,6 @@ class SignatureManager implements ISignatureManager { 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( @@ -128,44 +127,6 @@ class SignatureManager implements ISignatureManager { } /** - * 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 @@ class SignatureManager implements ISignatureManager { * @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 @@ class SignatureManager implements ISignatureManager { /** * @param Signatory $signatory - * @throws DBException */ private function insertSignatory(Signatory $signatory): void { + $time = time(); + $signatory->setCreation($time); + $signatory->setLastUpdated($time); $this->mapper->insert($signatory); } |