You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

149 lines
4.5 KiB

  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\PublicPrivateKeyPairs\Exceptions\KeyPairConflictException;
  9. use NCU\Security\PublicPrivateKeyPairs\Exceptions\KeyPairNotFoundException;
  10. use NCU\Security\PublicPrivateKeyPairs\IKeyPairManager;
  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\IIncomingSignedRequest;
  15. use NCU\Security\Signature\Model\ISignatory;
  16. use NCU\Security\Signature\Model\SignatoryType;
  17. use OC\Security\Signature\Model\Signatory;
  18. use OCP\IAppConfig;
  19. use OCP\IURLGenerator;
  20. use OCP\OCM\Exceptions\OCMProviderException;
  21. /**
  22. * @inheritDoc
  23. *
  24. * returns local signatory using IKeyPairManager
  25. * extract optional signatory (keyId+public key) from ocm discovery service on remote instance
  26. *
  27. * @since 31.0.0
  28. */
  29. class OCMSignatoryManager implements ISignatoryManager {
  30. public const PROVIDER_ID = 'ocm';
  31. public const APPCONFIG_SIGN_IDENTITY_EXTERNAL = 'ocm_signed_request_identity_external';
  32. public const APPCONFIG_SIGN_DISABLED = 'ocm_signed_request_disabled';
  33. public const APPCONFIG_SIGN_ENFORCED = 'ocm_signed_request_enforced';
  34. public function __construct(
  35. private readonly IAppConfig $appConfig,
  36. private readonly ISignatureManager $signatureManager,
  37. private readonly IURLGenerator $urlGenerator,
  38. private readonly IKeyPairManager $keyPairManager,
  39. private readonly OCMDiscoveryService $ocmDiscoveryService,
  40. ) {
  41. }
  42. /**
  43. * @inheritDoc
  44. *
  45. * @since 31.0.0
  46. * @return string
  47. */
  48. public function getProviderId(): string {
  49. return self::PROVIDER_ID;
  50. }
  51. /**
  52. * @inheritDoc
  53. *
  54. * @since 31.0.0
  55. * @return array
  56. */
  57. public function getOptions(): array {
  58. return [];
  59. }
  60. /**
  61. * @inheritDoc
  62. *
  63. * @return ISignatory
  64. * @throws KeyPairConflictException
  65. * @throws IdentityNotFoundException
  66. * @since 31.0.0
  67. */
  68. public function getLocalSignatory(): ISignatory {
  69. /**
  70. * TODO: manage multiple identity (external, internal, ...) to allow a limitation
  71. * based on the requested interface (ie. only accept shares from globalscale)
  72. */
  73. if ($this->appConfig->hasKey('core', self::APPCONFIG_SIGN_IDENTITY_EXTERNAL, true)) {
  74. $identity = $this->appConfig->getValueString('core', self::APPCONFIG_SIGN_IDENTITY_EXTERNAL, lazy: true);
  75. $keyId = 'https://' . $identity . '/ocm#signature';
  76. } else {
  77. $keyId = $this->generateKeyId();
  78. }
  79. try {
  80. $keyPair = $this->keyPairManager->getKeyPair('core', 'ocm_external');
  81. } catch (KeyPairNotFoundException) {
  82. $keyPair = $this->keyPairManager->generateKeyPair('core', 'ocm_external');
  83. }
  84. return new Signatory($keyId, $keyPair->getPublicKey(), $keyPair->getPrivateKey(), local: true);
  85. }
  86. /**
  87. * - tries to generate a keyId using global configuration (from signature manager) if available
  88. * - generate a keyId using the current route to ocm shares
  89. *
  90. * @return string
  91. * @throws IdentityNotFoundException
  92. */
  93. private function generateKeyId(): string {
  94. try {
  95. return $this->signatureManager->generateKeyIdFromConfig('/ocm#signature');
  96. } catch (IdentityNotFoundException) {
  97. }
  98. $url = $this->urlGenerator->linkToRouteAbsolute('cloud_federation_api.requesthandlercontroller.addShare');
  99. $identity = $this->signatureManager->extractIdentityFromUri($url);
  100. // catching possible subfolder to create a keyId like 'https://hostname/subfolder/ocm#signature
  101. $path = parse_url($url, PHP_URL_PATH);
  102. $pos = strpos($path, '/ocm/shares');
  103. $sub = ($pos) ? substr($path, 0, $pos) : '';
  104. return 'https://' . $identity . $sub . '/ocm#signature';
  105. }
  106. /**
  107. * @inheritDoc
  108. *
  109. * @param IIncomingSignedRequest $signedRequest
  110. *
  111. * @return ISignatory|null must be NULL if no signatory is found
  112. * @throws OCMProviderException on fail to discover ocm services
  113. * @since 31.0.0
  114. */
  115. public function getRemoteSignatory(IIncomingSignedRequest $signedRequest): ?ISignatory {
  116. return $this->getRemoteSignatoryFromHost($signedRequest->getOrigin());
  117. }
  118. /**
  119. * As host is enough to generate signatory using OCMDiscoveryService
  120. *
  121. * @param string $host
  122. *
  123. * @return ISignatory|null
  124. * @throws OCMProviderException on fail to discover ocm services
  125. * @since 31.0.0
  126. */
  127. public function getRemoteSignatoryFromHost(string $host): ?ISignatory {
  128. $ocmProvider = $this->ocmDiscoveryService->discover($host, true);
  129. $signatory = $ocmProvider->getSignatory();
  130. return $signatory?->setType(SignatoryType::TRUSTED);
  131. }
  132. }