From 2cf522957267be0b7b601c41e42166f312011e53 Mon Sep 17 00:00:00 2001 From: Mahdi Baghbani Date: Thu, 12 Nov 2026 08:04:37 +0000 Subject: [PATCH 1/2] docs(ocm): unify server address definitions Signed-off-by: Mahdi Baghbani --- IETF-OCM-IP.md | 30 ++++++++------- IETF-OCM-MLS.md | 17 +++++---- IETF-OCM.md | 99 ++++++++++++++++++++++++++++--------------------- spec.yaml | 63 +++++++++++++++++++++---------- 4 files changed, 124 insertions(+), 85 deletions(-) diff --git a/IETF-OCM-IP.md b/IETF-OCM-IP.md index 5e228d6..8f75245 100644 --- a/IETF-OCM-IP.md +++ b/IETF-OCM-IP.md @@ -289,7 +289,7 @@ at the Protocol Server. on the access protocol) presents the access token to the Protocol Server. 9. The Protocol Server verifies the token against the OCM Server's - published keys, looks up the Share Record by (issuer domain, + published keys, looks up the Share Record by (issuer server address, `client_id`), cross-checks the identities bound into the token, and serves the protocol-specific Resource access. @@ -676,7 +676,7 @@ Revocation Request: * with a request body containing a JSON document with the following fields: -* REQUIRED `sender` (string) - an OCM Address whose domain part +* REQUIRED `sender` (string) - an OCM Address whose server part identifies the paired OCM Server, subject to the same allowlist and signature checks as all Integration API requests. * REQUIRED `providerId` (string) - the `providerId` of the Share to @@ -869,6 +869,9 @@ understand MUST be ignored. ## Token Verification by the Protocol Server +Here, `` is the authority of the `iss` URL: its host and +optional port, used as an OCM Server Address as defined in [OCM]. + When a front-channel request presents a credential, the Protocol Server MUST authorize it as follows: @@ -881,7 +884,7 @@ instead: an `active` response supplies the fields (`iss`, `sub`, `aud`, `exp`, `client_id`, `ocm_ip`) used in steps 4 to 6, and steps 2 and 3 are skipped; an inactive response means the request is rejected. 2. Resolve the signing key: fetch (or use a cached copy of) the OCM -Server's Discovery document at `https:///.well-known/ocm`, +Server's Discovery document at `https:///.well-known/ocm`, read its `jwksUri` field, fetch the JWK Set from that URL, and select the key matching the token's `kid` header parameter. 3. Verify the token signature and validity per [RFC9068]: the algorithm @@ -889,14 +892,14 @@ MUST be an asymmetric algorithm matching the key, MUST NOT be `none`, and the `exp` claim MUST be in the future. The claims `iss`, `sub`, `aud`, `exp` and `client_id` MUST all be present. 4. Determine the integration mode: - * If a Share Record exists for the pair (host part of `iss`, + * If a Share Record exists for the pair (server address from `iss`, `client_id` claim), the token is authorized against that record (Provisioned Integration). Any `ocm_ip` claim in the token MUST be ignored. * Otherwise, if the verified token carries an `ocm_ip` claim and the - host part of `iss` is paired for Self-Contained Integration, or the - introspection response carries an `ocm_ip` member, the credential is - authorized against those claims. + server address from `iss` is paired for Self-Contained Integration, + or the introspection response carries an `ocm_ip` member, the + credential is authorized against those claims. * Otherwise, reject the request. Because Share Records only come into existence through signed back-channel requests from paired OCM Servers, the `ocm_ip` claim is only honored for paired issuers, and @@ -923,14 +926,13 @@ In Provisioned Integration, a valid signature and a matching Share Record are not sufficient: the token MUST also be bound to the identities stored in the record. The Protocol Server MUST verify that: -* the OCM Address formed as `@` equals the Share Record's -`owner`, and +* the OCM Address formed as `@` equals the Share +Record's `owner`, and * the `aud` claim equals the Share Record's `shareWith`. -Comparison of OCM Addresses SHOULD be performed after canonicalising the -host part (lowercasing, removing any stray scheme prefix or trailing -slash); the identifier part is opaque and MUST be compared byte for -byte. +OCM Addresses MUST be validated and compared as defined in [OCM]. +A scheme or trailing slash in a server address is invalid input; it +MUST NOT be removed to make identities match. These checks ensure that an access token can only be used for the exact Share it was issued for: a token legitimately issued to one Receiving @@ -943,7 +945,7 @@ come from an introspection response that names a Share Record via In Self-Contained Integration, and when serving from the `ocm_ip` member of an introspection response, there is no stored record to compare against: the credential itself is the authority, and the owner and the -Receiving Party are read directly from `@` and `aud`. +Receiving Party are read directly from `@` and `aud`. The cross-checks above therefore do not apply; what remains is to enforce the scope of the `ocm_ip` claim (protocol entries, permissions, expiration) and, where the protocol concerned authenticates the diff --git a/IETF-OCM-MLS.md b/IETF-OCM-MLS.md index 26c22ba..b1309cb 100644 --- a/IETF-OCM-MLS.md +++ b/IETF-OCM-MLS.md @@ -431,7 +431,8 @@ The request has two REQUIRED query parameters: - `userId` is the full OCM Address of the user whose KeyPackage is requested. -- `senderDomain` is the FQDN of the requesting OCM Server. +- `senderDomain` is the requesting server's OCM Server Address +as defined in [OCM]. Response: @@ -1345,13 +1346,13 @@ the application data: ~~~ Each entry identifies the share by its `providerId`, the group by its -OCM Address in `groupId`, and the intended Member Server by its FQDN in -`recipient`. A Member Server MUST ignore entries whose `recipient` does -not name itself. Upon accepting an entry, the Member Server replaces -the stored transport credential for that share; replacement follows the -same `(epoch, generation)` supersede rule as wrapped FKs -({{key-distribution}}). The sending server invalidates the old -credential once the rotation has been delivered. +OCM Address in `groupId`, and the intended Member Server by its OCM +Server Address in `recipient`. A Member Server MUST ignore entries whose +`recipient` does not name itself. Upon accepting an entry, the Member +Server replaces the stored transport credential for that share; +replacement follows the same `(epoch, generation)` supersede rule as +wrapped FKs ({{key-distribution}}). The sending server invalidates the +old credential once the rotation has been delivered. Encrypting the credential update in the current epoch guarantees that the removed member cannot read it, independent of transport protection: diff --git a/IETF-OCM.md b/IETF-OCM.md index 44a647e..af36fbf 100644 --- a/IETF-OCM.md +++ b/IETF-OCM.md @@ -124,9 +124,9 @@ related concepts from OAuth [RFC6749] and elsewhere: * __Invite Sender OCM Server__ - The server holding an address book used by the Invite Sender, to which details of the Invite Receiver are to be added. -* __Invite String__ - An Invite Token and the FQDN of an Invite Sender - OCM Server joined by an `@`-sign, then encoded using base64url (the - URL- and filename-safe alphabet defined in [RFC4648], Section 5) with +* __Invite String__ - An Invite Token and the Invite Sender OCM Server + Address joined by an `@`-sign, then encoded using base64url (the URL- + and filename-safe alphabet defined in [RFC4648], Section 5) with padding omitted. * __Invite Token__ - A hard-to-guess string used in the Invite Flow, generated by the Invite Sender OCM Server and linked uniquely to the @@ -139,10 +139,10 @@ related concepts from OAuth [RFC6749] and elsewhere: ocm-address = identifier "@" host [ ":" port] - "identifier" is an opaque, case-sensitive UTF-8 string. It is - separated from the host by the last "@" in the OCM Address. It is - possible to have multiple @-signs in a OCM-address, e.g. when an - email address is the local part of the address like + "identifier" is a non-empty, opaque, case-sensitive UTF-8 string. It + is separated from the host by the last "@" in the OCM Address. It is + possible to have multiple @-signs in a OCM-address, e.g. when an email + address is the local part of the address like `nomen.nescio@example.org@cloud.example.org`. "host" is an IP literal encapsulated within square brackets, an IPv4 address in dotted decimal form, or a registered name as described in @@ -155,6 +155,19 @@ related concepts from OAuth [RFC6749] and elsewhere: The OCM Server MUST be discoverable at the given host and optional port via the Well-Known [RFC8615] path `/.well-known/ocm`. The OCM Address MUST NOT contain a path. +* __OCM Server Address__ - The server part of an OCM Address: + its host and optional port, without an identifier or `@`. It has no + scheme, path, query or fragment. For example, `cloud.example.org`, + `cloud.example.org:8443`, `192.0.2.1` or `[2001:db8::1]:8443`. + Server addresses MUST be compared as complete strings after + lowercasing, preserving explicit ports and all other spelling. + OCM Addresses use this comparison for their server part and compare + identifiers byte for byte. An implementation MUST NOT strip a + scheme, infer a port, resolve a name or rewrite an IP spelling to + make identities match. Invalid input MUST be rejected before use; + an address that cannot be used for discovery MUST fail rather than + select another server. Discovered endpoint URLs locate services; + they do not change the server's identity. * __OCM API Discovery__ - Process of evaluating properties of a Remote Resource, after establishing contact with an OCM Server. * __OCM Notification__ - A message from the Receiving Server to the @@ -214,9 +227,9 @@ related concepts from OAuth [RFC6749] and elsewhere: another OCM Server, based on out-of-band information, federation membership or prior interactions, SHOULD be recorded in an internal registry of trusted servers, that SHOULD be updated over time based - on new information. The registry SHOULD include the FQDN of the - trusted server and the public keys (JWK Set [RFC7517]) used for - HTTP Message Signatures. It MAY + on new information. The registry SHOULD include the + trusted server's OCM Server Address and the public keys + (JWK Set [RFC7517]) used for HTTP Message Signatures. It MAY also include additional metadata such as the inviteAcceptDialog URL or supported capabilities. * __WAYF Page__ - A Where-Are-You-From page is a discovery service used @@ -289,11 +302,11 @@ a description of the whole protocol in OpenAPI format. # Establishing Contact Before the Sending Server can send a Share Creation Notification to the -Receiving Server, it MUST establish the Receiving Party's OCM -Address (containing the Receiving Server's FQDN, and the Receiving -Party's identifier), among other things. Some steps may preceed the +Receiving Server, it MUST establish the Receiving Party's OCM Address +(containing the Receiving Party's identifier and the Receiving Server's +OCM Server Address), among other things. Some steps may preceed the Sending Gesture, allowing the Sending Party to establish (with some -level of trust) the OCM Address of the Receiving Party. In other cases, +level of trust) the OCM Address of the Receiving Party. In other cases, establishing the OCM Address of the Receiving Party happens as part of the Sending Gesture. @@ -343,7 +356,7 @@ Share Creation Notification from an unknown Sending Party is received the Invite Sender to create the Invite Message * the Invite Sender uses some out-of-band communication to send the Invite Message, containing the Invite Token and the Invite Sender OCM - Server FQDN, to the Invite Receiver + Server Address, to the Invite Receiver * the Invite Receiver navigates to the Invite Receiver OCM Server and makes the Invite Acceptance Gesture. This step MAY be facilitated if the Invite Sender OCM Server implements a WAYF Page, such that the @@ -368,16 +381,16 @@ Acceptance Request MUST be a HTTP POST request: * using `application/json` as the `Content-Type` HTTP request header * its request body containing a JSON document representing an object with the following string fields: - - REQUIRED: `recipientProvider` - FQDN of the Invite Receiver OCM - Server. + - REQUIRED: `recipientProvider` - OCM Server Address of the Invite + Receiver. - REQUIRED: `token` - The Invite Token. The Invite Sender OCM Server SHOULD recall which Invite Sender OCM Address this token was linked to. - REQUIRED: `userID` - The Invite Receiver's identifier at their OCM Server. - REQUIRED: `email` - Non-normative / informational; an email address - for the Invite Receiver. Not necessarily at the same FQDN as their - OCM Server. + for the Invite Receiver. Its domain need not match the + OCM Server Address. - REQUIRED: `name` - Human-readable name of the Invite Receiver, as a suggestion for display in the Invite Sender's address book * using TLS @@ -426,8 +439,8 @@ The Invite Acceptance Response SHOULD be a HTTP response: - REQUIRED: `userID` - the Invite Sender's identifier at their OCM Server - REQUIRED: `email` - non-normative / informational; an email address - for the Invite Sender. Not necessarily at the same FQDN as their - OCM Server + for the Invite Sender. Its domain need not match the + OCM Server Address - REQUIRED: `name` - human-readable name of the Invite Sender, as a suggestion for display in the Invite Receiver's address book @@ -483,7 +496,8 @@ To accept an invite, two pieces of information are required: a `token` and a `provider`. There are two recognized formats: * **Invite string format:** - The token and the provider’s FQDN, joined by an `@` sign and then + The token and the provider's OCM Server Address, joined by an `@` + sign and then encoded using base64url (the URL- and filename-safe alphabet defined in [RFC4648], Section 5) with padding omitted. Example: @@ -547,7 +561,7 @@ delegated by the Sending Party, which is one of its registered users. Fourth, related to the second one, it removes the partial 'open relay' problem that exists when the Sending Server is allowed to include any -Receiving Server FQDN in the Sending Gesture. Without the use of +Receiving Server Address in the Sending Gesture. Without the use of Invites, a Distributed Denial of Service attack could be organised if many internet users collude to flood a given OCM Server with Share Creation Notifications which will be hard to distinguish from @@ -570,7 +584,7 @@ The Sharing User MUST provide the following information: * Protocol to be offered for access * Sending Party's identifier * Receiving Party's identifier -* Receiving Server FQDN +* Receiving Server Address * OPTIONAL: Share Requirements * OPTIONAL: Share Name * OPTIONAL: Share Permissions @@ -586,12 +600,12 @@ The next step is for the Sending Server to additionally discover: `jwksUri` field The Sending Server MAY first perform denylist and allowlist checks on -the FQDN. +the OCM Server Address. If a finite allowlist of Receiving Servers exists on the Sending Server side, then this list MAY already contain all necessary information. -If the FQDN passes the denylist and/or allowlist checks, but no details +If the server address passes these checks, but no details about its OCM API are known, the Sending Server can use the following process to try to fetch this information from the Receiving Server. @@ -608,13 +622,13 @@ Index](#appendix-e-navigation-index). ## Process At the start of the process, the Discovering Server has either an OCM -Address, or just an FQDN from for instance the `recipientProvider` -field of an Invite Acceptance Request. +Address, or an OCM Server Address from, for instance, the +`recipientProvider` field of an Invite Acceptance Request. -Step 1: In case it has an OCM Address, it SHOULD first extract `` -from it (the part after the last `@` sign). +Step 1: From an OCM Address, extract its OCM Server Address +(the part after the last `@` sign), called `` below. Step 2: The Discovering Server SHOULD attempt OCM API Discovery via a -HTTP GET request to `https:///.well-known/ocm`. +HTTP GET request to `https:///.well-known/ocm`. Step 3: If that results in a valid HTTP response with a valid JSON response body within reasonable time, go to step 5. Step 4: If not, fail. Implementations MAY fallback to HTTP instead @@ -1342,14 +1356,14 @@ following hold true: * the HTTP Message Signature is missing but the Sending Server advertises the `http-sig` capability in the Discovery response - obtained from the FQDN part of the `sender` field in the request + obtained from the server part of the `sender` field in the request body * the Sending Server advertises the `http-sig` capability but its Discovery response carries no `jwksUri` * the HTTP Message Signature is missing * the HTTP Message Signature is not valid (see [HTTP Message Signatures](#http-message-signatures)) -* no trusted JWK Set can be obtained for the FQDN part of the +* no trusted JWK Set can be obtained for the server part of the `sender` field in the request body, via the `jwksUri` field of its Discovery response * that JWK Set contains no key whose `kid` equals the `keyid` @@ -1398,10 +1412,10 @@ request: * REQUIRED notificationType (string) - it MUST be one of the registered values listed in the "OCM Notification Types" registry (see [IANA Considerations](#iana-considerations)). -* REQUIRED senderDomain (string) - the FQDN of the sender. A Receiving - Server SHOULD use this information to verify the HTTP message - signature on the request, and it SHOULD also use it to validate the - underlying share or resource the notification is about. +* REQUIRED senderDomain (string) - the OCM Server Address of the + sender. A Receiving Server SHOULD use this information to verify + the HTTP message signature and validate the underlying share or + resource. * OPTIONAL resourceType (string) - the type of the resource this notification is about (e.g., `file`). * OPTIONAL shareType (string) - the type of recipient this notification @@ -1577,7 +1591,7 @@ To access the Resource, the Receiving Server MAY use multiple ways, depending on the body of the Share Creation Notification and the protocol required for access. The procedure is as follows: -1. The receiver MUST extract the OCM Server FQDN from the `sender` +1. The receiver MUST extract the OCM Server Address from `sender` field of the received share, and MUST query the [Discovery](#ocm-api-discovery) endpoint at that address: let `` be the `resourceTypes[0].protocols.webdav` value @@ -1692,7 +1706,7 @@ grant_type=authorization_code&client_id=receiver.example.org&code=\ The request MUST be signed using an HTTP Message Signature [RFC9421]. The `client_id` identifies the Receiving Server and MUST be -set to its fully qualified domain name. The `code` parameter carries +set to its OCM Server Address. The `code` parameter carries the authorization secret that was issued by the Sending Server in the Share Creation Notification. It is allowed to send the additional parameters defined in [RFC6749] for the `authorization_code` grant type, @@ -1841,9 +1855,8 @@ Change Controller: Reference or Description: : An object representing an OCM address. The object contains: - - "address" (String, required): The OCM federated address in - format "user@provider" where provider is the FQDN of an - OCM-capable server. + - "address" (String, required): The full OCM Address, as + defined in [Terms](#terms). - "trusted" (Boolean, optional): Whether shares from this address are automatically accepted. Default: false. - "source" (String, optional): How this address was established. @@ -2614,7 +2627,7 @@ user such as if it was added via _Invites_ or direct entry. * __email__: Contact email address (informational) * __name__: Human-readable display name * __userID__: The identifier of the contact at their OCM Server -* __provider__: The FQDN of the contact's OCM Server +* __provider__: The contact's OCM Server Address ### Relationships diff --git a/spec.yaml b/spec.yaml index de37e05..430848f 100644 --- a/spec.yaml +++ b/spec.yaml @@ -20,7 +20,7 @@ paths: /.well-known/ocm: servers: - url: https://cloud.example.org - description: The OCM Server's top-level FQDN. + description: The origin used for OCM API discovery. get: summary: Discovery endpoint description: > @@ -52,15 +52,14 @@ paths: required: true description: Full OCM Address of the user whose KeyPackage is requested. schema: - type: string + $ref: "#/components/schemas/OCMAddress" example: alice@cloud.example.org - name: senderDomain in: query required: true - description: FQDN of the requesting OCM Server. + description: OCM Server Address of the requesting server. schema: - type: string - format: fqdn + $ref: "#/components/schemas/OCMServerAddress" example: requester.example.org responses: "200": @@ -397,7 +396,8 @@ components: - keyPackages properties: userId: - type: string + allOf: + - $ref: "#/components/schemas/OCMAddress" description: Full OCM Address of the user represented by the KeyPackages. example: alice@cloud.example.org keyPackages: @@ -651,7 +651,8 @@ components: - protocol properties: shareWith: - type: string + allOf: + - $ref: "#/components/schemas/OCMAddress" description: > OCM Address of the user, group or federation the provider wants to share the resource with. This MUST be known @@ -677,13 +678,15 @@ components: owner: description: | OCM Address of the user who owns the resource. - type: string + allOf: + - $ref: "#/components/schemas/OCMAddress" example: 6358b71804dfa8ab069cf05ed1b0ed2a@apiwise.nl sender: description: | OCM Address of the user that wants to share the resource. - type: string + allOf: + - $ref: "#/components/schemas/OCMAddress" example: 527bd5b5d689e2c32ae974c6229ff785@apiwise.nl ownerDisplayName: type: string @@ -1095,10 +1098,11 @@ components: MAY be used here. Implementers MAY define additional custom types, provided that they are registered in the OCM IANA Registry. senderDomain: - type: string + allOf: + - $ref: "#/components/schemas/OCMServerAddress" description: > - The FQDN of the sending OCM server, in order for the recipient - to validate its key material when verifying the HTTP Signature. + OCM Server Address of the sender, used to discover its + key material and verify the HTTP Message Signature. resourceType: type: string description: > @@ -1180,6 +1184,23 @@ components: mlsGroupId: 51dc30ddc473d43a6011e9ebba6ca770 userId: bob@cloud.example.org content: base64 MLS welcome + OCMServerAddress: + type: string + minLength: 1 + description: > + The host and optional port of an OCM Server, as defined in Terms + of the OCM Internet-Draft. No identifier, scheme, path, query or + fragment. Compare the complete value after lowercasing, preserving + explicit ports and all other spelling. + example: cloud.example.org:8443 + OCMAddress: + type: string + minLength: 1 + description: > + An OCM Address as defined in Terms of the OCM Internet-Draft: + a non-empty opaque identifier followed by "@" and an OCM Server + Address. Split at the last "@"; compare identifiers byte for byte. + example: alice@cloud.example.org:8443 AcceptedInvite: type: object required: @@ -1190,9 +1211,9 @@ components: - name properties: recipientProvider: - type: string - format: fqdn - description: FQDN of the receiver OCM service. + allOf: + - $ref: "#/components/schemas/OCMServerAddress" + description: OCM Server Address of the Invite Receiver. example: receiver.org token: type: string @@ -1237,13 +1258,15 @@ components: - share properties: owner: - type: string + allOf: + - $ref: "#/components/schemas/OCMAddress" description: > OCM Address of the user who will be requested to share the resource. example: 527bd5b5d689e2c32ae974c6229ff785@apiwise.nl shareWith: - type: string + allOf: + - $ref: "#/components/schemas/OCMAddress" description: > OCM Address of the user, group or federation that wants to receive a share of the resource. @@ -1260,9 +1283,9 @@ components: - code properties: client_id: - type: string - format: fqdn - description: FQDN of the Receiving Server. + allOf: + - $ref: "#/components/schemas/OCMServerAddress" + description: OCM Server Address of the Receiving Server. example: receiver.org code: type: string From dbb732b38b67fdfbbfa6df43c611f10e644177fd Mon Sep 17 00:00:00 2001 From: Mahdi Baghbani Date: Thu, 12 Nov 2026 10:17:16 +0000 Subject: [PATCH 2/2] docs(ocm): clarify identity comparison Signed-off-by: Mahdi Baghbani --- IETF-OCM-IP.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/IETF-OCM-IP.md b/IETF-OCM-IP.md index 8f75245..1b5494e 100644 --- a/IETF-OCM-IP.md +++ b/IETF-OCM-IP.md @@ -930,9 +930,10 @@ identities stored in the record. The Protocol Server MUST verify that: Record's `owner`, and * the `aud` claim equals the Share Record's `shareWith`. -OCM Addresses MUST be validated and compared as defined in [OCM]. -A scheme or trailing slash in a server address is invalid input; it -MUST NOT be removed to make identities match. +OCM Addresses MUST be compared as defined in the Terms section of [OCM]: +compare opaque identifiers byte for byte and complete server addresses +after lowercasing. Invalid server addresses MUST be rejected, rather +than repaired by stripping a scheme or trailing slash. These checks ensure that an access token can only be used for the exact Share it was issued for: a token legitimately issued to one Receiving @@ -950,7 +951,7 @@ The cross-checks above therefore do not apply; what remains is to enforce the scope of the `ocm_ip` claim (protocol entries, permissions, expiration) and, where the protocol concerned authenticates the Receiving Party, to derive that identity from `aud` using the same -canonicalisation rules. +address comparison rules. ## Token Presentation per Protocol