Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 98 additions & 143 deletions IETF-RFC.md
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,8 @@ contain the following information about its OCM API:
for a short-lived bearer token.
_ `"http-sig"` - to indicate that this OCM Server supports
[RFC9421] HTTP Message Signatures and advertises public keys in the
`publicKeys` array for signature verification.
format specified by [RFC7517] at the `/.well-known/jwks.json`
endpoint for signature verification.
_ `"invites"` - to indicate the server would support acting as an
Invite Sender or Invite Receiver OCM Server. This might be useful
for suggesting to a user that existing contacts might be upgraded
Expand Down Expand Up @@ -759,40 +760,12 @@ contain the following information about its OCM API:
_ `"denylist"` - some servers MAY be blocked based on their IP
address
_ `"allowlist"` - unknown servers MAY be blocked based on their IP
address \* `"invite"` - an invite MUST have been exchanged between the
address
_ `"invite"` - an invite MUST have been exchanged between the
sender and the receiver before a Share Creation Notification can be
sent
* OPTIONAL: publicKey (object) - DEPRECATED: Use publicKeys array
instead for RFC 9421 support.
Legacy field for draft-cavage HTTP Signatures (RSA only).
Maintained for backward compatibility with existing deployments.
The signatory is optional, but if present, it MUST contain
two string fields, `keyId` and `publicKeyPem`.
properties:
- REQUIRED keyId (string) unique id of the key in URI format. The
hostname set the origin of the request and MUST be
identical to the current discovery endpoint.
Example: https://cloud.example.org/ocm#signature
- REQUIRED publicKeyPem (string) - PEM-encoded RSA public key for
draft-cavage signatures.
Example:
"----BEGIN PUBLIC KEY----\n...\n----END PUBLIC KEY----\n"
* OPTIONAL: publicKeys (array of objects) - Array of public keys for
[RFC9421] HTTP Message Signatures.
Servers advertising the "http-sig" capability MUST provide this
field. Clients SHOULD prefer [RFC9421] signatures when this capability
is present. Each object in the array MUST contain:
- REQUIRED keyId (string) - Unique identifier for this key in URI
format. Hostname MUST match the discovery endpoint hostname.
Example: https://cloud.example.org/ocm#key-1
- REQUIRED publicKeyPem (string) - PEM-encoded public key for
[RFC9421] signatures.
Example:
"----BEGIN PUBLIC KEY----\nMCowBQYDK...\n----END PUBLIC KEY----\n"
- REQUIRED algorithm (string) - Cryptographic algorithm identifier
from the IANA HTTP Signature Algorithms Registry as defined in
[RFC9421] Section 6.2.
Example: "ed25519"
* DEPRECATED: publicKey (object) - Use public keys at
`/.well-known/jwks.json` instead for RFC 9421 support.
* OPTIONAL: inviteAcceptDialog (string) - URL path of a web page where
a user can accept an invite, when query parameters `"token"` and
`"providerDomain"` are provided. Implementations that offer the
Expand Down Expand Up @@ -1160,7 +1133,8 @@ To obtain an access token, the Receiving Server MUST send an HTTP POST
request to the Sending Server’s {tokenEndPoint} as discovered in the
OCM provider metadata, following section 4.4.2 of [RFC6749]. The
request payload MUST be in `x-www-form-urlencoded` form, as shown
in the following example:
in the following example (with line breaks in the Signature headers
for display purposes only):

```
POST {tokenEndPoint} HTTP/1.1
Expand All @@ -1170,11 +1144,12 @@ Content-Type: application/x-www-form-urlencoded
Digest: SHA-256=ok6mQ3WZzKc8nb7s/Jt2yY1uK7d2n8Zq7dhl3Q0s1xk=
Content-Length: 101
Signature-Input:
sig1=("@method" "@target-uri" "content-digest" "date"); \
created=1730815200; keyid="receiver.example.org#2025"; \
alg="rsa-sha256"
Signature: sig1=:
bM2sV2a4oM8pWc4Q8r9Zb8bQ7a2vH1kR9xT0yJ3uE4wO5lV6bZ1cP2rN3qD4tR5hC=:
sig1=("@method" "@target-uri" "content-digest" "date");
created=1730815200;
keyid="receiver.example.org#key1";
alg="rsa-sha256"
Signature: sig1=:bM2sV2a4oM8pWc4Q8r9Zb8bQ7a2vH1kR9xT0yJ3uE4wO5lV6bZ1cP
2rN3qD4tR5hC=:

grant_type=authorization_code&
client_id=receiver.example.org&
Expand Down Expand Up @@ -1391,7 +1366,14 @@ June 2007.
https://datatracker.ietf.org/html/rfc6749)", October 2012.

[RFC7515] Jones, M., Bradley, J., Sakimura, N., "[JSON Web Signature
(JWS)](https://datatracker.ietf.org/doc/html/rfc7515), May 2015."
(JWS)](https://datatracker.ietf.org/doc/html/rfc7515)", May 2015.

[RFC7517] Jones, M., "[JSON Web Key (JWK)](
https://datatracker.ietf.org/doc/html/rfc7517)", May 2015.

[RFC8032] Josefsson, S., Liusvaara, I., "[Edwards-Curve Digital
Signature Algorithm (EdDSA)](
https://datatracker.ietf.org/doc/html/rfc8032)", January 2017.

[RFC8174] Leiba, B. "[Ambiguity of Uppercase vs Lowercase in RFC 2119
Key Words](https://datatracker.ietf.org/html/rfc8174)", May 2017.
Expand Down Expand Up @@ -1430,126 +1412,100 @@ out of scope for this specification: a mechanism similar to the
[ScienceMesh](https://sciencemesh.io) integration for the
[Invite](#invite-flow) capability may be envisaged.

# Appendix B: Request Signing
# Appendix B: JWKS and HTTP Signature Examples

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.
## JWKS Endpoint

Note: Signed requests prove the identity of the sender but do not
encrypt nor affect its payload.
An OCM Server that advertises the `http-sig` capability MUST expose its
public keys at `/.well-known/jwks.json` in the format specified by
[RFC7517]. Here is an example response from
`https://sender.example.org/.well-known/jwks.json`:

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",
"content-digest": "SHA-256=U7gNVUQiixe5BRbp4...",
"host": "hostname.of.the.recipient",
"Signature": "keyId=\"https://author.hostname/key\",algorithm=
\"rsa-sha256\",headers=\"content-length date digest host\",
signature=\"DzN12OCS1rsA[...]o0VmxjQooRo6HHabg==\""
}
```json
{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"kid": "sender.example.org#key1",
"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
}
]
}
```

* '@request-target' (optional) 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,
* 'content-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,
content-digest, and host are mandatory to protect against
authenticity override.

## How to generate the Signature for outgoing request

After properties are set in the headers, the Signature is generated and
added to the list.

This is a pseudo-code example for generating the `Signature` header for
outgoing requests:
## Signing a Request (Sender)

Given a Share Creation Notification request:

```
headers = {
'content-length': length_of(payload),
# Use a function to get the current GMT date as 'D, d M Y H:i:s T'
'date': current_gmt_datetime(),
'content-digest': 'SHA-256=' + base64_encode(hash('sha256',
utf8_encode(payload))),
'host': 'recipient-fqdn',
}
POST /ocm/shares HTTP/1.1
Host: receiver.example.org
Date: Fri, 16 Jan 2026 13:37:00 GMT
Content-Type: application/json
Content-Digest: sha-256=:LkpHyFOVbBDPxc7YbHDOWNzAv88qWuVfLNf4TUf9Uo8=:

signed = ssl_sign(concatenate_with_newlines(headers),
private_key, 'sha256')
signature = {
'keyId': 'sender.fqdn', # The sending server's FQDN
'algorithm': 'rsa-sha256',
'headers': 'content-length date content-digest host',
'signature': signed,
{
"shareWith": "marie@receiver.example.org",
"name": "spec.yaml",
"providerId": "7c084226-d9a1-11e6-bf26-cec0c932ce01",
"owner": "einstein@sender.example.org",
"sender": "einstein@sender.example.org",
"ownerDisplayName": "Albert Einstein",
"senderDisplayName": "Albert Einstein",
"shareType": "user",
"resourceType": "file",
"protocol": {
"name": "multi",
"webdav": {
"uri": "spec.yaml",
"sharedSecret": "hfiuhworzwnur98d3wjiwhr",
"permissions": ["read", "write"]
}
}
}

headers['Signature'] = format_signature(signature)
```

## How to confirm Signature on incoming request
The signature base is constructed according to [RFC9421] (with line
breaks in @signature-params for display purposes only):

The first step would be to confirm the validity of each
properties:

* `content-length` and `content-digest` can be regenerated 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`,
`content-digest` and `host`,
- `signature` is the encrypted version of the
signature.
```
"@method": POST
"@target-uri": https://receiver.example.org/ocm/shares
"content-digest": sha-256=:<digest-value>=:
"@signature-params": ("@method" "@target-uri" "content-digest");
created=<timestamp>;
keyid="sender.example.org#key1";
alg="ed25519"
```

Here is an example of how to verify the signature using the headers,
the signature and the public key:
Sign this base using for example Ed25519 ([RFC8032]) to produce the
signature, then add headers (line breaks for display purposes only):

```
clear = {
'content-length': length_of(payload),
'date': 'Mon, 08 Jul 2024 14:16:20 GMT',
'content-digest': 'SHA-256=' + base64_encode(hash('sha256',
utf8_encode(payload))), # Recompute digest for verification
'host': 'sender-fqdn',
}
Signature-Input: sig1=("@method" "@target-uri" "content-digest");
created=<timestamp>;
keyid="sender.example.org#key1";
alg="ed25519"
Signature: sig1=:<signature-value>=:
```

signed = headers['Signature']
verification_result = ssl_verify(concatenate_with_newlines(clear),
signed, public_key, 'sha256')
## Verifying a Signature (Receiver)

if not verification_result then
raise InvalidSignatureException
```
To verify an incoming signed request:

## Validating the payload
1. Extract the provider domain from the `sender` field in the
request body
2. Fetch the public key from
`https://<provider-domain>/.well-known/jwks.json`
3. Extract `keyid` from `Signature-Input` header and find the key
matching the `kid` value in the [RFC7517] response
4. Reconstruct the signature base from the request using the
components listed in `Signature-Input` as specified in [RFC9421]
5. Verify the signature using the appropriate algorithm
(e.g., Ed25519 [RFC8032])

## Validating the Payload

Following the validation of the signature, the host SHOULD also confirm
the validity of the payload, that is ensuring that the actions implied
Expand Down Expand Up @@ -1938,7 +1894,6 @@ to model a few key properties.
* __type__: Type of Resource (file, folder, calendar, etc.)



# Acknowledgements

Our deepest thanks and appreciation go to the people who started the
Expand Down
37 changes: 2 additions & 35 deletions spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,8 @@ components:
type: object
deprecated: true
description: >
DEPRECATED: Use publicKeys array instead for RFC 9421 support.
DEPRECATED: Use public keys from /.well-known/jwks.json
instead for RFC 9421 support.
Legacy field for draft-cavage HTTP Signatures (RSA only).
Maintained for backward compatibility with existing deployments.
The signatory is optional but it MUST contain `keyId` and `publicKeyPem`.
Expand All @@ -444,40 +445,6 @@ components:
-----BEGIN PUBLIC KEY-----
MII...QDD
-----END PUBLIC KEY-----
publicKeys:
type: array
description: >
Array of public keys for RFC 9421 HTTP Message Signatures.
Servers advertising the "rfc-http-sig" capability MUST provide this field.
Clients SHOULD prefer RFC 9421 signatures when this capability is present.
items:
type: object
required:
- keyId
- publicKeyPem
- algorithm
properties:
keyId:
type: string
format: uri
description: >
Unique identifier for this key in URI format.
Hostname MUST match the discovery endpoint hostname.
example: https://cloud.example.org/ocm#key-1
publicKeyPem:
type: string
description: |
PEM-encoded public key for RFC 9421 signatures.
example: |
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA...
-----END PUBLIC KEY-----
algorithm:
type: string
description: >
Cryptographic algorithm identifier from the IANA HTTP Signature
Algorithms Registry as defined in RFC 9421 Section 6.2.
example: ed25519
tokenEndPoint:
type: string
format: uri
Expand Down
Loading