This repository contains the specification of the Open Cloud Mesh protocol, including the OpenAPI (fka Swagger) specification for its API. This specification describes disovery and use of the RESTful API endpoints, request and response headers, possible response codes, request and response formats, hypermedia controls, error handling, and other API design best practices which vendors should support to make sharing of resources between different vendors possible.
- For the core sharing functionality, the provider knows the consumer (both endpoint and user) when it creates a share with the consumer (also see #26). In addition, an optional invitation workflow is available in this specification (see below), which gives the consumer a way to automatically trust a provider (and vice versa). The ScienceMesh infrastructure provides a managed white list of trusted federated sites.
- Consumer doesn't have to accept a share, the resource will be available to the consumer immediately (#25).
- Dealing with incoming shares is a vendor specific implementation. One vendor might use an 'accept before' process while another vendor might use a 'decline after' approach. This is considered part of the UX and thus not part of the interaction between different vendors. However, the consumer could notify the provider by using the introduced
/notifications
endpoint (also see #27). - Reverting access to outgoing shares is a vendor specific implementation. One vendor might delete an entire share while another might invalidate an access token. This is considered part of vendor-specific internals and thus not part of the interaction between different vendors. However, the provider could notify the consumer by using the introduced
/notifications
endpoint (also see #27). - The actual file sync is not part of this specification. To keep this specification 'future proof', the file sync protocol will be embedded as a separate object in Open Cloud Mesh API calls. This protocol object contains all protocol specific options, e.g. WebDAV specific options.
Authentication between services is already established. This means that this specification doesn't cover the way a service authenticates incoming API calls (e.g. through an API Key, VPN connection or IP whitelisting). In this scope we assume that the services are already authenticated.
If a finite whitelist of receiver servers exists on the sender side, then this list may already contain all necessary endpoint details.
When a sending server allows sharing to any internet-hosted receiving server, then discovery can happen from the sharee address, using the /.well-known/ocm
(or /ocm-provider
, for backwards compatibility) URL that receiving servers SHOULD provide according to this specification.
To ease the process of confirming the identity of a remote party, the discovery data MAY contain a public key: each incoming request that requires to origin from an authenticated source MUST be signed in its headers using the private key of that source, whose public key MUST be exposed in its discovery data.
To fill the gap between users knowning other peers' email addresses of the form user@provider.org
, and the actual cloud storage endpoints being in the form https://my-cloud-storage.provider.org
, a further discovery mechanism MAY be provided in case hosting https://provider.org/.well-known/ocm is impractical, based on DNS SRV
Service Records.
- If e.g. https://provider.org/.well-known/ocm does not exist, a provider MAY instead point to e.g. https://my-cloud-storage.provider.org/.well-known/ocm by ensuring that a
type=SRV
DNS query to_ocm._tcp.provider.org
resolves to e.g.service = 10 10 443 my-cloud-storage.provider.org
- When requested to discover the EFSS endpoint for
user@provider.org
, if https://provider.org/.well-known/ocm can not be fetched, implementations SHOULD fall back to querying the corresponding_ocm._tcp.domain
DNS record, e.g._ocm._tcp.provider.org
, and subsequently make a HTTP GET request to the host returned by that DNS query, followed by the/.well-known/ocm
URL path.
To create a share, the sending server SHOULD make a HTTP POST request to the /shares
endpoint of the receiving server (docs).
In response to a share creation, the receiving server MAY send back a notification to the sending server, with notificationType
set to "SHARE_ACCEPTED"
or "SHARE_DECLINED"
. The sending server MAY expose this information to the end user.
To access a share, the receiving server MAY use multiple ways, depending on the received payload and on the protocol.name
property:
-
If
protocol.name
=multi
, the receiver MUST make a HTTP PROPFIND request toprotocol.webdav.uri
to access the remote share. Ifprotocol.webdav.sharedSecret
is not empty, the receiver MUST pass it as aAuthorization: bearer
header. Otherwise, ifprotocol.webdav.code
is not empty, the receiver SHOULD discover the sender's OCM endpoint and make a signed POST request to<OCM endpoint>/token
, to exchange the code for a short-lived bearer token, and then use that bearer token to access the remote share. -
If
protocol.name
=webdav
, the receiver SHOULD inspect theprotocol.options
property. If it contains asharedSecret
, as in the legacy example, then the receiver SHOULD make a HTTP PROPFIND request tohttps://<sharedSecret>:@<host><path>
, where<host>
is the remote server, and<path>
is obtained by querying the Discovery endpoint at the remote server and gettingresourceTypes[0].protocols.webdav
. Note that this access method is deprecated and may be removed in a future release of the Protocol.
In both cases, when the share is a folder and the receiver accesses a resource within the share, it SHOULD append its relative path to that URL.
Additionally, if protocol.<protocolname>.permissions
include mfa-enforced
, the receiving host MUST ensure that the user accessing the resource has been authenticated with MFA.
A "SHARE_ACCEPTED"
notification followed by a "SHARE_UNSHARED"
notification is
equivalent to a "SHARE_DECLINED"
notification.
TODO: document "RESHARE_CHANGE_PERMISSION"
The "REQUEST_RESHARE"
and "RESHARE_UNDO"
notification types MAY be used by the
receiving server to persuarde the sending server to share the same resource with another share recipient.
TODO: document how receiver.com can know if sender.com understood and processed the
reshare request.
If Alice (alice at sender.com
) and Bob (bob at receiver.com
) know each other, yet they may not have a mechanism to trust any received share request, as that may be similar to receiving spam.
In this case, Alice may invite Bob to initiate a mutual trust relationship: on the provider side, the sender.com
service MAY implement an interface for Alice to generate a single-use token and send it to Bob off-band (e.g. via e-mail). In addition, the sender.com
service MAY integrate this interface with ScienceMesh, and only allow a curated white list of sites as receivers.
On the receiving end, assuming that Bob wishes to accept the invitation, the receiving server MAY provide an interface for Bob to input the received token, or to interact with the ScienceMesh directory. In any case, the receiving server SHOULD make a HTTP POST request to the /invite-accepted endpoint of the sending server, sending the token and disclosing Bob's identity details such as bob at receiver.com
. If the token matches the one created earlier by Alice, the response MUST include Alice's identity details such as alice at sender.com
.
Following this step, both services at sender.com
and receiver.com
MAY display, respectively, bob
and alice
as trusted or white-listed contacts, and enable sharing between them. Sites MAY enforce a policy to only accept shares between such trusted contacts, or MAY display a warning to users when a share from an unknown party is received.
For further details on this concept, see also #54 and related issues. For a discussion about trust policies, see sciencemesh#196.
If an OCM provider exposes the capability /mfa-capable
, it indicates that it will try and comply with a MFA requirement set as a permission on a share. If the sharer OCM provider trusts the receiver OCM provider, the sharer MAY set the permission mfa-enforced
on a share, which SHOULD be honored. A compliant OCM provider that signals that it is MFA-capable MUST not allow access to a resource protected with the mfa-enforced
permission, if the consumer has not provided a second factor to establish their identity with greater confidence.
Since there is no way to guarantee that the sharee OCM provider will actually enforce the MFA requirement, it is up to the sharer OCM provider to establish a trust with the OCM sharee provider such that it is reasonable to assume that the sharee OCM provider will honor the MFA requirement. This establishment of trust will inevitably be implementation dependent, and can be done for example using a pre approved allow list of trusted OCM providers. The procedure of establishing trust is out of scope for this specification: a mechanism similar to the ScienceMesh integration for the Invite capability may be envisaged.
A request is signed by adding the signature in the headers. The sender also needs to expose the public key used to generate the signature. The receiver can then validate the signature and therefore the origin of the request. To help debugging, it is recommended to also add all properties used in the signature as headers, even if they can easily be re-generated from the payload.
Note: Signed requests prove the identity of the sender but does not encrypt nor affect its payload.
Here is an example of headers needed to sign a request.
{
"(request-target)": "post /path",
"content-length": 380,
"date": "Mon, 08 Jul 2024 14:16:20 GMT",
"digest": "SHA-256=U7gNVUQiixe5BRbp4Tg0xCZMTcSWXXUZI2\\/xtHM40S0=",
"host": "hostname.of.the.recipient",
"Signature": "keyId=\"https://author.hostname/key\",algorithm=\"rsa-sha256\",headers=\"content-length date digest host\",signature=\"DzN12OCS1rsA[...]o0VmxjQooRo6HHabg==\""
}
- '(request-target)' contains the reached endpoint and the used method,
- 'content-length' is the total length of the payload of the request,
- 'date' is the date and time when the request has been sent,
- 'digest' is a checksum of the payload of the request,
- 'host' is the hostname of the recipient of the request (remote when signing outgoing request, local on incoming request),
- 'Signature' contains the signature generated using the private key and details on its generation:
- 'keyId' is a unique id, formatted as an url. hostname is used to retrieve the public key via custom discovery
- 'algorithm' specify the algorithm used to generate signature
- 'headers' specify the properties used when generating the signature
- 'signature' the signature of an array containing the properties listed in 'headers'. Some properties like content-length, date, digest, and host are mandatory to protect against authenticity override.
After properties are set in the headers, the Signature is generated and added to the list.
This is a quick PHP example of headers for outgoing request:
$headers = [
'(request-target)' => 'post /path',
'content-length' => strlen($payload),
'date' => gmdate('D, d M Y H:i:s T'),
'digest': 'SHA-256=' . base64_encode(hash('sha256', utf8_encode($payload), true)),
'host': 'hostname.of.the.recipient',
];
openssl_sign(implode("\n", $headers), $signed, $privateKey, OPENSSL_ALGO_SHA256);
$signature = [
'keyId' => 'https://author.hostname/key',
'algorithm' => 'rsa-sha256',
'headers' => 'content-length date digest host',
'signature' => $signed
];
$headers['Signature'] = implode(',', $signature);
The first step would be to confirm the validity of each properties:
- '(request-target)' and 'host' are immutable to the type of the request and the local/current host,
- 'content-length' and 'digest' can be re-generated and compared from the payload of the request,
- A maximum TTL must be applied to 'date' and current timestamp,
- regarding data contained in the 'Signature' header:
- using 'keyId' to get the public key from remote signatory,
- 'headers' is used to generate the clear version of the signature and must contain at least 'content-length', 'date', 'digest' and 'host',
- 'signature' is the encrypted version of the signature.
Here is an example of how to verify the signature using the headers, the signature and the public key:
$clear = [
'(request-target)' => 'post /path',
'content-length' => strlen($payload),
'date' => 'Mon, 08 Jul 2024 14:16:20 GMT',
'digest': 'SHA-256=' . base64_encode(hash('sha256', utf8_encode($payload), true)),
'host': $localhost
];
$signed = "DzN12OCS1rsA[...]o0VmxjQooRo6HHabg==";
if (openssl_verify(implode("\n", $clear), $signed, $publicKey, 'sha256') !== 1) {
throw new InvalidSignatureException('signature issue');
}
Following the validation of the signature, the host should also confirm the validity of the payload, that is ensuring that the actions implied in the payload actually initiated on behalf of the source of the request.
As an example, if the payload is about initiating a new share the file owner has to be an account from the instance at the origin of the request.
The specification can be rendered as HTML documentation using ReDoc and is available as follows:
- version 1.1 - current official version, supported by ScienceMesh
- version 1.0 - first official and supported version
The current developments yet to be released are available in the develop branch
The Open Cloud Mesh API specification is an open source, community-driven project. If you'd like to contribute, please follow the Contributing Guidelines.
To stage the changes of your PR, you can change the repo and branch in the URL. For instance to see the proposed changes of cs3org#41, use: https://cs3org.github.io/OCM-API/docs.html?branch=add-endpoint-to-accept-invite&repo=OCM-API&user=LovisaLugnegard