OCMSignatoryManager.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OC\OCM;
  8. use NCU\Security\Signature\Enum\DigestAlgorithm;
  9. use NCU\Security\Signature\Enum\SignatoryType;
  10. use NCU\Security\Signature\Enum\SignatureAlgorithm;
  11. use NCU\Security\Signature\Exceptions\IdentityNotFoundException;
  12. use NCU\Security\Signature\ISignatoryManager;
  13. use NCU\Security\Signature\ISignatureManager;
  14. use NCU\Security\Signature\Model\Signatory;
  15. use OC\Security\IdentityProof\Manager;
  16. use OCP\IAppConfig;
  17. use OCP\IURLGenerator;
  18. use OCP\OCM\Exceptions\OCMProviderException;
  19. use Psr\Log\LoggerInterface;
  20. /**
  21. * @inheritDoc
  22. *
  23. * returns local signatory using IKeyPairManager
  24. * extract optional signatory (keyId+public key) from ocm discovery service on remote instance
  25. *
  26. * @since 31.0.0
  27. */
  28. class OCMSignatoryManager implements ISignatoryManager {
  29. public const PROVIDER_ID = 'ocm';
  30. public const APPCONFIG_SIGN_IDENTITY_EXTERNAL = 'ocm_signed_request_identity_external';
  31. public const APPCONFIG_SIGN_DISABLED = 'ocm_signed_request_disabled';
  32. public const APPCONFIG_SIGN_ENFORCED = 'ocm_signed_request_enforced';
  33. public function __construct(
  34. private readonly IAppConfig $appConfig,
  35. private readonly ISignatureManager $signatureManager,
  36. private readonly IURLGenerator $urlGenerator,
  37. private readonly Manager $identityProofManager,
  38. private readonly OCMDiscoveryService $ocmDiscoveryService,
  39. private readonly LoggerInterface $logger,
  40. ) {
  41. }
  42. /**
  43. * @inheritDoc
  44. *
  45. * @return string
  46. * @since 31.0.0
  47. */
  48. public function getProviderId(): string {
  49. return self::PROVIDER_ID;
  50. }
  51. /**
  52. * @inheritDoc
  53. *
  54. * @return array
  55. * @since 31.0.0
  56. */
  57. public function getOptions(): array {
  58. return [
  59. 'algorithm' => SignatureAlgorithm::RSA_SHA512,
  60. 'digestAlgorithm' => DigestAlgorithm::SHA512,
  61. 'extraSignatureHeaders' => [],
  62. 'ttl' => 300,
  63. 'dateHeader' => 'D, d M Y H:i:s T',
  64. 'ttlSignatory' => 86400 * 3,
  65. 'bodyMaxSize' => 50000,
  66. ];
  67. }
  68. /**
  69. * @inheritDoc
  70. *
  71. * @return Signatory
  72. * @throws IdentityNotFoundException
  73. * @since 31.0.0
  74. */
  75. public function getLocalSignatory(): Signatory {
  76. /**
  77. * TODO: manage multiple identity (external, internal, ...) to allow a limitation
  78. * based on the requested interface (ie. only accept shares from globalscale)
  79. */
  80. if ($this->appConfig->hasKey('core', self::APPCONFIG_SIGN_IDENTITY_EXTERNAL, true)) {
  81. $identity = $this->appConfig->getValueString('core', self::APPCONFIG_SIGN_IDENTITY_EXTERNAL, lazy: true);
  82. $keyId = 'https://' . $identity . '/ocm#signature';
  83. } else {
  84. $keyId = $this->generateKeyId();
  85. }
  86. if (!$this->identityProofManager->hasAppKey('core', 'ocm_external')) {
  87. $this->identityProofManager->generateAppKey('core', 'ocm_external', [
  88. 'algorithm' => 'rsa',
  89. 'private_key_bits' => 2048,
  90. 'private_key_type' => OPENSSL_KEYTYPE_RSA,
  91. ]);
  92. }
  93. $keyPair = $this->identityProofManager->getAppKey('core', 'ocm_external');
  94. $signatory = new Signatory(true);
  95. $signatory->setKeyId($keyId);
  96. $signatory->setPublicKey($keyPair->getPublic());
  97. $signatory->setPrivateKey($keyPair->getPrivate());
  98. return $signatory;
  99. }
  100. /**
  101. * - tries to generate a keyId using global configuration (from signature manager) if available
  102. * - generate a keyId using the current route to ocm shares
  103. *
  104. * @return string
  105. * @throws IdentityNotFoundException
  106. */
  107. private function generateKeyId(): string {
  108. try {
  109. return $this->signatureManager->generateKeyIdFromConfig('/ocm#signature');
  110. } catch (IdentityNotFoundException) {
  111. }
  112. $url = $this->urlGenerator->linkToRouteAbsolute('cloud_federation_api.requesthandlercontroller.addShare');
  113. $identity = $this->signatureManager->extractIdentityFromUri($url);
  114. // catching possible subfolder to create a keyId like 'https://hostname/subfolder/ocm#signature
  115. $path = parse_url($url, PHP_URL_PATH);
  116. $pos = strpos($path, '/ocm/shares');
  117. $sub = ($pos) ? substr($path, 0, $pos) : '';
  118. return 'https://' . $identity . $sub . '/ocm#signature';
  119. }
  120. /**
  121. * @inheritDoc
  122. *
  123. * @param string $remote
  124. *
  125. * @return Signatory|null must be NULL if no signatory is found
  126. * @since 31.0.0
  127. */
  128. public function getRemoteSignatory(string $remote): ?Signatory {
  129. try {
  130. $ocmProvider = $this->ocmDiscoveryService->discover($remote, true);
  131. /**
  132. * @experimental 31.0.0
  133. * @psalm-suppress UndefinedInterfaceMethod
  134. */
  135. $signatory = $ocmProvider->getSignatory();
  136. $signatory?->setSignatoryType(SignatoryType::TRUSTED);
  137. return $signatory;
  138. } catch (OCMProviderException $e) {
  139. $this->logger->warning('fail to get remote signatory', ['exception' => $e, 'remote' => $remote]);
  140. return null;
  141. }
  142. }
  143. }