From 07fdd3cdd926eeca8d29188784139adfb33df227 Mon Sep 17 00:00:00 2001 From: Micke Nordin Date: Mon, 12 Jan 2026 11:52:59 +0100 Subject: [PATCH 1/4] JWKS: Remove publicKeys in favor of RFC7515. Fixes: #313 --- IETF-RFC.md | 23 ++++------------------- spec.yaml | 37 ++----------------------------------- 2 files changed, 6 insertions(+), 54 deletions(-) diff --git a/IETF-RFC.md b/IETF-RFC.md index 5e7d387..130f7d7 100644 --- a/IETF-RFC.md +++ b/IETF-RFC.md @@ -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 [RFC7515] 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 @@ -762,8 +763,8 @@ contain the following information about its OCM API: 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. +* OPTIONAL: publicKey (object) - DEPRECATED: Use public keys at + `/.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 if present, it MUST contain @@ -777,22 +778,6 @@ contain the following information about its OCM API: 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" * 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 diff --git a/spec.yaml b/spec.yaml index ab7ed84..23a6955 100644 --- a/spec.yaml +++ b/spec.yaml @@ -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`. @@ -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 From 5b6e2ac01fcf3e146bd89d9738e7740deac46491 Mon Sep 17 00:00:00 2001 From: Micke Nordin Date: Tue, 13 Jan 2026 09:16:43 +0100 Subject: [PATCH 2/4] Remove example for deprecated functionality --- IETF-RFC.md | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/IETF-RFC.md b/IETF-RFC.md index 130f7d7..3d72aff 100644 --- a/IETF-RFC.md +++ b/IETF-RFC.md @@ -763,21 +763,8 @@ contain the following information about its OCM API: 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 public keys at +* DEPRECATED: publicKey (object) - Use public keys at `/.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 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: 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 From ad09853fc0c7606769c36701579d0ffd6c7be483 Mon Sep 17 00:00:00 2001 From: Micke Nordin Date: Fri, 16 Jan 2026 13:18:59 +0100 Subject: [PATCH 3/4] Rewrite Appendix B with RFC9421 HTTP signature examples - Fix http-sig capability to reference RFC7517 instead of RFC7515 - Add RFC7517 (JWK) and RFC8032 (EdDSA) to references - Fix RFC7515 reference formatting - Replace draft-cavage examples with RFC9421 signature format - Add JWKS endpoint and Ed25519 signing/verification examples Co-authored-by: Giuseppe Lo Presti --- IETF-RFC.md | 185 +++++++++++++++++++++++----------------------------- 1 file changed, 80 insertions(+), 105 deletions(-) diff --git a/IETF-RFC.md b/IETF-RFC.md index 3d72aff..3618a13 100644 --- a/IETF-RFC.md +++ b/IETF-RFC.md @@ -729,7 +729,7 @@ 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 - format specified by [RFC7515] at the `/.well-known/jwks.json` + 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 @@ -1363,7 +1363,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. @@ -1402,126 +1409,95 @@ 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 - -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. +# Appendix B: JWKS and HTTP Signature Examples -Note: Signed requests prove the identity of the sender but do not -encrypt nor affect its payload. +## JWKS Endpoint -Here is an example of headers needed to sign a request. +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`: +```json +{ + "keys": [ + { + "kty": "OKP", + "crv": "Ed25519", + "kid": "sender.example.org#key1", + "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + } + ] +} ``` - { - "@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==\"" - } -``` -* '@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 first step would be to confirm the validity of each -properties: +The signature base is constructed according to [RFC9421]: -* `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=:=: +"@signature-params": ("@method" "@target-uri" "content-digest")\ + ;created=;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: ``` -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=;keyid="sender.example.org#key1";alg="ed25519" +Signature: sig1=:=: +``` -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:///.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 @@ -1910,7 +1886,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 From 8a1b0d915148f2f832e91bd7b9c57cda82867c64 Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Sat, 17 Jan 2026 09:36:20 +0100 Subject: [PATCH 4/4] Formatting fixes with clause about display purposes --- IETF-RFC.md | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/IETF-RFC.md b/IETF-RFC.md index 3618a13..e4c6da9 100644 --- a/IETF-RFC.md +++ b/IETF-RFC.md @@ -760,7 +760,8 @@ 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 * DEPRECATED: publicKey (object) - Use public keys at @@ -1132,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 @@ -1142,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& @@ -1463,22 +1466,27 @@ Content-Digest: sha-256=:LkpHyFOVbBDPxc7YbHDOWNzAv88qWuVfLNf4TUf9Uo8=: } ``` -The signature base is constructed according to [RFC9421]: +The signature base is constructed according to [RFC9421] (with line +breaks in @signature-params for display purposes only): ``` "@method": POST "@target-uri": https://receiver.example.org/ocm/shares "content-digest": sha-256=:=: -"@signature-params": ("@method" "@target-uri" "content-digest")\ - ;created=;keyid="sender.example.org#key1";alg="ed25519" +"@signature-params": ("@method" "@target-uri" "content-digest"); + created=; + keyid="sender.example.org#key1"; + alg="ed25519" ``` Sign this base using for example Ed25519 ([RFC8032]) to produce the -signature, then add headers: +signature, then add headers (line breaks for display purposes only): ``` -Signature-Input: sig1=("@method" "@target-uri" "content-digest")\ - ;created=;keyid="sender.example.org#key1";alg="ed25519" +Signature-Input: sig1=("@method" "@target-uri" "content-digest"); + created=; + keyid="sender.example.org#key1"; + alg="ed25519" Signature: sig1=:=: ```