mirror of
https://github.com/nextcloud/server.git
synced 2026-02-27 18:37:17 +01:00
fix(ocm): switching to IdentityProof
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
This commit is contained in:
@@ -8,7 +8,7 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace OCA\CloudFederationAPI;
|
||||
|
||||
use NCU\Security\PublicPrivateKeyPairs\Exceptions\KeyPairException;
|
||||
use NCU\Security\Signature\Exceptions\IdentityNotFoundException;
|
||||
use NCU\Security\Signature\Exceptions\SignatoryException;
|
||||
use OC\OCM\OCMSignatoryManager;
|
||||
use OCP\Capabilities\ICapability;
|
||||
@@ -79,7 +79,7 @@ class Capabilities implements ICapability {
|
||||
} else {
|
||||
$this->logger->debug('ocm public key feature disabled');
|
||||
}
|
||||
} catch (SignatoryException|KeyPairException $e) {
|
||||
} catch (SignatoryException|IdentityNotFoundException $e) {
|
||||
$this->logger->warning('cannot generate local signatory', ['exception' => $e]);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
namespace OCA\CloudFederationAPI\Controller;
|
||||
|
||||
use NCU\Security\Signature\Exceptions\IdentityNotFoundException;
|
||||
use NCU\Security\Signature\Exceptions\IncomingRequestException;
|
||||
use NCU\Security\Signature\Exceptions\SignatoryNotFoundException;
|
||||
use NCU\Security\Signature\Exceptions\SignatureException;
|
||||
@@ -14,6 +15,7 @@ use NCU\Security\Signature\Model\IIncomingSignedRequest;
|
||||
use OC\OCM\OCMSignatoryManager;
|
||||
use OCA\CloudFederationAPI\Config;
|
||||
use OCA\CloudFederationAPI\ResponseDefinitions;
|
||||
use OCA\FederatedFileSharing\AddressHandler;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\BruteForceProtection;
|
||||
@@ -60,6 +62,7 @@ class RequestHandlerController extends Controller {
|
||||
private IURLGenerator $urlGenerator,
|
||||
private ICloudFederationProviderManager $cloudFederationProviderManager,
|
||||
private Config $config,
|
||||
private readonly AddressHandler $addressHandler,
|
||||
private readonly IAppConfig $appConfig,
|
||||
private ICloudFederationFactory $factory,
|
||||
private ICloudIdManager $cloudIdManager,
|
||||
@@ -289,6 +292,7 @@ class RequestHandlerController extends Controller {
|
||||
$response->throttle();
|
||||
return $response;
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->warning('incoming notification exception', ['exception' => $e]);
|
||||
return new JSONResponse(
|
||||
[
|
||||
'message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl(),
|
||||
@@ -376,7 +380,7 @@ class RequestHandlerController extends Controller {
|
||||
$body = json_decode($signedRequest->getBody(), true) ?? [];
|
||||
$entry = trim($body[$key] ?? '', '@');
|
||||
if ($this->getHostFromFederationId($entry) !== $signedRequest->getOrigin()) {
|
||||
throw new IncomingRequestException('share initiation from different instance');
|
||||
throw new IncomingRequestException('share initiation (' . $signedRequest->getOrigin() . ') from different instance (' . $entry . ') [key=' . $key . ']');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +395,6 @@ class RequestHandlerController extends Controller {
|
||||
* @param IIncomingSignedRequest|null $signedRequest
|
||||
* @param string $token
|
||||
*
|
||||
* @return void
|
||||
* @throws IncomingRequestException
|
||||
*/
|
||||
private function confirmShareOrigin(?IIncomingSignedRequest $signedRequest, string $token): void {
|
||||
@@ -401,8 +404,23 @@ class RequestHandlerController extends Controller {
|
||||
|
||||
$provider = $this->shareProviderFactory->getProviderForType(IShare::TYPE_REMOTE);
|
||||
$share = $provider->getShareByToken($token);
|
||||
$entry = $share->getSharedWith();
|
||||
try {
|
||||
$this->confirmShareEntry($signedRequest, $share->getSharedWith());
|
||||
} catch (IncomingRequestException) {
|
||||
// notification might come from the instance that owns the share
|
||||
$this->logger->debug('could not confirm origin on sharedWith (' . $share->getSharedWIth() . '); going with shareOwner (' . $share->getShareOwner() . ')');
|
||||
$this->confirmShareEntry($signedRequest, $share->getShareOwner());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IIncomingSignedRequest|null $signedRequest
|
||||
* @param string $entry
|
||||
*
|
||||
* @return void
|
||||
* @throws IncomingRequestException
|
||||
*/
|
||||
private function confirmShareEntry(?IIncomingSignedRequest $signedRequest, string $entry): void {
|
||||
$instance = $this->getHostFromFederationId($entry);
|
||||
if ($signedRequest === null) {
|
||||
try {
|
||||
@@ -412,7 +430,7 @@ class RequestHandlerController extends Controller {
|
||||
return;
|
||||
}
|
||||
} elseif ($instance !== $signedRequest->getOrigin()) {
|
||||
throw new IncomingRequestException('token sharedWith from different instance');
|
||||
throw new IncomingRequestException('token sharedWith (' . $instance . ') not linked to origin (' . $signedRequest->getOrigin() . ')');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,20 +441,16 @@ class RequestHandlerController extends Controller {
|
||||
*/
|
||||
private function getHostFromFederationId(string $entry): string {
|
||||
if (!str_contains($entry, '@')) {
|
||||
throw new IncomingRequestException('entry does not contains @');
|
||||
throw new IncomingRequestException('entry ' . $entry . ' does not contains @');
|
||||
}
|
||||
[, $rightPart] = explode('@', $entry, 2);
|
||||
$rightPart = substr($entry, strrpos($entry, '@') + 1);
|
||||
|
||||
$host = parse_url($rightPart, PHP_URL_HOST);
|
||||
$port = parse_url($rightPart, PHP_URL_PORT);
|
||||
if ($port !== null && $port !== false) {
|
||||
$host .= ':' . $port;
|
||||
// in case the full scheme is sent; getting rid of it
|
||||
$rightPart = $this->addressHandler->removeProtocolFromUrl($rightPart);
|
||||
try {
|
||||
return $this->signatureManager->extractIdentityFromUri('https://' . $rightPart);
|
||||
} catch (IdentityNotFoundException) {
|
||||
throw new IncomingRequestException('invalid host within federation id: ' . $entry);
|
||||
}
|
||||
|
||||
if (is_string($host) && $host !== '') {
|
||||
return $host;
|
||||
}
|
||||
|
||||
throw new IncomingRequestException('host is empty');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,7 +250,8 @@ class FederatedShareProvider implements IShareProvider {
|
||||
$remote,
|
||||
$shareWith,
|
||||
$share->getPermissions(),
|
||||
$share->getNode()->getName()
|
||||
$share->getNode()->getName(),
|
||||
$share->getShareType(),
|
||||
);
|
||||
|
||||
return [$token, $remoteId];
|
||||
|
||||
@@ -108,12 +108,13 @@ class Notifications {
|
||||
* @throws HintException
|
||||
* @throws \OC\ServerNotAvailableException
|
||||
*/
|
||||
public function requestReShare($token, $id, $shareId, $remote, $shareWith, $permission, $filename) {
|
||||
public function requestReShare($token, $id, $shareId, $remote, $shareWith, $permission, $filename, $shareType) {
|
||||
$fields = [
|
||||
'shareWith' => $shareWith,
|
||||
'token' => $token,
|
||||
'permission' => $permission,
|
||||
'remoteId' => $shareId,
|
||||
'shareType' => $shareType,
|
||||
];
|
||||
|
||||
$ocmFields = $fields;
|
||||
|
||||
@@ -38,7 +38,7 @@ class FederationContext implements Context, SnippetAcceptingContext {
|
||||
|
||||
$port = getenv('PORT_FED');
|
||||
|
||||
self::$phpFederatedServerPid = exec('php -S localhost:' . $port . ' -t ../../ >/dev/null & echo $!');
|
||||
self::$phpFederatedServerPid = exec('PHP_CLI_SERVER_WORKERS=2 php -S localhost:' . $port . ' -t ../../ >/dev/null & echo $!');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,27 @@ Feature: cleanup-remote-storage
|
||||
Background:
|
||||
Given using api version "1"
|
||||
|
||||
Scenario: cleanup remote storage with no storage
|
||||
Given Using server "LOCAL"
|
||||
And user "user0" exists
|
||||
Given Using server "REMOTE"
|
||||
And user "user1" exists
|
||||
# Rename file so it has a unique name in the target server (as the target
|
||||
# server may have its own /textfile0.txt" file)
|
||||
And User "user1" copies file "/textfile0.txt" to "/remote-share.txt"
|
||||
And User "user1" from server "REMOTE" shares "/remote-share.txt" with user "user0" from server "LOCAL"
|
||||
And As an "user1"
|
||||
And Deleting last share
|
||||
And the OCS status code should be "100"
|
||||
And the HTTP status code should be "200"
|
||||
And Deleting last share
|
||||
And Using server "LOCAL"
|
||||
When invoking occ with "sharing:cleanup-remote-storage"
|
||||
Then the command was successful
|
||||
And the command output contains the text "0 remote storage(s) need(s) to be checked"
|
||||
And the command output contains the text "0 remote share(s) exist"
|
||||
And the command output contains the text "no storages deleted"
|
||||
|
||||
Scenario: cleanup remote storage with active storages
|
||||
Given Using server "LOCAL"
|
||||
And user "user0" exists
|
||||
@@ -35,9 +56,6 @@ Feature: cleanup-remote-storage
|
||||
# server may have its own /textfile0.txt" file)
|
||||
And User "user1" copies file "/textfile0.txt" to "/remote-share.txt"
|
||||
And User "user1" from server "REMOTE" shares "/remote-share.txt" with user "user0" from server "LOCAL"
|
||||
And As an "user1"
|
||||
And sending "GET" to "/apps/files_sharing/api/v1/shares"
|
||||
And the list of returned shares has 1 shares
|
||||
And Using server "LOCAL"
|
||||
# Accept and download the file to ensure that a storage is created for the
|
||||
# federated share
|
||||
|
||||
@@ -1902,8 +1902,6 @@ return array(
|
||||
'OC\\Security\\Ip\\Range' => $baseDir . '/lib/private/Security/Ip/Range.php',
|
||||
'OC\\Security\\Ip\\RemoteAddress' => $baseDir . '/lib/private/Security/Ip/RemoteAddress.php',
|
||||
'OC\\Security\\Normalizer\\IpAddress' => $baseDir . '/lib/private/Security/Normalizer/IpAddress.php',
|
||||
'OC\\Security\\PublicPrivateKeyPairs\\KeyPairManager' => $baseDir . '/lib/private/Security/PublicPrivateKeyPairs/KeyPairManager.php',
|
||||
'OC\\Security\\PublicPrivateKeyPairs\\Model\\KeyPair' => $baseDir . '/lib/private/Security/PublicPrivateKeyPairs/Model/KeyPair.php',
|
||||
'OC\\Security\\RateLimiting\\Backend\\DatabaseBackend' => $baseDir . '/lib/private/Security/RateLimiting/Backend/DatabaseBackend.php',
|
||||
'OC\\Security\\RateLimiting\\Backend\\IBackend' => $baseDir . '/lib/private/Security/RateLimiting/Backend/IBackend.php',
|
||||
'OC\\Security\\RateLimiting\\Backend\\MemoryCacheBackend' => $baseDir . '/lib/private/Security/RateLimiting/Backend/MemoryCacheBackend.php',
|
||||
|
||||
@@ -1943,8 +1943,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
||||
'OC\\Security\\Ip\\Range' => __DIR__ . '/../../..' . '/lib/private/Security/Ip/Range.php',
|
||||
'OC\\Security\\Ip\\RemoteAddress' => __DIR__ . '/../../..' . '/lib/private/Security/Ip/RemoteAddress.php',
|
||||
'OC\\Security\\Normalizer\\IpAddress' => __DIR__ . '/../../..' . '/lib/private/Security/Normalizer/IpAddress.php',
|
||||
'OC\\Security\\PublicPrivateKeyPairs\\KeyPairManager' => __DIR__ . '/../../..' . '/lib/private/Security/PublicPrivateKeyPairs/KeyPairManager.php',
|
||||
'OC\\Security\\PublicPrivateKeyPairs\\Model\\KeyPair' => __DIR__ . '/../../..' . '/lib/private/Security/PublicPrivateKeyPairs/Model/KeyPair.php',
|
||||
'OC\\Security\\RateLimiting\\Backend\\DatabaseBackend' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Backend/DatabaseBackend.php',
|
||||
'OC\\Security\\RateLimiting\\Backend\\IBackend' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Backend/IBackend.php',
|
||||
'OC\\Security\\RateLimiting\\Backend\\MemoryCacheBackend' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Backend/MemoryCacheBackend.php',
|
||||
|
||||
@@ -18,6 +18,7 @@ use OCP\Federation\ICloudFederationProvider;
|
||||
use OCP\Federation\ICloudFederationProviderManager;
|
||||
use OCP\Federation\ICloudFederationShare;
|
||||
use OCP\Federation\ICloudIdManager;
|
||||
use OCP\Http\Client\IClient;
|
||||
use OCP\Http\Client\IClientService;
|
||||
use OCP\Http\Client\IResponse;
|
||||
use OCP\IAppConfig;
|
||||
@@ -105,25 +106,11 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager
|
||||
public function sendShare(ICloudFederationShare $share) {
|
||||
$cloudID = $this->cloudIdManager->resolveCloudId($share->getShareWith());
|
||||
try {
|
||||
$ocmProvider = $this->discoveryService->discover($cloudID->getRemote());
|
||||
} catch (OCMProviderException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$client = $this->httpClientService->newClient();
|
||||
try {
|
||||
// signing the payload using OCMSignatoryManager before initializing the request
|
||||
$uri = $ocmProvider->getEndPoint() . '/shares';
|
||||
$payload = array_merge($this->getDefaultRequestOptions(), ['body' => json_encode($share->getShare())]);
|
||||
if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
|
||||
$signedPayload = $this->signatureManager->signOutgoingRequestIClientPayload(
|
||||
$this->signatoryManager,
|
||||
$payload,
|
||||
'post', $uri
|
||||
);
|
||||
try {
|
||||
$response = $this->postOcmPayload($cloudID->getRemote(), '/shares', json_encode($share->getShare()));
|
||||
} catch (OCMProviderException) {
|
||||
return false;
|
||||
}
|
||||
$response = $client->post($uri, $signedPayload ?? $payload);
|
||||
|
||||
if ($response->getStatusCode() === Http::STATUS_CREATED) {
|
||||
$result = json_decode($response->getBody(), true);
|
||||
return (is_array($result)) ? $result : [];
|
||||
@@ -149,22 +136,9 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager
|
||||
*/
|
||||
public function sendCloudShare(ICloudFederationShare $share): IResponse {
|
||||
$cloudID = $this->cloudIdManager->resolveCloudId($share->getShareWith());
|
||||
$ocmProvider = $this->discoveryService->discover($cloudID->getRemote());
|
||||
|
||||
$client = $this->httpClientService->newClient();
|
||||
try {
|
||||
// signing the payload using OCMSignatoryManager before initializing the request
|
||||
$uri = $ocmProvider->getEndPoint() . '/shares';
|
||||
$payload = array_merge($this->getDefaultRequestOptions(), ['body' => json_encode($share->getShare())]);
|
||||
if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
|
||||
$signedPayload = $this->signatureManager->signOutgoingRequestIClientPayload(
|
||||
$this->signatoryManager,
|
||||
$payload,
|
||||
'post', $uri
|
||||
);
|
||||
}
|
||||
|
||||
return $client->post($uri, $signedPayload ?? $payload);
|
||||
return $this->postOcmPayload($cloudID->getRemote(), '/shares', json_encode($share->getShare()), $client);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Error while sending share to federation server: ' . $e->getMessage(), ['exception' => $e]);
|
||||
try {
|
||||
@@ -183,26 +157,11 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager
|
||||
*/
|
||||
public function sendNotification($url, ICloudFederationNotification $notification) {
|
||||
try {
|
||||
$ocmProvider = $this->discoveryService->discover($url);
|
||||
} catch (OCMProviderException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$client = $this->httpClientService->newClient();
|
||||
try {
|
||||
|
||||
// signing the payload using OCMSignatoryManager before initializing the request
|
||||
$uri = $ocmProvider->getEndPoint() . '/notifications';
|
||||
$payload = array_merge($this->getDefaultRequestOptions(), ['body' => json_encode($notification->getMessage())]);
|
||||
if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
|
||||
$signedPayload = $this->signatureManager->signOutgoingRequestIClientPayload(
|
||||
$this->signatoryManager,
|
||||
$payload,
|
||||
'post', $uri
|
||||
);
|
||||
try {
|
||||
$response = $this->postOcmPayload($url, '/notifications', json_encode($notification->getMessage()));
|
||||
} catch (OCMProviderException) {
|
||||
return false;
|
||||
}
|
||||
$response = $client->post($uri, $signedPayload ?? $payload);
|
||||
|
||||
if ($response->getStatusCode() === Http::STATUS_CREATED) {
|
||||
$result = json_decode($response->getBody(), true);
|
||||
return (is_array($result)) ? $result : [];
|
||||
@@ -222,21 +181,9 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager
|
||||
* @throws OCMProviderException
|
||||
*/
|
||||
public function sendCloudNotification(string $url, ICloudFederationNotification $notification): IResponse {
|
||||
$ocmProvider = $this->discoveryService->discover($url);
|
||||
|
||||
$client = $this->httpClientService->newClient();
|
||||
try {
|
||||
// signing the payload using OCMSignatoryManager before initializing the request
|
||||
$uri = $ocmProvider->getEndPoint() . '/notifications';
|
||||
$payload = array_merge($this->getDefaultRequestOptions(), ['body' => json_encode($notification->getMessage())]);
|
||||
if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
|
||||
$signedPayload = $this->signatureManager->signOutgoingRequestIClientPayload(
|
||||
$this->signatoryManager,
|
||||
$payload,
|
||||
'post', $uri
|
||||
);
|
||||
}
|
||||
return $client->post($uri, $signedPayload ?? $payload);
|
||||
return $this->postOcmPayload($url, '/notifications', json_encode($notification->getMessage()), $client);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Error while sending notification to federation server: ' . $e->getMessage(), ['exception' => $e]);
|
||||
try {
|
||||
@@ -256,6 +203,40 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager
|
||||
return $this->appManager->isEnabledForUser('cloud_federation_api');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cloudId
|
||||
* @param string $uri
|
||||
* @param string $payload
|
||||
*
|
||||
* @return IResponse
|
||||
* @throws OCMProviderException
|
||||
*/
|
||||
private function postOcmPayload(string $cloudId, string $uri, string $payload, ?IClient $client = null): IResponse {
|
||||
$ocmProvider = $this->discoveryService->discover($cloudId);
|
||||
$uri = $ocmProvider->getEndPoint() . '/' . ltrim($uri, '/');
|
||||
$client = $client ?? $this->httpClientService->newClient();
|
||||
return $client->post($uri, $this->prepareOcmPayload($uri, $payload));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @param string $payload
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function prepareOcmPayload(string $uri, string $payload): array {
|
||||
$payload = array_merge($this->getDefaultRequestOptions(), ['body' => $payload]);
|
||||
if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
|
||||
$signedPayload = $this->signatureManager->signOutgoingRequestIClientPayload(
|
||||
$this->signatoryManager,
|
||||
$payload,
|
||||
'post', $uri
|
||||
);
|
||||
}
|
||||
|
||||
return $signedPayload ?? $payload;
|
||||
}
|
||||
|
||||
private function getDefaultRequestOptions(): array {
|
||||
return [
|
||||
'headers' => ['content-type' => 'application/json'],
|
||||
|
||||
@@ -64,7 +64,6 @@ class DAV extends Common {
|
||||
protected $httpClientService;
|
||||
/** @var ICertificateManager */
|
||||
protected $certManager;
|
||||
protected bool $verify = true;
|
||||
protected LoggerInterface $logger;
|
||||
protected IEventLogger $eventLogger;
|
||||
protected IMimeTypeDetector $mimeTypeDetector;
|
||||
@@ -104,7 +103,6 @@ class DAV extends Common {
|
||||
if (isset($parameters['authType'])) {
|
||||
$this->authType = $parameters['authType'];
|
||||
}
|
||||
$this->verify = (($parameters['verify'] ?? true) !== false);
|
||||
if (isset($parameters['secure'])) {
|
||||
if (is_string($parameters['secure'])) {
|
||||
$this->secure = ($parameters['secure'] === 'true');
|
||||
@@ -164,11 +162,6 @@ class DAV extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->verify) {
|
||||
$this->client->addCurlSetting(CURLOPT_SSL_VERIFYHOST, 0);
|
||||
$this->client->addCurlSetting(CURLOPT_SSL_VERIFYPEER, false);
|
||||
}
|
||||
|
||||
$lastRequestStart = 0;
|
||||
$this->client->on('beforeRequest', function (RequestInterface $request) use (&$lastRequestStart) {
|
||||
$this->logger->debug('sending dav ' . $request->getMethod() . ' request to external storage: ' . $request->getAbsoluteUrl(), ['app' => 'dav']);
|
||||
|
||||
@@ -210,11 +210,11 @@ class OCMProvider implements IOCMProvider {
|
||||
* apiVersion: '1.0-proposal1',
|
||||
* endPoint: string,
|
||||
* publicKey: ISignatory|null,
|
||||
* resourceTypes: array{
|
||||
* resourceTypes: list<array{
|
||||
* name: string,
|
||||
* shareTypes: list<string>,
|
||||
* protocols: array<string, string>
|
||||
* }[],
|
||||
* }>,
|
||||
* version: string
|
||||
* }
|
||||
*/
|
||||
|
||||
@@ -46,6 +46,14 @@ class OCMDiscoveryService implements IOCMDiscoveryService {
|
||||
*/
|
||||
public function discover(string $remote, bool $skipCache = false): IOCMProvider {
|
||||
$remote = rtrim($remote, '/');
|
||||
if (!str_starts_with($remote, 'http://') && !str_starts_with($remote, 'https://')) {
|
||||
// if scheme not specified, we test both;
|
||||
try {
|
||||
return $this->discover('https://' . $remote, $skipCache);
|
||||
} catch (OCMProviderException) {
|
||||
return $this->discover('http://' . $remote, $skipCache);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$skipCache) {
|
||||
try {
|
||||
@@ -70,10 +78,7 @@ class OCMDiscoveryService implements IOCMDiscoveryService {
|
||||
if ($this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates') === true) {
|
||||
$options['verify'] = false;
|
||||
}
|
||||
$response = $client->get(
|
||||
$remote . '/ocm-provider/',
|
||||
$options,
|
||||
);
|
||||
$response = $client->get($remote . '/ocm-provider/', $options);
|
||||
|
||||
if ($response->getStatusCode() === Http::STATUS_OK) {
|
||||
$body = $response->getBody();
|
||||
|
||||
@@ -8,15 +8,13 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace OC\OCM;
|
||||
|
||||
use NCU\Security\PublicPrivateKeyPairs\Exceptions\KeyPairConflictException;
|
||||
use NCU\Security\PublicPrivateKeyPairs\Exceptions\KeyPairNotFoundException;
|
||||
use NCU\Security\PublicPrivateKeyPairs\IKeyPairManager;
|
||||
use NCU\Security\Signature\Exceptions\IdentityNotFoundException;
|
||||
use NCU\Security\Signature\ISignatoryManager;
|
||||
use NCU\Security\Signature\ISignatureManager;
|
||||
use NCU\Security\Signature\Model\IIncomingSignedRequest;
|
||||
use NCU\Security\Signature\Model\ISignatory;
|
||||
use NCU\Security\Signature\Model\SignatoryType;
|
||||
use OC\Security\IdentityProof\Manager;
|
||||
use OC\Security\Signature\Model\Signatory;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IURLGenerator;
|
||||
@@ -40,7 +38,7 @@ class OCMSignatoryManager implements ISignatoryManager {
|
||||
private readonly IAppConfig $appConfig,
|
||||
private readonly ISignatureManager $signatureManager,
|
||||
private readonly IURLGenerator $urlGenerator,
|
||||
private readonly IKeyPairManager $keyPairManager,
|
||||
private readonly Manager $identityProofManager,
|
||||
private readonly OCMDiscoveryService $ocmDiscoveryService,
|
||||
) {
|
||||
}
|
||||
@@ -69,7 +67,6 @@ class OCMSignatoryManager implements ISignatoryManager {
|
||||
* @inheritDoc
|
||||
*
|
||||
* @return ISignatory
|
||||
* @throws KeyPairConflictException
|
||||
* @throws IdentityNotFoundException
|
||||
* @since 31.0.0
|
||||
*/
|
||||
@@ -85,13 +82,16 @@ class OCMSignatoryManager implements ISignatoryManager {
|
||||
$keyId = $this->generateKeyId();
|
||||
}
|
||||
|
||||
try {
|
||||
$keyPair = $this->keyPairManager->getKeyPair('core', 'ocm_external');
|
||||
} catch (KeyPairNotFoundException) {
|
||||
$keyPair = $this->keyPairManager->generateKeyPair('core', 'ocm_external');
|
||||
if (!$this->identityProofManager->hasAppKey('core', 'ocm_external')) {
|
||||
$this->identityProofManager->generateAppKey('core', 'ocm_external', [
|
||||
'algorithm' => 'rsa',
|
||||
'private_key_bits' => 2048,
|
||||
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
||||
]);
|
||||
}
|
||||
$keyPair = $this->identityProofManager->getAppKey('core', 'ocm_external');
|
||||
|
||||
return new Signatory($keyId, $keyPair->getPublicKey(), $keyPair->getPrivateKey(), local: true);
|
||||
return new Signatory($keyId, $keyPair->getPublic(), $keyPair->getPrivate(), local: true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace OC\Security\IdentityProof;
|
||||
|
||||
use OC\Files\AppData\Factory;
|
||||
use OCP\Files\IAppData;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\IConfig;
|
||||
use OCP\IUser;
|
||||
use OCP\Security\ICrypto;
|
||||
@@ -31,18 +32,20 @@ class Manager {
|
||||
* Calls the openssl functions to generate a public and private key.
|
||||
* In a separate function for unit testing purposes.
|
||||
*
|
||||
* @param array $options config options to generate key {@see openssl_csr_new}
|
||||
*
|
||||
* @return array [$publicKey, $privateKey]
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function generateKeyPair(): array {
|
||||
protected function generateKeyPair(array $options = []): array {
|
||||
$config = [
|
||||
'digest_alg' => 'sha512',
|
||||
'private_key_bits' => 2048,
|
||||
'digest_alg' => $options['algorithm'] ?? 'sha512',
|
||||
'private_key_bits' => $options['bits'] ?? 2048,
|
||||
'private_key_type' => $options['type'] ?? OPENSSL_KEYTYPE_RSA,
|
||||
];
|
||||
|
||||
// Generate new key
|
||||
$res = openssl_pkey_new($config);
|
||||
|
||||
if ($res === false) {
|
||||
$this->logOpensslError();
|
||||
throw new \RuntimeException('OpenSSL reported a problem');
|
||||
@@ -65,15 +68,17 @@ class Manager {
|
||||
* Note: If a key already exists it will be overwritten
|
||||
*
|
||||
* @param string $id key id
|
||||
* @param array $options config options to generate key {@see openssl_csr_new}
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function generateKey(string $id): Key {
|
||||
[$publicKey, $privateKey] = $this->generateKeyPair();
|
||||
protected function generateKey(string $id, array $options = []): Key {
|
||||
[$publicKey, $privateKey] = $this->generateKeyPair($options);
|
||||
|
||||
// Write the private and public key to the disk
|
||||
try {
|
||||
$this->appData->newFolder($id);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Exception) {
|
||||
}
|
||||
$folder = $this->appData->getFolder($id);
|
||||
$folder->newFile('private')
|
||||
@@ -125,6 +130,38 @@ class Manager {
|
||||
return $this->retrieveKey('system-' . $instanceId);
|
||||
}
|
||||
|
||||
public function hasAppKey(string $app, string $name): bool {
|
||||
$id = $this->generateAppKeyId($app, $name);
|
||||
try {
|
||||
$this->appData->getFolder($id);
|
||||
return true;
|
||||
} catch (NotFoundException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getAppKey(string $app, string $name): Key {
|
||||
return $this->retrieveKey($this->generateAppKeyId($app, $name));
|
||||
}
|
||||
|
||||
public function generateAppKey(string $app, string $name, array $options = []): Key {
|
||||
return $this->generateKey($this->generateAppKeyId($app, $name), $options);
|
||||
}
|
||||
|
||||
public function deleteAppKey(string $app, string $name): bool {
|
||||
try {
|
||||
$folder = $this->appData->getFolder($this->generateAppKeyId($app, $name));
|
||||
} catch (NotFoundException) {
|
||||
return false;
|
||||
}
|
||||
$folder->delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
private function generateAppKeyId(string $app, string $name): string {
|
||||
return 'app-' . $app . '-' . $name;
|
||||
}
|
||||
|
||||
private function logOpensslError(): void {
|
||||
$errors = [];
|
||||
while ($error = openssl_error_string()) {
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OC\Security\PublicPrivateKeyPairs;
|
||||
|
||||
use NCU\Security\PublicPrivateKeyPairs\Exceptions\KeyPairConflictException;
|
||||
use NCU\Security\PublicPrivateKeyPairs\Exceptions\KeyPairNotFoundException;
|
||||
use NCU\Security\PublicPrivateKeyPairs\IKeyPairManager;
|
||||
use NCU\Security\PublicPrivateKeyPairs\Model\IKeyPair;
|
||||
use OC\Security\PublicPrivateKeyPairs\Model\KeyPair;
|
||||
use OCP\IAppConfig;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* KeyPairManager store internal public/private key pair using AppConfig, taking advantage of the encryption
|
||||
* and lazy loading.
|
||||
*
|
||||
* @since 31.0.0
|
||||
*/
|
||||
class KeyPairManager implements IKeyPairManager {
|
||||
private const CONFIG_PREFIX = 'security.keypair.';
|
||||
|
||||
public function __construct(
|
||||
private readonly IAppConfig $appConfig,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param string $app appId
|
||||
* @param string $name key name
|
||||
* @param array $options algorithms, metadata
|
||||
*
|
||||
* @return IKeyPair
|
||||
* @throws KeyPairConflictException if a key already exist
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function generateKeyPair(string $app, string $name, array $options = []): IKeyPair {
|
||||
if ($this->hasKeyPair($app, $name)) {
|
||||
throw new KeyPairConflictException('key pair already exist');
|
||||
}
|
||||
|
||||
$keyPair = new KeyPair($app, $name);
|
||||
|
||||
[$publicKey, $privateKey] = $this->generateKeys($options);
|
||||
$keyPair->setPublicKey($publicKey)
|
||||
->setPrivateKey($privateKey)
|
||||
->setOptions($options);
|
||||
|
||||
$this->appConfig->setValueArray(
|
||||
$app, $this->generateAppConfigKey($name),
|
||||
[
|
||||
'public' => $keyPair->getPublicKey(),
|
||||
'private' => $keyPair->getPrivateKey(),
|
||||
'options' => $keyPair->getOptions()
|
||||
],
|
||||
lazy: true,
|
||||
sensitive: true
|
||||
);
|
||||
|
||||
return $keyPair;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param string $app appId
|
||||
* @param string $name key name
|
||||
*
|
||||
* @return bool TRUE if key pair exists in database
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function hasKeyPair(string $app, string $name): bool {
|
||||
$key = $this->generateAppConfigKey($name);
|
||||
return $this->appConfig->hasKey($app, $key, lazy: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param string $app appId
|
||||
* @param string $name key name
|
||||
*
|
||||
* @return IKeyPair
|
||||
* @throws KeyPairNotFoundException if key pair is not known
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function getKeyPair(string $app, string $name): IKeyPair {
|
||||
if (!$this->hasKeyPair($app, $name)) {
|
||||
throw new KeyPairNotFoundException('unknown key pair');
|
||||
}
|
||||
|
||||
$key = $this->generateAppConfigKey($name);
|
||||
$stored = $this->appConfig->getValueArray($app, $key, lazy: true);
|
||||
if (!array_key_exists('public', $stored) ||
|
||||
!array_key_exists('private', $stored)) {
|
||||
throw new KeyPairNotFoundException('corrupted key pair');
|
||||
}
|
||||
|
||||
$keyPair = new KeyPair($app, $name);
|
||||
return $keyPair->setPublicKey($stored['public'])
|
||||
->setPrivateKey($stored['private'])
|
||||
->setOptions($stored['options'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param string $app appid
|
||||
* @param string $name key name
|
||||
*
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function deleteKeyPair(string $app, string $name): void {
|
||||
$this->appConfig->deleteKey('core', $this->generateAppConfigKey($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param IKeyPair $keyPair keypair to test
|
||||
*
|
||||
* @return bool
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function testKeyPair(IKeyPair $keyPair): bool {
|
||||
$clear = md5((string)time());
|
||||
|
||||
// signing with private key
|
||||
openssl_sign($clear, $signed, $keyPair->getPrivateKey(), OPENSSL_ALGO_SHA256);
|
||||
$encoded = base64_encode($signed);
|
||||
|
||||
// verify with public key
|
||||
$signed = base64_decode($encoded);
|
||||
return (openssl_verify($clear, $signed, $keyPair->getPublicKey(), 'sha256') === 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* return appconfig key based on name of the key pair
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function generateAppConfigKey(string $name): string {
|
||||
return self::CONFIG_PREFIX . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* generate the key pair, based on $options with the following default values:
|
||||
* [
|
||||
* 'algorithm' => 'rsa',
|
||||
* 'bits' => 2048,
|
||||
* 'type' => OPENSSL_KEYTYPE_RSA
|
||||
* ]
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function generateKeys(array $options = []): array {
|
||||
$res = openssl_pkey_new(
|
||||
[
|
||||
'digest_alg' => $options['algorithm'] ?? 'rsa',
|
||||
'private_key_bits' => $options['bits'] ?? 2048,
|
||||
'private_key_type' => $options['type'] ?? OPENSSL_KEYTYPE_RSA,
|
||||
]
|
||||
);
|
||||
|
||||
openssl_pkey_export($res, $privateKey);
|
||||
$publicKey = openssl_pkey_get_details($res)['key'];
|
||||
|
||||
return [$publicKey, $privateKey];
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OC\Security\PublicPrivateKeyPairs\Model;
|
||||
|
||||
use NCU\Security\PublicPrivateKeyPairs\Model\IKeyPair;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @since 31.0.0
|
||||
*/
|
||||
class KeyPair implements IKeyPair {
|
||||
private string $publicKey = '';
|
||||
private string $privateKey = '';
|
||||
private array $options = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly string $app,
|
||||
private readonly string $name,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @return string
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function getApp(): string {
|
||||
return $this->app;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @return string
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param string $publicKey
|
||||
* @return IKeyPair
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function setPublicKey(string $publicKey): IKeyPair {
|
||||
$this->publicKey = $publicKey;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @return string
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function getPublicKey(): string {
|
||||
return $this->publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param string $privateKey
|
||||
* @return IKeyPair
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function setPrivateKey(string $privateKey): IKeyPair {
|
||||
$this->privateKey = $privateKey;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @return string
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function getPrivateKey(): string {
|
||||
return $this->privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param array $options
|
||||
* @return IKeyPair
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function setOptions(array $options): IKeyPair {
|
||||
$this->options = $options;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @return array
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function getOptions(): array {
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
@@ -112,9 +112,7 @@ class SignatureManager implements ISignatureManager {
|
||||
$this->prepIncomingSignatureHeader($signedRequest);
|
||||
$this->verifyIncomingSignatureHeader($signedRequest);
|
||||
$this->prepEstimatedSignature($signedRequest, $options['extraSignatureHeaders'] ?? []);
|
||||
$this->verifyIncomingRequestSignature(
|
||||
$signedRequest, $signatoryManager, $options['ttlSignatory'] ?? self::SIGNATORY_TTL
|
||||
);
|
||||
$this->verifyIncomingRequestSignature($signedRequest, $signatoryManager, $options['ttlSignatory'] ?? self::SIGNATORY_TTL);
|
||||
} catch (SignatureException $e) {
|
||||
$this->logger->warning(
|
||||
'signature could not be verified', [
|
||||
@@ -724,7 +722,6 @@ class SignatureManager implements ISignatureManager {
|
||||
case SignatoryType::FORGIVABLE:
|
||||
$this->deleteSignatory($knownSignatory->getKeyId());
|
||||
$this->insertSignatory($signatory);
|
||||
|
||||
return;
|
||||
|
||||
case SignatoryType::REFRESHABLE:
|
||||
@@ -735,12 +732,10 @@ class SignatureManager implements ISignatureManager {
|
||||
case SignatoryType::TRUSTED:
|
||||
// TODO: send notice to admin
|
||||
throw new SignatoryConflictException();
|
||||
break;
|
||||
|
||||
case SignatoryType::STATIC:
|
||||
// TODO: send warning to admin
|
||||
throw new SignatoryConflictException();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,6 @@ use OC\Security\CSRF\CsrfTokenManager;
|
||||
use OC\Security\CSRF\TokenStorage\SessionStorage;
|
||||
use OC\Security\Hasher;
|
||||
use OC\Security\Ip\RemoteAddress;
|
||||
use OC\Security\PublicPrivateKeyPairs\KeyPairManager;
|
||||
use OC\Security\RateLimiting\Limiter;
|
||||
use OC\Security\SecureRandom;
|
||||
use OC\Security\Signature\SignatureManager;
|
||||
@@ -1290,7 +1289,6 @@ class Server extends ServerContainer implements IServerContainer {
|
||||
|
||||
$this->registerAlias(IRichTextFormatter::class, \OC\RichObjectStrings\RichTextFormatter::class);
|
||||
|
||||
$this->registerAlias(IKeyPairManager::class, KeyPairManager::class);
|
||||
$this->registerAlias(ISignatureManager::class, SignatureManager::class);
|
||||
|
||||
$this->connectDispatcher();
|
||||
|
||||
@@ -154,11 +154,11 @@ interface IOCMProvider extends JsonSerializable {
|
||||
* apiVersion: '1.0-proposal1',
|
||||
* endPoint: string,
|
||||
* publicKey: ISignatory|null,
|
||||
* resourceTypes: array{
|
||||
* resourceTypes: list<array{
|
||||
* name: string,
|
||||
* shareTypes: list<string>,
|
||||
* protocols: array<string, string>
|
||||
* }[],
|
||||
* }>,
|
||||
* version: string
|
||||
* }
|
||||
* @since 28.0.0
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace NCU\Security\PublicPrivateKeyPairs\Exceptions;
|
||||
|
||||
/**
|
||||
* conflict between public and private key pair
|
||||
*
|
||||
* @experimental 31.0.0
|
||||
* @since 31.0.0
|
||||
*/
|
||||
class KeyPairConflictException extends KeyPairException {
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace NCU\Security\PublicPrivateKeyPairs\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* global exception related to key pairs
|
||||
*
|
||||
* @experimental 31.0.0
|
||||
* @since 31.0.0
|
||||
*/
|
||||
class KeyPairException extends Exception {
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace NCU\Security\PublicPrivateKeyPairs\Exceptions;
|
||||
|
||||
/**
|
||||
* @experimental 31.0.0
|
||||
* @since 31.0.0
|
||||
*/
|
||||
class KeyPairNotFoundException extends KeyPairException {
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace NCU\Security\PublicPrivateKeyPairs;
|
||||
|
||||
use NCU\Security\PublicPrivateKeyPairs\Exceptions\KeyPairConflictException;
|
||||
use NCU\Security\PublicPrivateKeyPairs\Exceptions\KeyPairNotFoundException;
|
||||
use NCU\Security\PublicPrivateKeyPairs\Model\IKeyPair;
|
||||
|
||||
/**
|
||||
* IKeyPairManager contains a group of method to create/manage/store internal public/private key pair.
|
||||
*
|
||||
* @experimental 31.0.0
|
||||
* @since 31.0.0
|
||||
*/
|
||||
interface IKeyPairManager {
|
||||
|
||||
/**
|
||||
* generate and store public/private key pair.
|
||||
* throws exception if key pair already exist
|
||||
*
|
||||
* @param string $app appId
|
||||
* @param string $name key name
|
||||
* @param array $options algorithms, metadata
|
||||
*
|
||||
* @return IKeyPair
|
||||
* @throws KeyPairConflictException if a key already exist
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function generateKeyPair(string $app, string $name, array $options = []): IKeyPair;
|
||||
|
||||
/**
|
||||
* returns if key pair is known.
|
||||
*
|
||||
* @param string $app appId
|
||||
* @param string $name key name
|
||||
*
|
||||
* @return bool TRUE if key pair exists in database
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function hasKeyPair(string $app, string $name): bool;
|
||||
|
||||
/**
|
||||
* return key pair from database based on $app and $name.
|
||||
* throws exception if key pair does not exist
|
||||
*
|
||||
* @param string $app appId
|
||||
* @param string $name key name
|
||||
*
|
||||
* @return IKeyPair
|
||||
* @throws KeyPairNotFoundException if key pair is not known
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function getKeyPair(string $app, string $name): IKeyPair;
|
||||
|
||||
/**
|
||||
* delete key pair from database
|
||||
*
|
||||
* @param string $app appid
|
||||
* @param string $name key name
|
||||
*
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function deleteKeyPair(string $app, string $name): void;
|
||||
|
||||
/**
|
||||
* test key pair by encrypting/decrypting a string
|
||||
*
|
||||
* @param IKeyPair $keyPair keypair to test
|
||||
*
|
||||
* @return bool
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function testKeyPair(IKeyPair $keyPair): bool;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace NCU\Security\PublicPrivateKeyPairs\Model;
|
||||
|
||||
/**
|
||||
* simple model that store key pair, its name, its origin (app)
|
||||
* and the options used during its creation
|
||||
*
|
||||
* @experimental 31.0.0
|
||||
* @since 31.0.0
|
||||
*/
|
||||
interface IKeyPair {
|
||||
/**
|
||||
* returns id of the app owning the key pair
|
||||
*
|
||||
* @return string
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function getApp(): string;
|
||||
|
||||
/**
|
||||
* returns name of the key pair
|
||||
*
|
||||
* @return string
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* set public key
|
||||
*
|
||||
* @param string $publicKey
|
||||
* @return IKeyPair
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function setPublicKey(string $publicKey): IKeyPair;
|
||||
|
||||
/**
|
||||
* returns public key
|
||||
*
|
||||
* @return string
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function getPublicKey(): string;
|
||||
|
||||
/**
|
||||
* set private key
|
||||
*
|
||||
* @param string $privateKey
|
||||
* @return IKeyPair
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function setPrivateKey(string $privateKey): IKeyPair;
|
||||
|
||||
/**
|
||||
* returns private key
|
||||
*
|
||||
* @return string
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function getPrivateKey(): string;
|
||||
|
||||
/**
|
||||
* set options
|
||||
*
|
||||
* @param array $options
|
||||
* @return IKeyPair
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function setOptions(array $options): IKeyPair;
|
||||
|
||||
/**
|
||||
* returns options
|
||||
*
|
||||
* @return array
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function getOptions(): array;
|
||||
}
|
||||
Reference in New Issue
Block a user