aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private
diff options
context:
space:
mode:
authorMaxence Lange <maxence@artificial-owl.com>2024-12-02 11:30:37 -0100
committerMaxence Lange <maxence@artificial-owl.com>2024-12-04 09:30:55 -0100
commit948547bd5dbd181122333b8636f094638b036b39 (patch)
tree60c4f94ecf46f8805120c17064b0211aed01e8d0 /lib/private
parent4b0662005582e7a502b0de8e5e7e52f1675f3809 (diff)
downloadnextcloud-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')
-rw-r--r--lib/private/OCM/Model/OCMProvider.php4
-rw-r--r--lib/private/OCM/OCMSignatoryManager.php21
-rw-r--r--lib/private/Security/Signature/Model/IncomingSignedRequest.php48
-rw-r--r--lib/private/Security/Signature/Model/OutgoingSignedRequest.php6
-rw-r--r--lib/private/Security/Signature/Model/SignedRequest.php41
-rw-r--r--lib/private/Security/Signature/SignatureManager.php55
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);
}