aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Security/Signature/Model/IncomingSignedRequest.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Security/Signature/Model/IncomingSignedRequest.php')
-rw-r--r--lib/private/Security/Signature/Model/IncomingSignedRequest.php179
1 files changed, 109 insertions, 70 deletions
diff --git a/lib/private/Security/Signature/Model/IncomingSignedRequest.php b/lib/private/Security/Signature/Model/IncomingSignedRequest.php
index 8fe83a7b09b..77914d1e3b2 100644
--- a/lib/private/Security/Signature/Model/IncomingSignedRequest.php
+++ b/lib/private/Security/Signature/Model/IncomingSignedRequest.php
@@ -10,11 +10,14 @@ namespace OC\Security\Signature\Model;
use JsonSerializable;
use NCU\Security\Signature\Exceptions\IdentityNotFoundException;
-use NCU\Security\Signature\Exceptions\IncomingRequestNotFoundException;
+use NCU\Security\Signature\Exceptions\IncomingRequestException;
use NCU\Security\Signature\Exceptions\SignatoryException;
+use NCU\Security\Signature\Exceptions\SignatureElementNotFoundException;
+use NCU\Security\Signature\Exceptions\SignatureNotFoundException;
use NCU\Security\Signature\ISignatureManager;
use NCU\Security\Signature\Model\IIncomingSignedRequest;
use NCU\Security\Signature\Model\ISignatory;
+use OC\Security\Signature\SignatureManager;
use OCP\IRequest;
/**
@@ -26,77 +29,134 @@ use OCP\IRequest;
class IncomingSignedRequest extends SignedRequest implements
IIncomingSignedRequest,
JsonSerializable {
- private ?IRequest $request = null;
- private int $time = 0;
private string $origin = '';
- private string $estimatedSignature = '';
/**
- * @inheritDoc
+ * @throws IncomingRequestException if incoming request is wrongly signed
+ * @throws SignatureNotFoundException if signature is not fully implemented
+ */
+ public function __construct(
+ string $body,
+ private readonly IRequest $request,
+ private readonly array $options = [],
+ ) {
+ parent::__construct($body);
+ $this->verifyHeadersFromRequest();
+ $this->extractSignatureHeaderFromRequest();
+ }
+
+ /**
+ * confirm that:
*
- * @param ISignatory $signatory
+ * - date is available in the header and its value is less than 5 minutes old
+ * - content-length is available and is the same as the payload size
+ * - digest is available and fit the checksum of the payload
*
- * @return $this
- * @throws SignatoryException
- * @throws IdentityNotFoundException
- * @since 31.0.0
+ * @throws IncomingRequestException
+ * @throws SignatureNotFoundException
*/
- public function setSignatory(ISignatory $signatory): self {
- $identity = \OCP\Server::get(ISignatureManager::class)->extractIdentityFromUri($signatory->getKeyId());
- if ($identity !== $this->getOrigin()) {
- throw new SignatoryException('keyId from provider is different from the one from signed request');
+ private function verifyHeadersFromRequest(): void {
+ // confirm presence of date, content-length, digest and Signature
+ $date = $this->getRequest()->getHeader('date');
+ if ($date === '') {
+ throw new SignatureNotFoundException('missing date in header');
+ }
+ $contentLength = $this->getRequest()->getHeader('content-length');
+ if ($contentLength === '') {
+ throw new SignatureNotFoundException('missing content-length in header');
+ }
+ $digest = $this->getRequest()->getHeader('digest');
+ if ($digest === '') {
+ throw new SignatureNotFoundException('missing digest in header');
+ }
+ if ($this->getRequest()->getHeader('Signature') === '') {
+ throw new SignatureNotFoundException('missing Signature in header');
}
- parent::setSignatory($signatory);
- return $this;
+ // confirm date
+ try {
+ $dTime = new \DateTime($date);
+ $requestTime = $dTime->getTimestamp();
+ } catch (\Exception) {
+ throw new IncomingRequestException('datetime exception');
+ }
+ if ($requestTime < (time() - ($this->options['ttl'] ?? SignatureManager::DATE_TTL))) {
+ throw new IncomingRequestException('object is too old');
+ }
+
+ // confirm validity of content-length
+ if (strlen($this->getBody()) !== (int)$contentLength) {
+ throw new IncomingRequestException('inexact content-length in header');
+ }
+
+ // confirm digest value, based on body
+ if ($digest !== $this->getDigest()) {
+ throw new IncomingRequestException('invalid value for digest in header');
+ }
}
/**
- * @inheritDoc
+ * extract data from the header entry 'Signature' and convert its content from string to an array
+ * also confirm that it contains the minimum mandatory information
*
- * @param IRequest $request
- * @return IIncomingSignedRequest
- * @since 31.0.0
+ * @throws IncomingRequestException
*/
- public function setRequest(IRequest $request): IIncomingSignedRequest {
- $this->request = $request;
- return $this;
+ private function extractSignatureHeaderFromRequest(): void {
+ $sign = [];
+ foreach (explode(',', $this->getRequest()->getHeader('Signature')) as $entry) {
+ if ($entry === '' || !strpos($entry, '=')) {
+ continue;
+ }
+
+ [$k, $v] = explode('=', $entry, 2);
+ preg_match('/"([^"]+)"/', $v, $var);
+ if ($var[0] !== '') {
+ $v = trim($var[0], '"');
+ }
+ $sign[$k] = $v;
+ }
+
+ $this->setSignatureElements($sign);
+
+ try {
+ // confirm keys are in the Signature header
+ $this->getSignatureElement('keyId');
+ $this->getSignatureElement('headers');
+ $this->setSignedSignature($this->getSignatureElement('signature'));
+ } catch (SignatureElementNotFoundException $e) {
+ throw new IncomingRequestException($e->getMessage());
+ }
}
/**
* @inheritDoc
*
* @return IRequest
- * @throws IncomingRequestNotFoundException
* @since 31.0.0
*/
public function getRequest(): IRequest {
- if ($this->request === null) {
- throw new IncomingRequestNotFoundException();
- }
return $this->request;
}
/**
* @inheritDoc
*
- * @param int $time
- * @return IIncomingSignedRequest
- * @since 31.0.0
- */
- public function setTime(int $time): IIncomingSignedRequest {
- $this->time = $time;
- return $this;
- }
-
- /**
- * @inheritDoc
+ * @param ISignatory $signatory
*
- * @return int
+ * @return $this
+ * @throws IdentityNotFoundException
+ * @throws IncomingRequestException
+ * @throws SignatoryException
* @since 31.0.0
*/
- public function getTime(): int {
- return $this->time;
+ public function setSignatory(ISignatory $signatory): self {
+ $identity = \OCP\Server::get(ISignatureManager::class)->extractIdentityFromUri($signatory->getKeyId());
+ if ($identity !== $this->getOrigin()) {
+ throw new SignatoryException('keyId from provider is different from the one from signed request');
+ }
+
+ parent::setSignatory($signatory);
+ return $this;
}
/**
@@ -115,9 +175,13 @@ class IncomingSignedRequest extends SignedRequest implements
* @inheritDoc
*
* @return string
+ * @throws IncomingRequestException
* @since 31.0.0
*/
public function getOrigin(): string {
+ if ($this->origin === '') {
+ throw new IncomingRequestException('empty origin');
+ }
return $this->origin;
}
@@ -126,44 +190,19 @@ class IncomingSignedRequest extends SignedRequest implements
* keyId is a mandatory entry in the headers of a signed request.
*
* @return string
+ * @throws SignatureElementNotFoundException
* @since 31.0.0
*/
public function getKeyId(): string {
- return $this->getSignatureHeader()['keyId'] ?? '';
- }
-
- /**
- * @inheritDoc
- *
- * @param string $signature
- * @return IIncomingSignedRequest
- * @since 31.0.0
- */
- public function setEstimatedSignature(string $signature): IIncomingSignedRequest {
- $this->estimatedSignature = $signature;
- return $this;
- }
-
- /**
- * @inheritDoc
- *
- * @return string
- * @since 31.0.0
- */
- public function getEstimatedSignature(): string {
- return $this->estimatedSignature;
+ return $this->getSignatureElement('keyId');
}
public function jsonSerialize(): array {
return array_merge(
parent::jsonSerialize(),
[
- 'body' => $this->getBody(),
- 'time' => $this->getTime(),
- 'incomingRequest' => $this->request ?? false,
- 'origin' => $this->getOrigin(),
- 'keyId' => $this->getKeyId(),
- 'estimatedSignature' => $this->getEstimatedSignature(),
+ 'options' => $this->options,
+ 'origin' => $this->origin,
]
);
}