nextcloud/lib/private/Security/Signature/Model/SignedRequest.php
Micke Nordin cc9e0ba582 fix(http-sig): make setSignature public and skip third-party-dependent test
Two CI failures introduced by the test additions in this PR:

1. testEd25519VerifyAcceptedWhenSodiumLoaded calls setSignature() to inject
   an externally-produced Ed25519 signature (since Algorithm::sign() rejects
   Ed25519 by design). setSignature was declared protected, so the test
   couldn't call it from outside the class hierarchy. Make it public —
   SignedRequest lives in the OC\ private namespace, so this widens
   internal-only visibility, not the public API surface.

2. testParseKeyRejectsContradictoryAlg expected firebase/php-jwt's
   JWK::parseKey() to throw on a kty=OKP/crv=Ed25519/alg=ES256 key. The
   current firebase/php-jwt version does not validate that coherence at
   parse time, so the test now fails to see any throwable. The actual
   security check happens at Algorithm::verify() time and is covered by
   testVerifyEd25519KeyAgainstES256Alg right above it. Skip the parse-time
   test with a comment pointing at the verify-time coverage.

Signed-off-by: Micke Nordin <kano@sunet.se>
2026-05-27 11:03:55 +02:00

228 lines
4.4 KiB
PHP

<?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 OCP\Security\Signature\Enum\DigestAlgorithm;
use OCP\Security\Signature\Exceptions\SignatoryNotFoundException;
use OCP\Security\Signature\Exceptions\SignatureElementNotFoundException;
use OCP\Security\Signature\ISignedRequest;
use OCP\Security\Signature\Model\Signatory;
/**
* @inheritDoc
*
* @since 31.0.0
*/
class SignedRequest implements ISignedRequest, JsonSerializable {
private string $digest = '';
private DigestAlgorithm $digestAlgorithm = DigestAlgorithm::SHA256;
private array $signingElements = [];
private array $signatureData = [];
private string $signature = '';
private ?Signatory $signatory = null;
public function __construct(
private readonly string $body,
) {
}
/**
* @inheritDoc
*
* @return string
* @since 31.0.0
*/
#[\Override]
public function getBody(): string {
return $this->body;
}
/**
* set algorithm used to generate digest
*
* @param DigestAlgorithm $algorithm
*
* @return self
* @since 31.0.0
*/
protected function setDigestAlgorithm(DigestAlgorithm $algorithm): self {
$this->digestAlgorithm = $algorithm;
return $this;
}
/**
* @inheritDoc
*
* @return DigestAlgorithm
* @since 31.0.0
*/
#[\Override]
public function getDigestAlgorithm(): DigestAlgorithm {
return $this->digestAlgorithm;
}
/**
* @inheritDoc
*
* @return string
* @since 31.0.0
*/
#[\Override]
public function getDigest(): string {
if ($this->digest === '') {
$this->digest = $this->digestAlgorithm->value . '='
. base64_encode(hash($this->digestAlgorithm->getHashingAlgorithm(), $this->body, true));
}
return $this->digest;
}
/**
* @inheritDoc
*
* @param array $elements
*
* @return self
* @since 31.0.0
*/
#[\Override]
public function setSigningElements(array $elements): self {
$this->signingElements = $elements;
return $this;
}
/**
* @inheritDoc
*
* @return array
* @since 31.0.0
*/
#[\Override]
public function getSigningElements(): array {
return $this->signingElements;
}
/**
* @param string $key
*
* @return string
* @throws SignatureElementNotFoundException
* @since 31.0.0
*
*/
#[\Override]
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->signingElements[$key];
}
/**
* store data used to generate signature
*
* @param array $data
*
* @return self
* @since 31.0.0
*/
protected function setSignatureData(array $data): self {
$this->signatureData = $data;
return $this;
}
/**
* @inheritDoc
*
* @return array
* @since 31.0.0
*/
#[\Override]
public function getSignatureData(): array {
return $this->signatureData;
}
/**
* set the signed version of the signature
*
* @param string $signature
*
* @return self
* @since 31.0.0
*/
public function setSignature(string $signature): self {
$this->signature = $signature;
return $this;
}
/**
* @inheritDoc
*
* @return string
* @since 31.0.0
*/
#[\Override]
public function getSignature(): string {
return $this->signature;
}
/**
* @inheritDoc
*
* @param Signatory $signatory
* @return self
* @since 31.0.0
*/
#[\Override]
public function setSignatory(Signatory $signatory): self {
$this->signatory = $signatory;
return $this;
}
/**
* @inheritDoc
*
* @return Signatory
* @throws SignatoryNotFoundException
* @since 31.0.0
*/
#[\Override]
public function getSignatory(): Signatory {
if ($this->signatory === null) {
throw new SignatoryNotFoundException();
}
return $this->signatory;
}
/**
* @inheritDoc
*
* @return bool
* @since 31.0.0
*/
#[\Override]
public function hasSignatory(): bool {
return ($this->signatory !== null);
}
#[\Override]
public function jsonSerialize(): array {
return [
'body' => $this->body,
'digest' => $this->getDigest(),
'digestAlgorithm' => $this->getDigestAlgorithm()->value,
'signingElements' => $this->signingElements,
'signatureData' => $this->signatureData,
'signature' => $this->signature,
'signatory' => $this->signatory ?? false,
];
}
}